forked from MystenLabs/sui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[wallet-ext] build new "Protect Account" UI screen and add some form …
…primitives (MystenLabs#13160) ## Description This PR adds the new "Protect Account" screen used when clicking "Create a new account" button and as the second step of the "Import Passphrase" and "Import Private Key" flows. As part of this, I attempted to create some new form primitives to make building forms more manageable (all of the existing components aren't quite up to spec, use Formik, and aren't fully accessible). I ended up following an approach similar to https://www.brendonovich.dev/blog/the-ultimate-form-abstraction which gave some creds to @Jordan-Mysten 😆 As a rough outline, we have generic, non-form-library-specific input controls like `TextArea`, `Input`, `PasswordInput`, `Checkbox` which are used to create `react-hook-form` specific controls such as `TextField`, `TextAreaField`, `CheckboxField`, and so forth. We also have some helper components like `Form` and `FormField` to help abstract away some specific form details such as error states when using react-hook-form. I considered using the Radix form primitives, but I didn't really see the immediate value 🤷🏼 Additional note #1: Some of these pages are used in different flows and have different submission logic depending on the usage context. I think I might need to brainstorm on the best way to handle that and tackle it in a follow-up PR Additional note #2: the auto-lock input is still a WIP on the design side, so I have a TODO to add that once it's ready. Checkbox in Figma - https://www.figma.com/file/T06obgYVOUD2JDGXM8QEDX?node-id=341%3A378&main-component=1&fuid=1209977329759347633 Input in Figma - https://www.figma.com/file/T06obgYVOUD2JDGXM8QEDX/01-Components-%3A-Shared?node-id=19%3A312&mode=dev <img width="631" alt="image" src="https://github.com/MystenLabs/sui/assets/7453188/4c851808-b751-412a-b25e-06d4660b5fa3"> ## Test Plan - Manual testing (error states, successful submission, accessibility, focus/disabled/hover states, etc.) - CI --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [ ] protocol change - [ ] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes
- Loading branch information
1 parent
57b9a8e
commit ffb4258
Showing
18 changed files
with
427 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
apps/wallet/src/ui/app/components/accounts/ProtectAccountForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { yupResolver } from '@hookform/resolvers/yup'; | ||
import { useForm, type SubmitHandler } from 'react-hook-form'; | ||
import { useNavigate } from 'react-router-dom'; | ||
import * as Yup from 'yup'; | ||
import { CheckboxField } from '../../shared/forms/CheckboxField'; | ||
import { Form } from '../../shared/forms/Form'; | ||
import { TextField } from '../../shared/forms/TextField'; | ||
import ExternalLink from '../external-link'; | ||
import { Button } from '_app/shared/ButtonUI'; | ||
import { ToS_LINK } from '_src/shared/constants'; | ||
|
||
const formSchema = Yup.object({ | ||
password: Yup.string().required('Required'), | ||
confirmedPassword: Yup.string().required('Required'), | ||
acceptedTos: Yup.boolean().required().oneOf([true]), | ||
enabledAutolock: Yup.boolean(), | ||
}); | ||
|
||
type FormValues = Yup.InferType<typeof formSchema>; | ||
|
||
type ProtectAccountFormProps = { | ||
submitButtonText: string; | ||
cancelButtonText: string; | ||
onSubmit: SubmitHandler<FormValues>; | ||
}; | ||
|
||
export function ProtectAccountForm({ | ||
submitButtonText, | ||
cancelButtonText, | ||
onSubmit, | ||
}: ProtectAccountFormProps) { | ||
const form = useForm({ | ||
mode: 'all', | ||
defaultValues: { | ||
password: '', | ||
confirmedPassword: '', | ||
acceptedTos: false, | ||
enabledAutolock: true, | ||
}, | ||
resolver: yupResolver(formSchema), | ||
}); | ||
const { | ||
register, | ||
formState: { isSubmitting, isValid }, | ||
} = form; | ||
const navigate = useNavigate(); | ||
|
||
return ( | ||
<Form className="flex flex-col gap-6 h-full" form={form} onSubmit={onSubmit}> | ||
<TextField type="password" label="Create Account Password" {...register('password')} /> | ||
<TextField | ||
type="password" | ||
label="Confirm Account Password" | ||
{...register('confirmedPassword')} | ||
/> | ||
<div className="flex flex-col gap-4"> | ||
<CheckboxField name="enabledAutolock" label="Auto-lock after I am inactive for" /> | ||
{/* TODO: Abhi is working on designs for the auto-lock input, we'll add this when it's ready */} | ||
</div> | ||
<div className="flex flex-col gap-5 mt-auto"> | ||
<CheckboxField | ||
name="acceptedTos" | ||
label={ | ||
<> | ||
I read and agreed to the{' '} | ||
<ExternalLink href={ToS_LINK} className="text-[#1F6493] no-underline"> | ||
Terms of Services | ||
</ExternalLink> | ||
</> | ||
} | ||
/> | ||
<div className="flex gap-2.5"> | ||
<Button | ||
variant="outline" | ||
size="tall" | ||
text={cancelButtonText} | ||
onClick={() => navigate(-1)} | ||
/> | ||
<Button | ||
type="submit" | ||
disabled={isSubmitting || !isValid} | ||
variant="primary" | ||
size="tall" | ||
loading={isSubmitting} | ||
text={submitButtonText} | ||
/> | ||
</div> | ||
</div> | ||
</Form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 0 additions & 6 deletions
6
apps/wallet/src/ui/app/pages/accounts/CreateNewAccountPage.tsx
This file was deleted.
Oops, something went wrong.
34 changes: 34 additions & 0 deletions
34
apps/wallet/src/ui/app/pages/accounts/ProtectAccountPage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { ProtectAccountForm } from '../../components/accounts/ProtectAccountForm'; | ||
import { Heading } from '../../shared/heading'; | ||
import { Text } from '_app/shared/text'; | ||
|
||
export function ProtectAccountPage() { | ||
return ( | ||
<div className="rounded-20 bg-sui-lightest shadow-wallet-content flex flex-col items-center px-6 py-10 h-full"> | ||
<Text variant="caption" color="steel-dark" weight="semibold"> | ||
Wallet Setup | ||
</Text> | ||
<div className="text-center mt-2.5"> | ||
<Heading variant="heading1" color="gray-90" as="h1" weight="bold"> | ||
Protect Account with a Password Lock | ||
</Heading> | ||
</div> | ||
<div className="mt-6 w-full grow"> | ||
<ProtectAccountForm | ||
cancelButtonText="Back" | ||
submitButtonText="Create Wallet" | ||
onSubmit={(formValues) => { | ||
// eslint-disable-next-line no-console | ||
console.log( | ||
'TODO: Do something when the user submits the form successfully', | ||
formValues, | ||
); | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { type ComponentProps, type ReactNode, forwardRef } from 'react'; | ||
import { Controller, useFormContext } from 'react-hook-form'; | ||
import { Checkbox } from './controls/Checkbox'; | ||
|
||
type CheckboxFieldProps = { | ||
name: string; | ||
label: ReactNode; | ||
} & Omit<ComponentProps<'button'>, 'ref'>; | ||
|
||
export const CheckboxField = forwardRef<HTMLButtonElement, CheckboxFieldProps>( | ||
({ label, name, ...props }, forwardedRef) => { | ||
const { control } = useFormContext(); | ||
return ( | ||
<Controller | ||
control={control} | ||
name={name} | ||
render={({ field: { onChange, name, value } }) => ( | ||
<Checkbox | ||
label={label} | ||
onCheckedChange={onChange} | ||
name={name} | ||
checked={value} | ||
ref={forwardedRef} | ||
{...props} | ||
/> | ||
)} | ||
/> | ||
); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { type ComponentProps } from 'react'; | ||
import { | ||
type FieldValues, | ||
FormProvider, | ||
type SubmitHandler, | ||
type UseFormReturn, | ||
} from 'react-hook-form'; | ||
|
||
type FormProps<T extends FieldValues> = Omit<ComponentProps<'form'>, 'onSubmit'> & { | ||
form: UseFormReturn<T>; | ||
onSubmit: SubmitHandler<T>; | ||
}; | ||
|
||
export function Form<T extends FieldValues>({ form, onSubmit, children, ...props }: FormProps<T>) { | ||
return ( | ||
<FormProvider {...form}> | ||
<form onSubmit={form.handleSubmit(onSubmit)} {...props}> | ||
{children} | ||
</form> | ||
</FormProvider> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import { type ReactNode } from 'react'; | ||
import { useFormContext } from 'react-hook-form'; | ||
import { FormLabel } from './FormLabel'; | ||
import Alert from '../../components/alert'; | ||
|
||
type FormFieldProps = { | ||
name: string; | ||
label: ReactNode; | ||
children: ReactNode; | ||
}; | ||
|
||
export function FormField({ children, name, label }: FormFieldProps) { | ||
const { getFieldState, formState } = useFormContext(); | ||
const state = getFieldState(name, formState); | ||
|
||
return ( | ||
<div className="flex flex-col gap-2.5"> | ||
<FormLabel label={label}>{children}</FormLabel> | ||
{state.error && <Alert>{state.error.message}</Alert>} | ||
</div> | ||
); | ||
} | ||
|
||
export default FormField; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { Text } from '_app/shared/text'; | ||
|
||
import type { ReactNode } from 'react'; | ||
|
||
export type FormLabelProps = { | ||
label: ReactNode; | ||
children: ReactNode; | ||
}; | ||
|
||
export function FormLabel({ label, children }: FormLabelProps) { | ||
return ( | ||
<label className="flex flex-col flex-nowrap gap-2.5"> | ||
<div className="pl-2.5"> | ||
<Text variant="body" color="steel-darker" weight="semibold"> | ||
{label} | ||
</Text> | ||
</div> | ||
{children} | ||
</label> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { type ComponentProps, type ReactNode, forwardRef } from 'react'; | ||
import FormField from './FormField'; | ||
import { TextArea } from './controls/TextArea'; | ||
|
||
type TextAreaFieldProps = { | ||
name: string; | ||
label: ReactNode; | ||
} & ComponentProps<'textarea'>; | ||
|
||
export const TextAreaField = forwardRef<HTMLTextAreaElement, TextAreaFieldProps>( | ||
({ label, ...props }, forwardedRef) => ( | ||
<FormField name={props.name} label={label}> | ||
<TextArea {...props} ref={forwardedRef} /> | ||
</FormField> | ||
), | ||
); |
Oops, something went wrong.