Files
pezkuwi-extension/packages/extension-ui/src/Popup/Export.tsx
T
Satoshi Qazi Muhammed 9d769676e4 Move to @pezkuwi/dev 0.86 (eslint 10 toolchain)
- @pezkuwi/dev, dev-test, dev-ts ^0.86.3 in the root and in every
  workspace package (they still asked for 0.84.3)
- tsconfig.base target ES2022, the @pezkuwi/dev default; it carries
  the ErrorOptions type for `cause`
- rethrown errors carry the original as `cause`; rejections are
  normalised to Error
- page/messaging: the typed subscriber is called directly, not as
  `Function`
- chrome mock: constant results through Promise.resolve
- useLedger: drop a construction every branch below repeated
- InputWithLabel: onKeyDown instead of the deprecated onKeyPress
- Account: toggleActions + 1 instead of a pre-increment of a parameter
- optional chains where a guard tested the same value
- specs: mount<React.Component> so the wrappers are not `any`-typed
- AccountsStore.set keeps its async override of KeyringStore.set:
  BaseStore.set handles its own failure, so it never rejects
- extension-chains keeps its ChainProperties assertion: the package's
  own build does not load the type augmentation
- the existing React Compiler findings (37, react-hooks rules only)
  are recorded in eslint-suppressions.json; a new one fails lint
2026-10-09 16:32:46 +04:00

136 lines
3.5 KiB
TypeScript

// Copyright 2019-2026 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { RouteComponentProps } from 'react-router';
import fileSaver from 'file-saver';
import React, { useCallback, useContext, useState } from 'react';
import { withRouter } from 'react-router';
import { ActionBar, ActionContext, ActionText, Address, Button, InputWithLabel, Warning } from '../components/index.js';
import { useTranslation } from '../hooks/index.js';
import { exportAccount } from '../messaging.js';
import { Header } from '../partials/index.js';
import { styled } from '../styled.js';
const MIN_LENGTH = 6;
interface Props extends RouteComponentProps<{ address: string }> {
className?: string;
}
function Export ({ className, match: { params: { address } } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const onAction = useContext(ActionContext);
const [isBusy, setIsBusy] = useState(false);
const [pass, setPass] = useState('');
const [error, setError] = useState('');
const _goHome = useCallback(
() => onAction('/'),
[onAction]
);
const onPassChange = useCallback(
(password: string) => {
setPass(password);
setError('');
},
[]);
const _onExportButtonClick = useCallback(
(): void => {
setIsBusy(true);
exportAccount(address, pass)
.then(({ exportedJson }) => {
const blob = new Blob([JSON.stringify(exportedJson)], { type: 'application/json; charset=utf-8' });
// eslint-disable-next-line @typescript-eslint/no-deprecated
fileSaver.saveAs(blob, `${address}.json`);
onAction('/');
})
.catch((error: Error) => {
console.error(error);
setError(error.message);
setIsBusy(false);
});
},
[address, onAction, pass]
);
return (
<>
<Header
showBackArrow
text={t('Export account')}
/>
<div className={className}>
<Address address={address}>
<Warning className='movedWarning'>
{t("You are exporting your account. Keep it safe and don't share it with anyone.")}
</Warning>
<div className='actionArea'>
<InputWithLabel
data-export-password
disabled={isBusy}
isError={pass.length < MIN_LENGTH || !!error}
label={t('password for this account')}
onChange={onPassChange}
type='password'
/>
{error && (
<Warning
isBelowInput
isDanger
>
{error}
</Warning>
)}
<Button
className='export-button'
data-export-button
isBusy={isBusy}
isDanger
isDisabled={pass.length === 0 || !!error}
onClick={_onExportButtonClick}
>
{t('I want to export this account')}
</Button>
<ActionBar className='withMarginTop'>
<ActionText
className='center'
onClick={_goHome}
text={t('Cancel')}
/>
</ActionBar>
</div>
</Address>
</div>
</>
);
}
export default withRouter(styled(Export)`
.actionArea {
padding: 10px 24px;
}
.center {
margin: auto;
}
.export-button {
margin-top: 6px;
}
.movedWarning {
margin-top: 8px;
}
.withMarginTop {
margin-top: 4px;
}
`);