Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect hot-reload issues and fix them #1401

Merged
merged 13 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 73 additions & 70 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,74 @@
// @ts-check

// Find google translate issues: https://github.com/facebook/react/issues/11538#issuecomment-390386520
const noGoogleTranslateCrashingSyntax = [
// Ban `condition && text node`
{
selector:
'JSXElement > JSXExpressionContainer > LogicalExpression[operator="&&"]' +
'[right.type!="JSXElement"][right.type!="JSXFragment"]',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban `condition && <>text node</>`
{
selector:
'JSXElement > JSXExpressionContainer > LogicalExpression[operator="&&"]' +
' > JSXFragment > .children:not(JSXElement, JSXText[value=/^\\s+$/])',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban text nodes before or after a condition `text {condition && <span/>} text`
{
selector:
'JSXElement:has(JSXExpressionContainer.children > LogicalExpression[operator="&&"])' +
' > JSXText[value!=/^\\s+$/]',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Wrap text into an element.',
},
// Ban variables before or after `{var} {condition && <span/>} {var}` (just in case they return a string)
{
selector:
'JSXElement:has(JSXExpressionContainer.children > LogicalExpression[operator="&&"])' +
' > JSXExpressionContainer:matches([expression.type="Identifier"], [expression.type="CallExpression"])',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Identifier could possibly return a string, so wrap it into an element.',
},

// Ban `condition ? text node : <span/>`
{
selector:
'JSXElement > JSXExpressionContainer > ConditionalExpression' +
' > :matches(.consequent, .alternate):not(JSXElement, JSXFragment)',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban `condition ? <>text node</> : <span/>`
{
selector:
'JSXElement > JSXExpressionContainer > ConditionalExpression' +
' > JSXFragment > .children:not(JSXElement, JSXText[value=/^\\s+$/])',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban text nodes before or after a condition `text {condition ? <span/> : <span/>} text`
{
selector:
'JSXElement:has(JSXExpressionContainer.children > ConditionalExpression)' +
' > JSXText[value!=/^\\s+$/]',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Wrap text into an element.',
},
// Ban variables before or after `{var} {condition ? <span/> : <span/>} {var}` (just in case they return a string)
{
selector:
'JSXElement:has(JSXExpressionContainer.children > ConditionalExpression)' +
' > JSXExpressionContainer:matches([expression.type="Identifier"], [expression.type="CallExpression"])',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Identifier could possibly return a string, so wrap it into an element.',
},
]

/** @type { import('eslint').Linter.Config } */
const config = {
extends: [
Expand All @@ -9,6 +78,7 @@ const config = {
'react-app', // https://github.com/facebook/create-react-app/blob/main/packages/eslint-config-react-app/index.js
'plugin:prettier/recommended', // See .prettierrc
],
plugins: ['react-refresh'],
parser: '@typescript-eslint/parser',

settings: {
Expand Down Expand Up @@ -67,81 +137,14 @@ const config = {
},
],
'prefer-template': 'error',
'no-restricted-syntax': [
// Find google translate issues: https://github.com/facebook/react/issues/11538#issuecomment-390386520
'error',

// Ban `condition && text node`
{
selector:
'JSXElement > JSXExpressionContainer > LogicalExpression[operator="&&"]' +
'[right.type!="JSXElement"][right.type!="JSXFragment"]',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban `condition && <>text node</>`
{
selector:
'JSXElement > JSXExpressionContainer > LogicalExpression[operator="&&"]' +
' > JSXFragment > .children:not(JSXElement, JSXText[value=/^\\s+$/])',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban text nodes before or after a condition `text {condition && <span/>} text`
{
selector:
'JSXElement:has(JSXExpressionContainer.children > LogicalExpression[operator="&&"])' +
' > JSXText[value!=/^\\s+$/]',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Wrap text into an element.',
},
// Ban variables before or after `{var} {condition && <span/>} {var}` (just in case they return a string)
{
selector:
'JSXElement:has(JSXExpressionContainer.children > LogicalExpression[operator="&&"])' +
' > JSXExpressionContainer:matches([expression.type="Identifier"], [expression.type="CallExpression"])',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Identifier could possibly return a string, so wrap it into an element.',
},

// Ban `condition ? text node : <span/>`
{
selector:
'JSXElement > JSXExpressionContainer > ConditionalExpression' +
' > :matches(.consequent, .alternate):not(JSXElement, JSXFragment)',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban `condition ? <>text node</> : <span/>`
{
selector:
'JSXElement > JSXExpressionContainer > ConditionalExpression' +
' > JSXFragment > .children:not(JSXElement, JSXText[value=/^\\s+$/])',
message:
'Conditional plain text nodes could break React if used with Google Translate. Wrap text into an element.',
},
// Ban text nodes before or after a condition `text {condition ? <span/> : <span/>} text`
{
selector:
'JSXElement:has(JSXExpressionContainer.children > ConditionalExpression)' +
' > JSXText[value!=/^\\s+$/]',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Wrap text into an element.',
},
// Ban variables before or after `{var} {condition ? <span/> : <span/>} {var}` (just in case they return a string)
{
selector:
'JSXElement:has(JSXExpressionContainer.children > ConditionalExpression)' +
' > JSXExpressionContainer:matches([expression.type="Identifier"], [expression.type="CallExpression"])',
message:
'Plain text nodes before or after a condition could break React if used with Google Translate. Identifier could possibly return a string, so wrap it into an element.',
},
],
'no-restricted-syntax': ['error', ...noGoogleTranslateCrashingSyntax],

'react/jsx-no-target-blank': ['error', { allowReferrer: true }],
'react/react-in-jsx-scope': 'off', // Not needed after React v17
'react/display-name': 'off', // TODO: Maybe enable

'react-refresh/only-export-components': 'error',

'@typescript-eslint/no-empty-function': 'off', // Allow empty reducers for saga
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-react-refresh": "0.3.4",
"i18next-scanner": "4.2.0",
"i18next-scanner-typescript": "1.1.1",
"jest": "29.5.0",
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/BuildPreviewBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Alert } from 'grommet-icons/es6/icons/Alert'
import { Box } from 'grommet/es6/components/Box'
import { Text } from 'grommet/es6/components/Text'
import { AlertBox } from 'app/components/AlertBox'
import { mobileHeaderZIndex } from '../Sidebar'
import { buildPreviewBannerZIndex } from '../../../styles/theme/elementSizes'

export const BuildPreviewBanner = () => {
const { t } = useTranslation()
Expand All @@ -18,7 +18,7 @@ export const BuildPreviewBanner = () => {
style={{
position: 'sticky',
top: 0,
zIndex: mobileHeaderZIndex - 1,
zIndex: buildPreviewBannerZIndex,
}}
>
<AlertBox color="status-warning">
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/DateFormatter/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react'

import { intlDateTimeFormat, DateFormatter } from '..'
import { DateFormatter } from '..'
import { intlDateTimeFormat } from '../intlDateTimeFormat'
import { buildDatetime } from '../../../../../internals/getBuildData'

describe('intlDateTimeFormat', () => {
Expand Down
23 changes: 2 additions & 21 deletions src/app/components/DateFormatter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,12 @@
*
*/
import * as React from 'react'
import { intlDateTimeFormat } from './intlDateTimeFormat'

interface Props {
date: Date | number
}

const dateFormat = new Intl.DateTimeFormat(process?.env?.NODE_ENV === 'test' ? 'en-US' : undefined, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZone: process?.env?.NODE_ENV === 'test' ? 'UTC' : undefined,
})

/**
* Narrow No-Brake Space
*
* This is used by the date formatter.
* See https://unicodeplus.com/U+202F
*/
const NNBSP = '\u202F'

export const intlDateTimeFormat = (date: Date | number) => dateFormat.format(date).replaceAll(NNBSP, ' ')

export function DateFormatter(props: Props) {
return <>{intlDateTimeFormat(props.date).replaceAll(NNBSP, ' ')}</>
return <>{intlDateTimeFormat(props.date)}</>
}
19 changes: 19 additions & 0 deletions src/app/components/DateFormatter/intlDateTimeFormat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const dateFormat = new Intl.DateTimeFormat(process?.env?.NODE_ENV === 'test' ? 'en-US' : undefined, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZone: process?.env?.NODE_ENV === 'test' ? 'UTC' : undefined,
})

/**
* Narrow No-Brake Space
*
* This is used by the date formatter.
* See https://unicodeplus.com/U+202F
*/
const NNBSP = '\u202F'

export const intlDateTimeFormat = (date: Date | number) => dateFormat.format(date).replaceAll(NNBSP, ' ')
5 changes: 3 additions & 2 deletions src/app/components/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import React, { memo } from 'react'
import { useSelector } from 'react-redux'
import { Trans, useTranslation } from 'react-i18next'
import { selectIsOpen } from 'app/state/wallet/selectors'
import { intlDateTimeFormat } from '../DateFormatter'
import { intlDateTimeFormat } from '../DateFormatter/intlDateTimeFormat'
import { backend } from 'vendors/backend'
import { BackendAPIs } from 'config'
import { MobileFooterNavigation, mobileFooterNavigationHeight } from '../MobileFooterNavigation'
import { MobileFooterNavigation } from '../MobileFooterNavigation'
import { mobileFooterNavigationHeight } from '../../../styles/theme/elementSizes'

const githubLink = 'https://github.com/oasisprotocol/oasis-wallet-web/'
const githubReleaseLink = (tag: string) => `${githubLink}releases/tag/${tag}`
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/MobileFooterNavigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { normalizeColor } from 'grommet/es6/utils'
import { NavLink } from 'react-router-dom'
import { selectAddress } from 'app/state/wallet/selectors'
import { useParaTimesNavigation } from 'app/pages/ParaTimesPage/useParaTimesNavigation'
import { IS_FIAT_ONRAMP_ENABLED } from '../../pages/FiatOnramp'
import { IS_FIAT_ONRAMP_ENABLED } from '../../pages/FiatOnramp/isEnabled'
import { mobileFooterNavigationHeight } from '../../../styles/theme/elementSizes'

export const mobileFooterNavigationHeight = '4rem'
const StyledMobileFooterNavigation = styled.nav`
background-color: ${({ theme }) => normalizeColor('background-front', theme)};
position: fixed;
Expand Down
55 changes: 55 additions & 0 deletions src/app/components/Modal/ModalContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useCallback } from 'react'
import { Box } from 'grommet/es6/components/Box'
import { Button } from 'grommet/es6/components/Button'
import { Layer } from 'grommet/es6/components/Layer'
import { Paragraph } from 'grommet/es6/components/Paragraph'
import { useTranslation } from 'react-i18next'
import { Alert } from 'grommet-icons/es6/icons/Alert'
import { Checkmark } from 'grommet-icons/es6/icons/Checkmark'
import { Close } from 'grommet-icons/es6/icons/Close'
import { ModalHeader } from 'app/components/Header'

export interface Modal {
title: string
description: string
handleConfirm: () => void
isDangerous: boolean
}

interface ModalContainerProps {
modal: Modal
closeModal: () => void
}

export const ModalContainer = ({ modal, closeModal }: ModalContainerProps) => {
const { t } = useTranslation()
const confirm = useCallback(() => {
modal.handleConfirm()
closeModal()
}, [closeModal, modal])

return (
<Layer modal onEsc={closeModal} onClickOutside={closeModal} background="background-front">
<Box margin="medium">
<ModalHeader>{modal.title}</ModalHeader>
<Paragraph fill>{modal.description}</Paragraph>
<Box direction="row" gap="small" justify="between" pad={{ top: 'large' }}>
<Button
label={t('common.cancel', 'Cancel')}
onClick={closeModal}
secondary
icon={<Close size="18px" />}
/>
<Button
label={t('common.confirm', 'Confirm')}
onClick={confirm}
disabled={modal.isDangerous}
primary={modal.isDangerous}
color={modal.isDangerous ? 'status-error' : ''}
icon={modal.isDangerous ? <Alert size="18px" /> : <Checkmark size="18px" />}
/>
</Box>
</Box>
</Layer>
)
}
Loading