Skip to content

Commit

Permalink
feat(app): add Unlock dialog (FuelLabs#140)
Browse files Browse the repository at this point in the history
feat(app): add UnlockDialog component
  • Loading branch information
pedronauck authored Oct 14, 2022
1 parent 0b62d72 commit 63badce
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 28 deletions.
1 change: 1 addition & 0 deletions packages/app/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const WALLET_HEIGHT = 600;
export const TAB_BAR_HEIGHT = 30;
export const IS_CRX = VITE_CRX === 'true';
export const IS_LOGGED_KEY = 'fuel__isLogged';
export const IS_LOCKED_KEY = 'fuel__isLocked';
export const IS_DEVELOPMENT = process.env.NODE_ENV !== 'production';
export const IS_CRX_POPUP =
IS_CRX && globalThis.location.pathname === CRXPages.popup;
10 changes: 5 additions & 5 deletions packages/app/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { Navigate, Route, Routes } from 'react-router-dom';

import { IS_CRX, IS_CRX_POPUP } from './config';
import { CRXPrivateRoute, CRXPublicRoute } from './systems/CRX/components';
import { WalletCreatedPage } from './systems/SignUp/pages';

import { PrivateRoute, PublicRoute } from '~/systems/Core';
import { Pages } from '~/systems/Core/types';
import { homeRoutes } from '~/systems/Home/routes';
import { landingPageRoutes } from '~/systems/LandingPage/routes';
import { networkRoutes } from '~/systems/Network/routes';
import { signUpRoutes } from '~/systems/SignUp/routes';
import { homeRoutes } from '~/systems/Home';
import { landingPageRoutes } from '~/systems/LandingPage';
import { networkRoutes } from '~/systems/Network';
import { signUpRoutes } from '~/systems/SignUp';
import { WalletCreatedPage } from '~/systems/SignUp/pages';

const walletRoutes = (
<>
Expand Down
26 changes: 26 additions & 0 deletions packages/app/src/systems/Account/__mocks__/accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { Mnemonic } from '@fuel-ts/mnemonic';

import { AccountService } from '../services';

import { MNEMONIC_SIZE } from '~/config';
import { getWordsFromValue } from '~/systems/Core';

export const MOCK_ACCOUNTS = [
{
name: 'Account 1',
Expand All @@ -16,3 +23,22 @@ export const MOCK_ACCOUNTS = [
publicKey: '0x00',
},
];

export async function createMockAccount(password: string) {
const mnemonic = getWordsFromValue(Mnemonic.generate(MNEMONIC_SIZE));
const manager = await AccountService.createManager({
data: {
mnemonic,
password,
},
});
const firstAccount = manager.getAccounts()[0];
await AccountService.clearAccounts();
return AccountService.addAccount({
data: {
...MOCK_ACCOUNTS[0],
address: firstAccount.address.toString(),
publicKey: firstAccount.publicKey,
},
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Story } from '@storybook/react';

import { createMockAccount } from '../../__mocks__';

import { UnlockDialog } from './UnlockDialog';

export default {
component: UnlockDialog,
title: 'Account/Components/UnlockDialog',
parameters: {
layout: 'fullscreen',
},
};

export const Usage: Story<never> = () => {
return <UnlockDialog isOpen />;
};

Usage.loaders = [
async () => {
await createMockAccount('123123123');
return {};
},
];
Usage.parameters = {
layout: 'centered',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { cssObj } from '@fuel-ui/css';
import { Alert, Button, Dialog, Flex, Icon, Stack } from '@fuel-ui/react';

import type { UnlockFormValues } from '../../hooks';
import { useAccount, useUnlockForm } from '../../hooks';
import { UnlockForm } from '../UnlockForm';

export type UnlockDialogProps = {
isOpen?: boolean;
onOpenChange?: (open: boolean) => void;
};

export function UnlockDialog({ isOpen, onOpenChange }: UnlockDialogProps) {
const form = useUnlockForm();
const { handlers, isLoading } = useAccount();
const { formState } = form;

function onSubmit(values: UnlockFormValues) {
handlers.unlock(values.password);
}

return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<Dialog.Content css={styles.content}>
<Dialog.Heading>
<Flex css={{ alignItems: 'center' }}>
<Icon
color="gray8"
icon={Icon.is('LockKeyOpen')}
css={styles.headingIcon}
/>
Unlock Wallet
</Flex>
</Dialog.Heading>
<Dialog.Description>
<Stack gap="$3">
<Alert status="info" css={styles.alert}>
You need to unlock your wallet to be able to make transactions
and more-sensitive actions.
</Alert>
<UnlockForm form={form} />
</Stack>
</Dialog.Description>
<Dialog.Footer>
<Dialog.Close>
<Button
type="submit"
color="accent"
isDisabled={!formState.isValid}
isLoading={isLoading}
leftIcon={Icon.is('LockKeyOpen')}
css={styles.button}
>
Unlock
</Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</form>
</Dialog>
);
}

const styles = {
headingIcon: cssObj({
marginRight: '$3',
}),
alert: cssObj({
py: '$2',
pr: '$2',
background: '$gray2',
}),
button: cssObj({
width: '100%',
}),
content: cssObj({
maxWidth: 312,

/** This is temporary until have this option on @fuel-ui */
'button[aria-label="Close"]': {
display: 'none',
},
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './UnlockDialog';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { InputPassword, Stack } from '@fuel-ui/react';

import type { UseUnlockFormReturn } from '../../hooks/useUnlockForm';

import { ControlledField } from '~/systems/Core';

type UnlockFormProps = {
form: UseUnlockFormReturn;
};

export function UnlockForm({ form }: UnlockFormProps) {
const { control } = form;
return (
<Stack gap="$4">
<ControlledField
control={control}
name="password"
label="Password"
render={({ field }) => (
<InputPassword
{...field}
placeholder="Type your password"
aria-label="Your Password"
/>
)}
/>
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './UnlockForm';
2 changes: 2 additions & 0 deletions packages/app/src/systems/Account/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './AccountItem';
export * from './AccountList';
export * from './BalanceWidget';
export * from './UnlockDialog';
export * from './UnlockForm';
1 change: 1 addition & 0 deletions packages/app/src/systems/Account/hooks/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './useAccount';
export * from './useUnlockForm';
20 changes: 19 additions & 1 deletion packages/app/src/systems/Account/hooks/useAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,32 @@ const selectors = {
account: (state: AccountMachineState) => {
return state.context?.data;
},
isLocked: (state: AccountMachineState) => {
return !state.context?.wallet;
},
wallet: (state: AccountMachineState) => {
return state.context?.wallet;
},
};

export function useAccount() {
const service = store.useService(Services.account);
const isLoading = store.useSelector(Services.account, selectors.isLoading);
const account = store.useSelector(Services.account, selectors.account);
const isLocked = store.useSelector(Services.account, selectors.isLocked);
const wallet = store.useSelector(Services.account, selectors.wallet);

function unlock(password: string) {
service.send('UNLOCK_WALLET', { input: { password, account } });
}

return {
isLoading,
account,
wallet,
isLoading,
isLocked,
handlers: {
unlock,
},
};
}
42 changes: 42 additions & 0 deletions packages/app/src/systems/Account/hooks/useUnlockForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { useNavigate, useLocation } from 'react-router-dom';
import * as yup from 'yup';

import { store, Services } from '~/store';
import { Pages } from '~/systems/Core';

const schema = yup
.object({
password: yup.string().min(8).required('Password is required'),
})
.required();

export type UseUnlockFormReturn = ReturnType<typeof useUnlockForm>;

export type UnlockFormValues = {
password: string;
};

export function useUnlockForm() {
const navigate = useNavigate();
const location = useLocation();
const form = useForm<UnlockFormValues>({
resolver: yupResolver(schema),
reValidateMode: 'onChange',
mode: 'onChange',
defaultValues: {
password: '',
},
});

store.useSetMachineConfig(Services.account, {
actions: {
redirectToStatePath() {
navigate(location.state?.lastPage ?? Pages.wallet());
},
},
});

return form;
}
Loading

0 comments on commit 63badce

Please sign in to comment.