Skip to content

Commit

Permalink
Merge branch 'main' into contact-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
secondl1ght authored Jul 4, 2024
2 parents c999374 + 91efac4 commit aec948f
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 16 deletions.
103 changes: 91 additions & 12 deletions src/components/SignUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import { ApolloError, useApolloClient } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Loader2 } from 'lucide-react';
import { generateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { Copy, CopyCheck, Eye, EyeOff, Loader2 } from 'lucide-react';
import Link from 'next/link';
import { useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useCopyToClipboard } from 'usehooks-ts';
import { z } from 'zod';

import { Button } from '@/components/ui/button';
Expand Down Expand Up @@ -34,6 +37,7 @@ import {
import { ROUTES } from '@/utils/routes';
import { WorkerMessage, WorkerResponse } from '@/workers/account/types';

import { Badge } from './ui/badge';
import {
Card,
CardContent,
Expand All @@ -56,6 +60,7 @@ const FormSchema = z
confirm_password: z.string(),
password_hint: z.string().optional(),
accept_tos_and_pp: z.boolean(),
accept_condition_1: z.boolean(),
})
.refine(data => data.password === data.confirm_password, {
message: "Passwords don't match.",
Expand All @@ -64,6 +69,10 @@ const FormSchema = z
.refine(data => !!data.accept_tos_and_pp, {
message: 'You must accept to sign up.',
path: ['accept_tos_and_pp'],
})
.refine(data => !!data.accept_condition_1, {
message: 'You must accept to sign up.',
path: ['accept_condition_1'],
});

export function SignUpForm() {
Expand All @@ -74,6 +83,10 @@ export function SignUpForm() {
const workerRef = useRef<Worker>();

const [loading, setLoading] = useState(true);
const [clickedGenerate, setClickedGenerate] = useState(false);
const [showPassword, setShowPassword] = useState(false);

const [copiedPassword, copyPassword] = useCopyToClipboard();

const form = useForm<z.infer<typeof FormSchema>>({
reValidateMode: 'onChange',
Expand All @@ -84,6 +97,7 @@ export function SignUpForm() {
password_hint: '',
confirm_password: '',
accept_tos_and_pp: false,
accept_condition_1: false,
},
});

Expand Down Expand Up @@ -178,6 +192,13 @@ export function SignUpForm() {
};
}, [client, toast]);

const handleGenerateClick = () => {
const mnemonic = generateMnemonic(wordlist);
form.setValue('password', mnemonic);
form.setValue('confirm_password', mnemonic);
setClickedGenerate(true);
};

return (
<Card>
<CardHeader>
Expand Down Expand Up @@ -208,20 +229,57 @@ export function SignUpForm() {
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Master Password</FormLabel>
<div className="flex w-full items-center justify-between">
<FormLabel>Master Password</FormLabel>
<button type="button" onClick={handleGenerateClick}>
<Badge variant={'secondary'}>
Generate Strong Password
</Badge>
</button>
</div>
<FormControl>
<Input
placeholder="super secret password"
type="password"
{...field}
/>
<div className="flex gap-2">
<Input
placeholder="super secret password"
type={showPassword ? undefined : 'password'}
{...field}
/>
<Button
type="button"
onClick={() => setShowPassword(p => !p)}
size={'icon'}
className="px-2"
variant={'outline'}
>
{showPassword ? (
<EyeOff className="size-4" />
) : (
<Eye className="size-4" />
)}
</Button>
{clickedGenerate ? (
<Button
type="button"
onClick={() => copyPassword(password)}
size={'icon'}
className="px-2"
variant={'outline'}
>
{copiedPassword ? (
<CopyCheck color="green" className="size-4" />
) : (
<Copy className="size-4" />
)}
</Button>
) : null}
</div>
</FormControl>
<FormMessage />
<Progress value={strength?.progress || 0} />
<FormDescription>
<strong>Important: </strong>
Your master password cannot be recovered if you forget it!
Minimum length is {MIN_PASSWORD_LENGTH}.
Your account cannot be recovered if you forget it! Minimum
length is {MIN_PASSWORD_LENGTH} characters.
</FormDescription>
</FormItem>
)}
Expand Down Expand Up @@ -261,8 +319,7 @@ export function SignUpForm() {
<FormMessage />
<FormDescription>
<strong>Important: </strong>
The password hint will be stored in clear text on the
server.
The password hint will be stored in clear text.
</FormDescription>
</FormItem>
)}
Expand All @@ -272,7 +329,7 @@ export function SignUpForm() {
control={form.control}
name="accept_tos_and_pp"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 py-4">
<FormItem className="flex flex-row items-start space-x-3 space-y-0 pt-4">
<FormControl>
<Checkbox
checked={field.value}
Expand All @@ -285,6 +342,28 @@ export function SignUpForm() {
the Privacy Policy.
</FormLabel>

<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="accept_condition_1"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 pb-4">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="flex flex-col gap-2">
<FormLabel>
I understand that if I forget the password
<strong> my account cannot be recovered.</strong>
</FormLabel>

<FormMessage />
</div>
</FormItem>
Expand Down
13 changes: 12 additions & 1 deletion src/components/button/VaultButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useCheckPasswordMutation } from '@/graphql/mutations/__generated__/chec
import { useUserQuery } from '@/graphql/queries/__generated__/user.generated';
import { cn } from '@/lib/utils';
import { useKeyStore } from '@/stores/keys';
import { handleApolloError } from '@/utils/error';
import { MIN_PASSWORD_LENGTH } from '@/utils/password';
import { WorkerMessage, WorkerResponse } from '@/workers/account/types';

Expand All @@ -33,6 +34,7 @@ import {
FormMessage,
} from '../ui/form';
import { Input } from '../ui/input';
import { useToast } from '../ui/use-toast';

const formSchema = z.object({
password: z.string().min(MIN_PASSWORD_LENGTH, {
Expand All @@ -50,6 +52,8 @@ const UnlockDialogContent: FC<{ callback: () => void }> = ({ callback }) => {
},
});

const { toast } = useToast();

const setMasterKey = useKeyStore(s => s.setMasterKey);

const [loading, setLoading] = useState(true);
Expand All @@ -67,7 +71,14 @@ const UnlockDialogContent: FC<{ callback: () => void }> = ({ callback }) => {
}
},
onError: error => {
console.log(error);
const messages = handleApolloError(error);

toast({
variant: 'destructive',
title: 'Unable to unlock.',
description: messages.join(', '),
});

setTempMasterKey('');
form.reset();
},
Expand Down
6 changes: 6 additions & 0 deletions src/components/button/WalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@/components/ui/popover';
import { useGetAllWalletsQuery } from '@/graphql/queries/__generated__/wallet.generated';
import { cn } from '@/lib/utils';
import { useChat, useContactStore } from '@/stores/contacts';
import { LOCALSTORAGE_KEYS } from '@/utils/constants';
import { ROUTES } from '@/utils/routes';

Expand All @@ -29,6 +30,9 @@ import { RefreshWallet } from './RefreshWallet';
export function WalletButton() {
const [open, setOpen] = useState(false);

const setCurrentContact = useContactStore(s => s.setCurrentContact);
const setCurrentPaymentOption = useChat(s => s.setCurrentPaymentOption);

const { push } = useRouter();

const { data, error } = useGetAllWalletsQuery();
Expand Down Expand Up @@ -92,6 +96,8 @@ export function WalletButton() {
setValue(currentValue);
push(ROUTES.app.home);
setOpen(false);
setCurrentContact(undefined);
setCurrentPaymentOption(undefined);
}}
>
{w.label}
Expand Down
10 changes: 9 additions & 1 deletion src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const AppLayout: FC<{ children: ReactNode }> = ({ children }) => {
<div className="flex items-center justify-center gap-4">
<h1 className="text-xl font-black">BANCO</h1>

<Badge variant={'destructive'}>
<Badge variant={'destructive'} className="hidden md:block">
Alpha - Limit funds and use at your own risk.
</Badge>
</div>
Expand Down Expand Up @@ -168,6 +168,14 @@ export const AppLayout: FC<{ children: ReactNode }> = ({ children }) => {
</SheetContent>
</Sheet>
</header>

<Badge
variant={'destructive'}
className="mx-4 mt-2 block text-center md:hidden"
>
Alpha - Limit funds and use at your own risk.
</Badge>

<main className="flex flex-col justify-center px-4">{children}</main>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/stores/contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ type Contact = {

type ContactState = {
currentContact: Contact | undefined;
setCurrentContact: (contact: Contact) => void;
setCurrentContact: (contact: Contact | undefined) => void;
};

export const useContactStore = create<ContactState>()(set => ({
currentContact: undefined,
setCurrentContact: (contact: Contact) => set({ currentContact: contact }),
setCurrentContact: contact => set({ currentContact: contact }),
}));

export type PaymentOption = {
Expand Down
2 changes: 2 additions & 0 deletions src/utils/password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const evaluatePasswordStrength = (password: string) => {
// Check password length
if (password.length < MIN_PASSWORD_LENGTH) score = 0;

if (password.split(' ').length > 6) score = 4;

switch (score) {
case 0:
return { title: 'Weak', progress: 0 };
Expand Down

0 comments on commit aec948f

Please sign in to comment.