diff --git a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.scss b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.scss new file mode 100644 index 00000000..8b72c633 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.scss @@ -0,0 +1,7 @@ +.change-passcode-x { + .divider { + display: block; + margin: rem(16px) 0 rem(24px) 0; + border-bottom: 1px solid rgba($color-white, 0.1); + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.tsx b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.tsx new file mode 100644 index 00000000..5ba298e4 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.tsx @@ -0,0 +1,109 @@ +import React, { useRef } from 'react'; +import Page from '@popup/popupX/shared/Page'; +import { useTranslation } from 'react-i18next'; +import Button from '@popup/popupX/shared/Button'; +import FormPassword from '@popup/popupX/shared/Form/Password'; +import Form, { useForm } from '@popup/popupX/shared/Form/Form'; +import { useAtom, useSetAtom } from 'jotai'; +import { encryptedSeedPhraseAtom, sessionPasscodeAtom } from '@popup/store/settings'; +import { addToastAtom } from '@popup/state'; +import { SubmitHandler, UseFormGetValues, Validate } from 'react-hook-form'; +import { decrypt, encrypt } from '@shared/utils/crypto'; + +type FormValues = { + currentPasscode: string; + newPasscode: string; + newPasscodeRepeated: string; +}; + +export default function ChangePasscode() { + const { t } = useTranslation('x', { keyPrefix: 'passcode' }); + + const formRef = useRef(null); + const form = useForm(); + const [encryptedSeedPhrase, setEncryptedSeedPhrase] = useAtom(encryptedSeedPhraseAtom); + const [passcode, setPasscode] = useAtom(sessionPasscodeAtom); + const addToast = useSetAtom(addToastAtom); + + if (passcode.loading || !passcode.value || encryptedSeedPhrase.loading) { + return null; + } + + const handleSubmit: SubmitHandler = async (vs) => { + if (encryptedSeedPhrase.value && passcode.value) { + const decryptedSeedPhrase = await decrypt(encryptedSeedPhrase.value, passcode.value); + const encryptedSeedPhraseWithNewPasscode = await encrypt(decryptedSeedPhrase, vs.newPasscode); + + setEncryptedSeedPhrase(encryptedSeedPhraseWithNewPasscode); + setPasscode(vs.newPasscode); + addToast(t('passcodeUpdated')); + } + }; + + function validateCurrentPasscode(): Validate { + return (currentPasscode) => (currentPasscode !== passcode.value ? t('incorrectPasscode') : undefined); + } + + function validateNewPasscode(getValues: UseFormGetValues): Validate { + return (newPasscodeRepeated) => + getValues().newPasscode !== newPasscodeRepeated ? t('form.passcodeMismatch') : undefined; + } + + return ( + + + +
+ {(f) => { + return ( + <> + + + + + + ); + }} + +
+ + + +
+ ); +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/i18n/en.ts new file mode 100644 index 00000000..bdc29011 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/i18n/en.ts @@ -0,0 +1,17 @@ +const t = { + changePasscode: 'Change passcode', + incorrectPasscode: 'Incorrect passcode', + passcodeUpdated: 'Successfully updated passcode', + labels: { + currentPasscode: 'Enter current passcode', + newPasscode: 'Enter new passcode', + newPasscodeRepeated: 'Confirm new passcode', + }, + form: { + passcodeRequired: 'A passcode must be entered', + passcodeMismatch: 'Passcode does not match', + passcodeMinLength: 'Passcode must be at least 6 characters', + }, +}; + +export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/index.ts b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/index.ts new file mode 100644 index 00000000..338bd076 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/index.ts @@ -0,0 +1 @@ +export { default } from './ChangePasscode'; diff --git a/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx b/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx index 6664183f..e1a511ce 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx +++ b/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx @@ -5,7 +5,7 @@ import Text from '@popup/popupX/shared/Text'; export type ButtonProps = Pick< ButtonHTMLAttributes, - 'type' | 'children' | 'disabled' | 'className' | 'onClick' | 'onMouseUp' | 'tabIndex' + 'type' | 'children' | 'disabled' | 'className' | 'onClick' | 'onMouseUp' | 'tabIndex' | 'form' >; type PolymorphicProps = PolymorphicComponentProps; diff --git a/packages/browser-wallet/src/popup/popupX/shared/Form/Form.scss b/packages/browser-wallet/src/popup/popupX/shared/Form/Form.scss index 8aef77e5..ed557a00 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/Form/Form.scss +++ b/packages/browser-wallet/src/popup/popupX/shared/Form/Form.scss @@ -42,13 +42,13 @@ @include when-valid { .form-input__field:where(:focus) { - border-color: green; /* ToDo get color */ + border-color: $color-green-success; } } @include when-invalid { .form-input__field { - border-color: red; /* ToDo get color */ + border-color: $color-red-attention; } } diff --git a/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx b/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx index 14353192..06579342 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx +++ b/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx @@ -9,7 +9,7 @@ import { useForm as useFormLib, FormProvider, } from 'react-hook-form'; -import { ClassName } from 'wallet-common-helpers'; +import { ClassName, Id } from 'wallet-common-helpers'; const useFormDefaults: Pick = { mode: 'onTouched', @@ -23,24 +23,25 @@ export const useForm = ( props?: UseFormProps ): UseFormReturn => useFormLib({ ...useFormDefaults, ...props }); -type FormProps = ClassName & { - /** - * Submit handler, receiving the values of the form as arg. - */ - onSubmit: SubmitHandler; - /** - * Optional form methods from 'react-hook-form' useForm, if form methods need to be accessed outside of the form. - */ - formMethods?: UseFormReturn; - /** - * Default values of the entire form. - */ - defaultValues?: DefaultValues; - /** - * Function passing form methods to function body. Should be used to register form components (using register/control) to the form context. - */ - children: (methods: UseFormReturn) => React.ReactNode; -}; +type FormProps = Id & + ClassName & { + /** + * Submit handler, receiving the values of the form as arg. + */ + onSubmit: SubmitHandler; + /** + * Optional form methods from 'react-hook-form' useForm, if form methods need to be accessed outside of the form. + */ + formMethods?: UseFormReturn; + /** + * Default values of the entire form. + */ + defaultValues?: DefaultValues; + /** + * Function passing form methods to function body. Should be used to register form components (using register/control) to the form context. + */ + children: (methods: UseFormReturn) => React.ReactNode; + }; /** * @description @@ -68,7 +69,7 @@ type FormProps = ClassName & { const Form = forwardRef( // eslint-disable-next-line @typescript-eslint/no-explicit-any >( - { onSubmit, formMethods: external, defaultValues, children, className }: FormProps, + { onSubmit, formMethods: external, defaultValues, children, className, id }: FormProps, ref: Ref ) => { const internal = useForm({ defaultValues }); @@ -76,7 +77,7 @@ const Form = forwardRef( return ( -
+ {children(methods)}
diff --git a/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx b/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx index bd9e1279..df76d099 100644 --- a/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx +++ b/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx @@ -11,6 +11,7 @@ import { TokenDetails, TokenDetailsCcd } from '@popup/popupX/pages/TokenDetails' import IdCards from '@popup/popupX/pages/IdCards'; import Accounts from '@popup/popupX/pages/Accounts'; import SeedPhrase from 'src/popup/popupX/pages/SeedPhrase'; +import ChangePasscode from 'src/popup/popupX/pages/ChangePasscode'; import { Web3IdCredentials, Web3IdImport } from '@popup/popupX/pages/Web3Id'; import NetworkSettings from '@popup/popupX/pages/NetworkSettings'; import ConnectNetwork from '@popup/popupX/pages/ConnectNetwork'; @@ -74,6 +75,7 @@ export default function Routes() { } path={relativeRoutes.settings.accounts.privateKey.path} /> } path={relativeRoutes.settings.seedPhrase.path} /> + } path={relativeRoutes.settings.passcode.path} /> } /> } path={relativeRoutes.settings.web3Id.import.path} /> diff --git a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss index 2ce9e952..7f014c02 100644 --- a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss +++ b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss @@ -9,6 +9,7 @@ @import '../pages/ConnectNetwork/ConnectNetwork'; @import '../pages/About/About'; @import '../pages/SeedPhrase/SeedPhrase'; +@import '../pages/ChangePasscode/ChangePasscode'; @import '../pages/TransactionLog/TransactionLog'; @import '../pages/Web3Id/Web3Id'; @import '../pages/MainPage/MainPage'; diff --git a/packages/browser-wallet/src/popup/shell/Root.tsx b/packages/browser-wallet/src/popup/shell/Root.tsx index f6063949..b2fe7aba 100644 --- a/packages/browser-wallet/src/popup/shell/Root.tsx +++ b/packages/browser-wallet/src/popup/shell/Root.tsx @@ -106,7 +106,7 @@ export default function Root() { return ( - + diff --git a/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts b/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts index 3aa74d74..87e9512c 100644 --- a/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts +++ b/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts @@ -39,6 +39,7 @@ import restore from '@popup/popupX/pages/Restore/i18n/en'; import connectedSites from '@popup/popupX/pages/ConnectedSites/i18n/en'; import privateKey from '@popup/popupX/pages/PrivateKey/i18n/en'; import seedPhrase from '@popup/popupX/pages/SeedPhrase/i18n/en'; +import passcode from '@popup/popupX/pages/ChangePasscode/i18n/en'; import network from '@popup/popupX/pages/NetworkSettings/i18n/en'; import connect from '@popup/popupX/pages/ConnectNetwork/i18n/en'; import sharedX from '@popup/popupX/shared/i18n/en'; @@ -88,6 +89,7 @@ const t = { connectedSites, privateKey, seedPhrase, + passcode, network, connect, sharedX, diff --git a/packages/browser-wallet/src/wallet-common-helpers/utils/types.ts b/packages/browser-wallet/src/wallet-common-helpers/utils/types.ts index 7dd1b85d..d5596858 100644 --- a/packages/browser-wallet/src/wallet-common-helpers/utils/types.ts +++ b/packages/browser-wallet/src/wallet-common-helpers/utils/types.ts @@ -27,6 +27,7 @@ export type MakeRequired = NotOptional> & Omit< */ export type EqualRecord = { [P in keyof T]: P }; +export type Id = Pick, 'id'>; export type ClassName = Pick, 'className'>; export type Style = Pick, 'style'>;