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

frontend: add eslint rule restrict-template-expressions #2325

Merged
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
12 changes: 11 additions & 1 deletion frontends/web/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,17 @@
"overrides": [
{
"files": ["**/*.ts?(x)"],
"rules": { }
"rules": {
"@typescript-eslint/restrict-template-expressions": ["error", {
"allowNumber": true,
"allowAny": false,
"allowBoolean": false,
"allowNullish": false
}]
},
"parserOptions": {
"project": ["./tsconfig.json"]
}
},
{
"files": ["**/*.test.ts?(x)"],
Expand Down
2 changes: 1 addition & 1 deletion frontends/web/src/api/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const subscribeEndpoint = <T>(
.catch(console.error);
break;
default:
throw new Error(`Event: ${event} not supported`);
throw new Error(`Event: ${JSON.stringify(event)} not supported`);
}
});
};
Expand Down
2 changes: 1 addition & 1 deletion frontends/web/src/components/forms/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const Select = ({
<select id={id} {...props}>
{options.map(({ value, text, disabled = false }) => (
<option
key={`${value}`}
key={String(value)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? I guess best practice, right?

Copy link
Collaborator Author

@thisconnect thisconnect Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without converting to string somehow i.e. String() this will error with:

Type 'string | number | readonly string[] | undefined' is not assignable to type 'Key | null | undefined'.
  Type 'readonly string[]' is not assignable to type 'Key | null | undefined'.ts(2322)
index.d.ts(261, 9): The expected type comes from property 'key' which is declared here on type 'DetailedHTMLProps<OptionHTMLAttributes<HTMLOptionElement>, HTMLOptionElement>'
(property) Attributes.key?: Key | null | undefined

We use the intrinsic element option (prob shouldn't :) ) this has the type value?: string | readonly string[] | number | undefined;

    interface OptionHTMLAttributes<T> extends HTMLAttributes<T> {
        disabled?: boolean | undefined;
        label?: string | undefined;
        selected?: boolean | undefined;
        value?: string | readonly string[] | number | undefined;
    }

value={value}
disabled={disabled}
>
Expand Down
8 changes: 0 additions & 8 deletions frontends/web/src/components/icon/logo.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,8 @@
width: 100% !important;
}

.swissOpenSource.large {
max-width: 280px !important;
}

@media (max-width: 768px) {
.swissOpenSource {
max-width: 180px !important;
}

.swissOpenSource.large {
max-width: 210px !important;
}
}
41 changes: 20 additions & 21 deletions frontends/web/src/components/icon/logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,22 @@ import PAXG_GREY from './assets/paxg-white.svg';
import ShiftLogo from './assets/shift-cryptosecurity-logo.svg';
import style from './logo.module.css';

interface GenericProps {
[property: string]: any;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed and replaced by type ImgProps = JSX.IntrinsicElements['img']; as we already do in icons.tsx


export const BitBox = (props: GenericProps) => <img {...props} draggable={false} src={BitBoxLogo} alt="BitBox" className={style.logo} />;
export const BitBox02 = (props: GenericProps) => <img {...props} draggable={false} src={BitBox02Logo} alt="BitBox02" className={style.logo} />;
export const BitBox02Inverted = (props: GenericProps) => <img {...props} draggable={false} src={BitBox02InvertedLogo} alt="BitBox02" className={style.logo} />;
export const AppLogo = (props: GenericProps) => <img {...props} draggable={false} src={AppLogoImg} alt="BitBox" className={style.logo} />;
export const AppLogoInverted = (props: GenericProps) => <img {...props} draggable={false} src={AppLogoInvertedImg} alt="BitBox" className={style.logo} />;
export const BitBoxSwiss = (props: GenericProps) => <img {...props} draggable={false} src={BitBoxSwissLogo} alt="BitBox" className={style.logo} />;
export const BitBoxSwissInverted = (props: GenericProps) => <img {...props} draggable={false} src={BitBoxSwissInvertedLogo} alt="BitBox" className={style.logo} />;
export const Shift = (props: GenericProps) => <img {...props} draggable={false} src={ShiftLogo} alt="Shift Crypto" className={style.logo} />;
export const SwissMadeOpenSource = ({ large: boolean, className, ...props }: GenericProps) => <img {...props} draggable={false} src={SwissOpenSourceLight} alt="Swiss Made Open Source" className={`${style.swissOpenSource} ${props.large ? style.large : ''} ${className ? className : ''}`} />;
export const SwissMadeOpenSourceDark = ({ large: boolean, className, ...props }: GenericProps) => <img {...props} draggable={false} src={SwissOpenSourceDark} alt="Swiss Made Open Source" className={`${style.swissOpenSource} ${props.large ? style.large : ''} ${className ? className : ''}`} />;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed large as this didn't work and props.large was always undefined:

({ large, ...props }) => props.large // props.large is always undefined

type ImgProps = JSX.IntrinsicElements['img'];

export const BitBox = (props: ImgProps) => <img {...props} draggable={false} src={BitBoxLogo} alt="BitBox" className={style.logo} />;
export const BitBox02 = (props: ImgProps) => <img {...props} draggable={false} src={BitBox02Logo} alt="BitBox02" className={style.logo} />;
export const BitBox02Inverted = (props: ImgProps) => <img {...props} draggable={false} src={BitBox02InvertedLogo} alt="BitBox02" className={style.logo} />;
export const AppLogo = (props: ImgProps) => <img {...props} draggable={false} src={AppLogoImg} alt="BitBox" className={style.logo} />;
export const AppLogoInverted = (props: ImgProps) => <img {...props} draggable={false} src={AppLogoInvertedImg} alt="BitBox" className={style.logo} />;
export const BitBoxSwiss = (props: ImgProps) => <img {...props} draggable={false} src={BitBoxSwissLogo} alt="BitBox" className={style.logo} />;
export const BitBoxSwissInverted = (props: ImgProps) => <img {...props} draggable={false} src={BitBoxSwissInvertedLogo} alt="BitBox" className={style.logo} />;
export const Shift = (props: ImgProps) => <img {...props} draggable={false} src={ShiftLogo} alt="Shift Crypto" className={style.logo} />;
export const SwissMadeOpenSource = ({ className, ...props }: ImgProps) => <img {...props} draggable={false} src={SwissOpenSourceLight} alt="Swiss Made Open Source" className={`${style.swissOpenSource} ${className ? className : ''}`} />;
export const SwissMadeOpenSourceDark = ({ className, ...props }: ImgProps) => <img {...props} draggable={false} src={SwissOpenSourceDark} alt="Swiss Made Open Source" className={`${style.swissOpenSource} ${className ? className : ''}`} />;

type LogoMap = {
[key in CoinCode]: string[];
[key in CoinCode]: string[];
}

const logoMap: LogoMap = {
Expand All @@ -96,20 +95,20 @@ const logoMap: LogoMap = {
'eth-erc20-paxg': [PAXG, PAXG_GREY],
};

interface Props {
active?: boolean;
alt?: string;
className?: string;
coinCode: CoinCode;
stacked?: boolean;
type LogoProps = {
active?: boolean;
alt?: string;
className?: string;
coinCode: CoinCode;
stacked?: boolean;
}

export const Logo = ({
coinCode,
active,
stacked,
...rest
}: Props) => {
}: LogoProps) => {
if (!logoMap[coinCode]) {
console.error('logo undefined for ', coinCode);
return null;
Expand Down
12 changes: 6 additions & 6 deletions frontends/web/src/contexts/WCWeb3WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import { useLoad } from '@/hooks/api';
import { getConfig, setConfig } from '@/utils/config';

type TProps = {
children: ReactNode;
}
children: ReactNode;
}

export const WCWeb3WalletProvider = ({ children }: TProps) => {
const { t } = useTranslation();
Expand Down Expand Up @@ -83,13 +83,13 @@ export const WCWeb3WalletProvider = ({ children }: TProps) => {
}
await web3wallet?.core.pairing.pair({ uri });
setConfig({ frontend: { hasUsedWalletConnect: true } });
} catch (e: any) {
console.error(`Wallet connect attempt to pair error ${e}`);
if (e.message.includes('Pairing already exists')) {
} catch (error: any) {
console.error('Wallet connect attempt to pair error', error);
if (error?.message?.includes('Pairing already exists')) {
throw new Error(t('walletConnect.useNewUri'));
}
//unexpected error, display native error message
throw new Error(e.message);
throw new Error(error.message);
}
};

Expand Down
2 changes: 1 addition & 1 deletion frontends/web/src/i18n/i18n.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('i18n', () => {
{ nativeLocale: 'fr', newLang: 'en', userLang: 'en' },
];
table.forEach((test) => {
it(`sets userLanguage to ${test.userLang} if native-locale is ${test.nativeLocale}`, async () => {
it(`sets userLanguage to ${test.userLang || 'null'} if native-locale is ${test.nativeLocale}`, async () => {
(apiGet as Mock).mockImplementation(endpoint => {
switch (endpoint) {
case 'config': { return Promise.resolve({}); }
Expand Down
6 changes: 4 additions & 2 deletions frontends/web/src/routes/account/info/buyReceiveCTA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,16 @@ export const BuyReceiveCTA = ({
const isMobile = useMediaQuery('(max-width: 768px)');

const onExchangeCTA = () => navigate(code ? `/exchange/info/${code}` : '/exchange/info');
const onWalletConnect = () => navigate(`/account/${code}/wallet-connect/dashboard`);
const onWalletConnect = () => code && navigate(`/account/${code}/wallet-connect/dashboard`);
const onReceiveCTA = () => {
if (balanceList) {
if (balanceList.length > 1) {
navigate('/accounts/select-receive');
return;
}
navigate(`/account/${code}/receive`);
if (code) {
navigate(`/account/${code}/receive`);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit weird and could be cleaned up by refatoring AddBuyReceiveOnEmptyBalances and BuyReceiveCTA component.

I.e. balanceList is probably not really needed in BuyReceiveCTA and something like a boolean prop.isOnAccount or similar may be enough

}
};

Expand Down
17 changes: 11 additions & 6 deletions frontends/web/src/routes/account/info/info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import { Status } from '@/components/status/status';
import style from './info.module.css';

type TProps = {
accounts: IAccount[];
code: AccountCode;
accounts: IAccount[];
code: AccountCode;
};

export const Info = ({
Expand Down Expand Up @@ -57,6 +57,8 @@ export const Info = ({
setViewXPub((viewXPub + 1) % numberOfXPubs);
};

const xpubType = xpubTypes[(viewXPub + 1) % numberOfXPubs];

return (
<div className="contentWithGuide">
<div className="container">
Expand All @@ -75,10 +77,13 @@ export const Info = ({
current: `${viewXPub + 1}`,
numberOfXPubs: numberOfXPubs.toString(),
scriptType: config.bitcoinSimple.scriptType.toUpperCase(),
})}<br />
<button className={style.nextButton} onClick={showNextXPub}>
{t(`accountInfo.xpubTypeChangeBtn.${xpubTypes[(viewXPub + 1) % numberOfXPubs]}`)}
</button>
})}
<br />
{xpubType && (
<button className={style.nextButton} onClick={showNextXPub}>
{t(`accountInfo.xpubTypeChangeBtn.${xpubType}`)}
</button>
)}
</p>
) : null}
{ (config.bitcoinSimple?.scriptType === 'p2tr') ? (
Expand Down
28 changes: 14 additions & 14 deletions frontends/web/src/routes/account/send/feetargets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,21 @@ import { customFeeUnit, getCoinCode, isEthereumBased } from '@/routes/account/ut
import style from './feetargets.module.css';

type Props = {
accountCode: accountApi.AccountCode;
coinCode: accountApi.CoinCode;
disabled: boolean;
fiatUnit: accountApi.ConversionUnit;
proposedFee?: accountApi.IAmount;
customFee: string;
showCalculatingFeeLabel?: boolean;
onFeeTargetChange: (code: accountApi.FeeTargetCode) => void;
onCustomFee: (customFee: string) => void;
error?: string;
accountCode: accountApi.AccountCode;
coinCode: accountApi.CoinCode;
disabled: boolean;
fiatUnit: accountApi.ConversionUnit;
proposedFee?: accountApi.IAmount;
customFee: string;
showCalculatingFeeLabel?: boolean;
onFeeTargetChange: (code: accountApi.FeeTargetCode) => void;
onCustomFee: (customFee: string) => void;
error?: string;
}


type TOptions = {
value: accountApi.FeeTargetCode;
text: string;
value: accountApi.FeeTargetCode;
text: string;
}

export const FeeTargets = ({
Expand Down Expand Up @@ -113,7 +112,8 @@ export const FeeTargets = ({
return '';
}
const { amount, unit, conversions } = proposedFee;
return `${amount} ${unit} ${conversions ? ` = ${conversions[fiatUnit]} ${fiatUnit}` : ''}`;
const conversion = (conversions && conversions[fiatUnit]) ? ` = ${conversions[fiatUnit]} ${fiatUnit}` : '';
return `${amount} ${unit} ${conversion}`;
};

if (options === null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,8 @@ export const WCIncomingPairing = ({

onApprove();
} catch (e: any) {
console.error(`Wallet connect approve pairing error ${e}`);
console.error('Wallet connect approve pairing error', e);

console.error(e);
if (e.message.includes('Non conforming namespaces')) {
alertUser(t('walletConnect.invalidPairingChain',
{
Expand Down
8 changes: 5 additions & 3 deletions frontends/web/src/routes/device/bitbox01/restore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,11 @@ class Restore extends Component<Props, State> {
route('/', true);
}
} else {
alertUser(this.props.t(`backup.restore.error.e${code}`, {
defaultValue: errorMessage,
}));
if (typeof code === 'string') {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some un-typed BitBox01 backups specific api call.

We usually know what type when we use the src/api backend calls, but here we call apiPost directly.

alertUser(this.props.t(`backup.restore.error.e${code}`, {
defaultValue: errorMessage,
}));
}
}
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@ import { apiPost } from '../../../../../utils/request';
import { A } from '../../../../../components/anchor/anchor';
import style from '../../bitbox01.module.css';

interface PairingProps {
deviceID: string;
deviceLocked: boolean;
paired: boolean;
hasMobileChannel: boolean;
onPairingEnabled: () => void;
type PairingProps = {
deviceID: string;
deviceLocked: boolean;
paired: boolean;
hasMobileChannel: boolean;
onPairingEnabled: () => void;
}

type Props = PairingProps & TranslateProps;

interface State {
channel: string | null;
status: string | boolean;
showQRCode: boolean;
type State = {
channel: string | null;
status: string | false;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed boolean -> false as it is initialized with false.

showQRCode: boolean;
}

class MobilePairing extends Component<Props, State> {
Expand Down Expand Up @@ -206,14 +206,14 @@ class MobilePairing extends Component<Props, State> {
);
} else if (status === 'connectOnly') {
content = (<QRCode tapToCopy={false} data={JSON.stringify({ channel, connectOnly: true })} />);
} else {
} else if (status) {
content = (<p className="m-top-none">{t(`pairing.${status}.text`)}</p>);
}
return (
<div>
<SettingsButton
onClick={hasMobileChannel && !paired ? this.reconnectUnpaired : this.startPairing}
optionalText={t(`deviceSettings.pairing.status.${paired}`)}>
optionalText={t(`deviceSettings.pairing.status.${paired ? 'true' : 'false' }`)}>
{ deviceLocked ? (
hasMobileChannel ? t('pairing.reconnectOnly.button') : t('pairing.connectOnly.button')
) : (
Expand Down
2 changes: 1 addition & 1 deletion frontends/web/src/routes/device/bitbox01/setup/goal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Goal extends Component {
</div>
</div>
<div className="text-center m-top-large">
{getDarkmode() ? <SwissMadeOpenSourceDark large /> : <SwissMadeOpenSource large />}
{getDarkmode() ? <SwissMadeOpenSourceDark /> : <SwissMadeOpenSource />}
</div>
</div>
</div>
Expand Down
18 changes: 10 additions & 8 deletions frontends/web/src/routes/device/bitbox01/setup/initialize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,15 @@ class Initialize extends Component<Props, State> {
formSubmissionState = <Message type="info">{t('initialize.creating')}</Message>;
break;
case stateEnum.ERROR:
formSubmissionState = (
<Message type="error">
{t(`initialize.error.e${errorCode}`, {
defaultValue: errorMessage,
})}
</Message>
);
if (errorCode !== null) {
formSubmissionState = (
<Message type="error">
{t(`initialize.error.e${errorCode}`, {
defaultValue: errorMessage,
})}
</Message>
);
}
}

const content = showInfo ? (
Expand Down Expand Up @@ -170,7 +172,7 @@ class Initialize extends Component<Props, State> {
{formSubmissionState}
{content}
<div className="text-center m-top-large">
{getDarkmode() ? <SwissMadeOpenSourceDark large /> : <SwissMadeOpenSource large />}
{getDarkmode() ? <SwissMadeOpenSourceDark /> : <SwissMadeOpenSource />}
</div>
</div>
</div>
Expand Down
Loading