-
Notifications
You must be signed in to change notification settings - Fork 855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for uploading signature #905
base: main
Are you sure you want to change the base?
Changes from all commits
e341b1e
7abd156
6241500
1638534
c62da64
194371b
d8fd912
9426558
1ca4c25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation'; | |
import { Loader } from 'lucide-react'; | ||
|
||
import type { Recipient } from '@documenso/prisma/client'; | ||
import { SignatureType } from '@documenso/prisma/client'; | ||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; | ||
import { trpc } from '@documenso/trpc/react'; | ||
import { Button } from '@documenso/ui/primitives/button'; | ||
|
@@ -29,8 +30,11 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { | |
const router = useRouter(); | ||
|
||
const { toast } = useToast(); | ||
const { signature: providedSignature, setSignature: setProvidedSignature } = | ||
useRequiredSigningContext(); | ||
const { | ||
signature: providedSignature, | ||
setSignature: setProvidedSignature, | ||
setSignatureType: setProvidedSignatureType, | ||
} = useRequiredSigningContext(); | ||
|
||
const [isPending, startTransition] = useTransition(); | ||
|
||
|
@@ -47,7 +51,10 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { | |
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending; | ||
|
||
const [showSignatureModal, setShowSignatureModal] = useState(false); | ||
const [localSignature, setLocalSignature] = useState<string | null>(null); | ||
const [localSignature, setLocalSignature] = useState<{ | ||
value: string | null; | ||
type: SignatureType | null; | ||
} | null>(); | ||
const [isLocalSignatureSet, setIsLocalSignatureSet] = useState(false); | ||
|
||
const state = useMemo<SignatureFieldState>(() => { | ||
|
@@ -70,13 +77,16 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { | |
|
||
const onSign = async (source: 'local' | 'provider' = 'provider') => { | ||
try { | ||
if (!providedSignature && !localSignature) { | ||
if (!providedSignature && !localSignature?.value) { | ||
setIsLocalSignatureSet(false); | ||
setShowSignatureModal(true); | ||
return; | ||
} | ||
|
||
const value = source === 'local' && localSignature ? localSignature : providedSignature ?? ''; | ||
const value = | ||
source === 'local' && localSignature?.value | ||
? localSignature.value | ||
: providedSignature ?? ''; | ||
|
||
if (!value) { | ||
return; | ||
|
@@ -90,7 +100,8 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { | |
}); | ||
|
||
if (source === 'local' && !providedSignature) { | ||
setProvidedSignature(localSignature); | ||
setProvidedSignature(localSignature?.value ?? ''); | ||
setProvidedSignatureType(localSignature?.type ?? 'DRAW'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
} | ||
|
||
setLocalSignature(null); | ||
|
@@ -167,8 +178,17 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { | |
|
||
<SignaturePad | ||
id="signature" | ||
signature={{ | ||
value: localSignature?.value ?? '', | ||
type: localSignature?.type ?? SignatureType.DRAW, | ||
}} | ||
className="border-border mt-2 h-44 w-full rounded-md border" | ||
onChange={(value) => setLocalSignature(value)} | ||
onChange={(value: string | null, isUploaded: boolean) => { | ||
setLocalSignature({ | ||
value, | ||
type: isUploaded ? SignatureType.UPLOAD : SignatureType.DRAW, | ||
}); | ||
}} | ||
Comment on lines
+186
to
+191
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for complex onChange function, it's better them to define them as function and use them in JSX |
||
/> | ||
</div> | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
|
||
import { useRouter } from 'next/navigation'; | ||
|
||
import { zodResolver } from '@hookform/resolvers/zod'; | ||
|
@@ -21,12 +23,12 @@ import { | |
} from '@documenso/ui/primitives/form/form'; | ||
import { Input } from '@documenso/ui/primitives/input'; | ||
import { Label } from '@documenso/ui/primitives/label'; | ||
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; | ||
import { SignaturePad } from '@documenso/ui/primitives/signature-pad/signature-pad'; | ||
import { useToast } from '@documenso/ui/primitives/use-toast'; | ||
|
||
export const ZProfileFormSchema = z.object({ | ||
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }), | ||
signature: z.string().min(1, 'Signature Pad cannot be empty'), | ||
signature: z.string(), | ||
}); | ||
|
||
export type TProfileFormSchema = z.infer<typeof ZProfileFormSchema>; | ||
|
@@ -40,6 +42,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { | |
const router = useRouter(); | ||
|
||
const { toast } = useToast(); | ||
const [isUploaded, setIsUploaded] = useState(false); | ||
|
||
const form = useForm<TProfileFormSchema>({ | ||
values: { | ||
|
@@ -55,9 +58,17 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { | |
|
||
const onFormSubmit = async ({ name, signature }: TProfileFormSchema) => { | ||
try { | ||
if (signature === '') { | ||
form.setError('signature', { | ||
type: 'manual', | ||
message: 'Signature Pad cannot be empty', | ||
}); | ||
return; | ||
} | ||
await updateProfile({ | ||
name, | ||
signature, | ||
signatureType: isUploaded ? 'UPLOAD' : 'DRAW', | ||
}); | ||
|
||
toast({ | ||
|
@@ -85,6 +96,11 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { | |
} | ||
}; | ||
|
||
const handleSignatureChange = (signature: string | null, isUploaded: boolean) => { | ||
setIsUploaded(isUploaded); | ||
form.setValue('signature', signature ?? ''); | ||
}; | ||
|
||
return ( | ||
<Form {...form}> | ||
<form | ||
|
@@ -115,16 +131,21 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { | |
<FormField | ||
control={form.control} | ||
name="signature" | ||
render={({ field: { onChange } }) => ( | ||
render={() => ( | ||
<FormItem> | ||
<FormLabel>Signature</FormLabel> | ||
<FormControl> | ||
<SignaturePad | ||
className="h-44 w-full" | ||
disabled={isSubmitting} | ||
containerClassName={cn('rounded-lg border bg-background')} | ||
defaultValue={user.signature ?? undefined} | ||
onChange={(v) => onChange(v ?? '')} | ||
signature={{ | ||
value: user.signature, | ||
type: user.signatureType, | ||
}} | ||
onChange={(v: string | null, isUploaded: boolean) => | ||
handleSignatureChange(v, isUploaded) | ||
} | ||
Comment on lines
+146
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can simply write onChange={handleSignatureChange} |
||
/> | ||
</FormControl> | ||
<FormMessage /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import { useRouter } from 'next/navigation'; | ||
|
||
import { zodResolver } from '@hookform/resolvers/zod'; | ||
|
@@ -9,6 +10,7 @@ import { FcGoogle } from 'react-icons/fc'; | |
import { z } from 'zod'; | ||
|
||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; | ||
import { SignatureType } from '@documenso/prisma/client'; | ||
import { TRPCClientError } from '@documenso/trpc/client'; | ||
import { trpc } from '@documenso/trpc/react'; | ||
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema'; | ||
|
@@ -24,7 +26,7 @@ import { | |
} from '@documenso/ui/primitives/form/form'; | ||
import { Input } from '@documenso/ui/primitives/input'; | ||
import { PasswordInput } from '@documenso/ui/primitives/password-input'; | ||
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; | ||
import { SignaturePad } from '@documenso/ui/primitives/signature-pad/signature-pad'; | ||
import { useToast } from '@documenso/ui/primitives/use-toast'; | ||
|
||
const SIGN_UP_REDIRECT_PATH = '/documents'; | ||
|
@@ -35,6 +37,7 @@ export const ZSignUpFormSchema = z | |
email: z.string().email().min(1), | ||
password: ZPasswordSchema, | ||
signature: z.string().min(1, { message: 'We need your signature to sign documents' }), | ||
signatureType: z.nativeEnum(SignatureType), | ||
}) | ||
.refine( | ||
(data) => { | ||
|
@@ -57,6 +60,7 @@ export type SignUpFormProps = { | |
export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: SignUpFormProps) => { | ||
const { toast } = useToast(); | ||
const analytics = useAnalytics(); | ||
const [isUploaded, setIsUploaded] = useState(false); | ||
const router = useRouter(); | ||
|
||
const form = useForm<TSignUpFormSchema>({ | ||
|
@@ -65,6 +69,7 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign | |
email: initialEmail ?? '', | ||
password: '', | ||
signature: '', | ||
signatureType: 'DRAW', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replace ''DRAW' with |
||
}, | ||
resolver: zodResolver(ZSignUpFormSchema), | ||
}); | ||
|
@@ -75,7 +80,13 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign | |
|
||
const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => { | ||
try { | ||
await signup({ name, email, password, signature }); | ||
await signup({ | ||
name, | ||
email, | ||
password, | ||
signature, | ||
signatureType: isUploaded ? SignatureType.UPLOAD : SignatureType.DRAW, | ||
}); | ||
|
||
router.push(`/unverified-account`); | ||
|
||
|
@@ -121,6 +132,11 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign | |
} | ||
}; | ||
|
||
const handleSignatureChange = (signature: string | null, isUploaded: boolean) => { | ||
setIsUploaded(isUploaded); | ||
form.setValue('signature', signature ?? ''); | ||
}; | ||
|
||
return ( | ||
<Form {...form}> | ||
<form | ||
|
@@ -181,7 +197,13 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign | |
className="h-36 w-full" | ||
disabled={isSubmitting} | ||
containerClassName="mt-2 rounded-lg border bg-background" | ||
onChange={(v) => onChange(v ?? '')} | ||
signature={{ | ||
value: form.watch('signature'), | ||
type: isUploaded ? 'UPLOAD' : 'DRAW', | ||
}} | ||
onChange={(v: string | null, isUploaded: boolean) => | ||
handleSignatureChange(v, isUploaded) | ||
} | ||
Comment on lines
+204
to
+206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can simply write onChange={handleSignatureChange} |
||
/> | ||
</FormControl> | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isolating useState Type to respective type.ts file is recommended/