mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
feat: prefill typed signature with user's full name (#2324)
Add fullName prop to signature pad components to automatically populate typed signature field with signer's name. Updates signature dialog, type component, and all signing forms across embed, document, template, and envelope flows to pass through the user's full name for better user experience.
This commit is contained in:
@@ -17,6 +17,7 @@ import { DocumentSigningDisclosure } from '../general/document-signing/document-
|
||||
|
||||
export type SignFieldSignatureDialogProps = {
|
||||
initialSignature?: string;
|
||||
fullName?: string;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
@@ -28,6 +29,7 @@ export const SignFieldSignatureDialog = createCallable<
|
||||
>(
|
||||
({
|
||||
call,
|
||||
fullName,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
@@ -46,6 +48,7 @@ export const SignFieldSignatureDialog = createCallable<
|
||||
</DialogHeader>
|
||||
|
||||
<SignaturePad
|
||||
fullName={fullName}
|
||||
value={localSignature ?? ''}
|
||||
onChange={({ value }) => setLocalSignature(value)}
|
||||
typedSignatureEnabled={typedSignatureEnabled}
|
||||
|
||||
@@ -438,6 +438,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
className="mt-2"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
disableAnimation
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
|
||||
@@ -455,6 +455,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
className="mt-2"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
disableAnimation
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
|
||||
@@ -319,6 +319,7 @@ export const MultiSignDocumentSigningView = ({
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
disableAnimation
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={
|
||||
|
||||
@@ -110,7 +110,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
||||
<Label htmlFor="email" className="text-muted-foreground">
|
||||
<Trans>Email</Trans>
|
||||
</Label>
|
||||
<Input id="email" type="email" className="bg-muted mt-2" value={user.email} disabled />
|
||||
<Input id="email" type="email" className="mt-2 bg-muted" value={user.email} disabled />
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
@@ -124,6 +124,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
||||
<FormControl>
|
||||
<SignaturePadDialog
|
||||
disabled={isSubmitting}
|
||||
fullName={user.name ?? ''}
|
||||
value={value}
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
/>
|
||||
|
||||
@@ -417,6 +417,7 @@ export const DirectTemplateSigningForm = ({
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(value) => setSignature(value)}
|
||||
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
|
||||
@@ -433,7 +434,7 @@ export const DirectTemplateSigningForm = ({
|
||||
|
||||
<div className="mt-4 flex gap-x-4">
|
||||
<Button
|
||||
className="dark:bg-muted dark:hover:bg-muted/80 w-full bg-black/5 hover:bg-black/10"
|
||||
className="w-full bg-black/5 hover:bg-black/10 dark:bg-muted dark:hover:bg-muted/80"
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
disabled={isSubmitting}
|
||||
|
||||
@@ -280,6 +280,7 @@ export const DocumentSigningForm = ({
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
|
||||
|
||||
+11
-7
@@ -56,8 +56,11 @@ export const DocumentSigningSignatureField = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [fontSize, setFontSize] = useState(2);
|
||||
|
||||
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
const {
|
||||
fullName,
|
||||
signature: providedSignature,
|
||||
setSignature: setProvidedSignature,
|
||||
} = useRequiredDocumentSigningContext();
|
||||
|
||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||
|
||||
@@ -236,13 +239,13 @@ export const DocumentSigningSignatureField = ({
|
||||
type="Signature"
|
||||
>
|
||||
{isLoading && (
|
||||
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
|
||||
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-background">
|
||||
<Loader className="h-5 w-5 animate-spin text-primary md:h-8 md:w-8" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state === 'empty' && (
|
||||
<p className="group-hover:text-primary font-signature text-muted-foreground group-hover:text-recipient-green text-[clamp(0.575rem,25cqw,1.2rem)] text-xl duration-200">
|
||||
<p className="font-signature text-[clamp(0.575rem,25cqw,1.2rem)] text-xl text-muted-foreground duration-200 group-hover:text-primary group-hover:text-recipient-green">
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
)}
|
||||
@@ -259,7 +262,7 @@ export const DocumentSigningSignatureField = ({
|
||||
<div ref={containerRef} className="flex h-full w-full items-center justify-center p-2">
|
||||
<p
|
||||
ref={signatureRef}
|
||||
className="font-signature text-muted-foreground w-full overflow-hidden break-all text-center leading-tight duration-200"
|
||||
className="w-full overflow-hidden break-all text-center font-signature leading-tight text-muted-foreground duration-200"
|
||||
style={{ fontSize: `${fontSize}rem` }}
|
||||
>
|
||||
{signature?.typedSignature}
|
||||
@@ -272,12 +275,13 @@ export const DocumentSigningSignatureField = ({
|
||||
<DialogTitle>
|
||||
<Trans>
|
||||
Sign as {recipient.name}{' '}
|
||||
<div className="text-muted-foreground h-5">({recipient.email})</div>
|
||||
<div className="h-5 text-muted-foreground">({recipient.email})</div>
|
||||
</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<SignaturePad
|
||||
className="mt-2"
|
||||
fullName={fullName}
|
||||
value={localSignature ?? ''}
|
||||
onChange={({ value }) => setLocalSignature(value)}
|
||||
typedSignatureEnabled={typedSignatureEnabled}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function EnvelopeSignerForm() {
|
||||
|
||||
if (recipient.role === RecipientRole.ASSISTANT) {
|
||||
return (
|
||||
<fieldset className="embed--DocumentWidgetForm dark:bg-background border-border rounded-2xl sm:border sm:p-3">
|
||||
<fieldset className="embed--DocumentWidgetForm rounded-2xl border-border sm:border sm:p-3 dark:bg-background">
|
||||
<RadioGroup
|
||||
className="gap-0 space-y-2 shadow-none sm:space-y-3"
|
||||
value={selectedAssistantRecipient?.id?.toString()}
|
||||
@@ -54,7 +54,7 @@ export default function EnvelopeSignerForm() {
|
||||
.map((r) => (
|
||||
<div
|
||||
key={r.id}
|
||||
className="bg-widget border-border relative flex flex-col gap-4 rounded-lg border p-4"
|
||||
className="relative flex flex-col gap-4 rounded-lg border border-border bg-widget p-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -69,15 +69,15 @@ export default function EnvelopeSignerForm() {
|
||||
{r.name}
|
||||
|
||||
{r.id === recipient.id && (
|
||||
<span className="text-muted-foreground ml-2">
|
||||
<span className="ml-2 text-muted-foreground">
|
||||
<Trans>(You)</Trans>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<p className="text-muted-foreground text-xs">{r.email}</p>
|
||||
<p className="text-xs text-muted-foreground">{r.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-muted-foreground text-xs leading-[inherit]">
|
||||
<div className="text-xs leading-[inherit] text-muted-foreground">
|
||||
<Plural
|
||||
value={assistantFields.filter((field) => field.recipientId === r.id).length}
|
||||
one="# field"
|
||||
@@ -103,7 +103,7 @@ export default function EnvelopeSignerForm() {
|
||||
<Input
|
||||
type="text"
|
||||
id="full-name"
|
||||
className="bg-background mt-2"
|
||||
className="mt-2 bg-background"
|
||||
value={fullName}
|
||||
disabled={isNameLocked}
|
||||
onChange={(e) => !isNameLocked && setFullName(e.target.value.trimStart())}
|
||||
@@ -119,6 +119,7 @@ export default function EnvelopeSignerForm() {
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={envelope.documentMeta.typedSignatureEnabled}
|
||||
|
||||
@@ -374,6 +374,7 @@ export default function EnvelopeSignerPageRenderer() {
|
||||
.with({ type: FieldType.SIGNATURE }, (field) => {
|
||||
handleSignatureFieldClick({
|
||||
field,
|
||||
fullName,
|
||||
signature,
|
||||
typedSignatureEnabled: envelope.documentMeta.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: envelope.documentMeta.uploadSignatureEnabled,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SignFieldSignatureDialog } from '~/components/dialogs/sign-field-signat
|
||||
|
||||
type HandleSignatureFieldClickOptions = {
|
||||
field: TFieldSignature;
|
||||
fullName?: string;
|
||||
signature: string | null;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
@@ -17,8 +18,14 @@ type HandleSignatureFieldClickOptions = {
|
||||
export const handleSignatureFieldClick = async (
|
||||
options: HandleSignatureFieldClickOptions,
|
||||
): Promise<Extract<TSignEnvelopeFieldValue, { type: typeof FieldType.SIGNATURE }> | null> => {
|
||||
const { field, signature, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled } =
|
||||
options;
|
||||
const {
|
||||
field,
|
||||
fullName,
|
||||
signature,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
} = options;
|
||||
|
||||
if (field.type !== FieldType.SIGNATURE) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
@@ -37,6 +44,7 @@ export const handleSignatureFieldClick = async (
|
||||
|
||||
if (!signatureToInsert) {
|
||||
signatureToInsert = await SignFieldSignatureDialog.call({
|
||||
fullName,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SignatureRender } from './signature-render';
|
||||
|
||||
export type SignaturePadDialogProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
|
||||
disabled?: boolean;
|
||||
fullName?: string;
|
||||
value?: string;
|
||||
onChange: (_value: string) => void;
|
||||
dialogConfirmText?: MessageDescriptor | string;
|
||||
@@ -26,6 +27,7 @@ export type SignaturePadDialogProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'o
|
||||
|
||||
export const SignaturePadDialog = ({
|
||||
className,
|
||||
fullName,
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
@@ -43,7 +45,7 @@ export const SignaturePadDialog = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'aspect-signature-pad bg-background relative block w-full select-none rounded-lg border',
|
||||
'relative block aspect-signature-pad w-full select-none rounded-lg border bg-background',
|
||||
className,
|
||||
{
|
||||
'pointer-events-none opacity-50': disabled,
|
||||
@@ -112,6 +114,7 @@ export const SignaturePadDialog = ({
|
||||
<DialogContent hideClose={true} className="p-6 pt-4">
|
||||
<SignaturePad
|
||||
id="signature"
|
||||
fullName={fullName}
|
||||
value={value}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
@@ -7,13 +7,27 @@ import { cn } from '../../lib/utils';
|
||||
export type SignaturePadTypeProps = {
|
||||
className?: string;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
onChange: (_value: string) => void;
|
||||
};
|
||||
|
||||
export const SignaturePadType = ({ className, value, onChange }: SignaturePadTypeProps) => {
|
||||
export const SignaturePadType = ({
|
||||
className,
|
||||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
}: SignaturePadTypeProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const $isDirty = useRef(false);
|
||||
// Colors don't actually work for text.
|
||||
const [selectedColor, setSelectedColor] = useState('black');
|
||||
|
||||
useEffect(() => {
|
||||
if (!$isDirty.current && !value && defaultValue) {
|
||||
$isDirty.current = true;
|
||||
onChange(defaultValue);
|
||||
}
|
||||
}, [defaultValue, value, onChange]);
|
||||
|
||||
return (
|
||||
<div className={cn('flex h-full w-full items-center justify-center', className)}>
|
||||
@@ -23,7 +37,10 @@ export const SignaturePadType = ({ className, value, onChange }: SignaturePadTyp
|
||||
className="w-full bg-transparent px-4 text-center font-signature text-7xl text-black placeholder:text-4xl focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-white"
|
||||
// style={{ color: selectedColor }}
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.target.value.trimStart())}
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value.trimStart());
|
||||
$isDirty.current = true;
|
||||
}}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
|
||||
@@ -21,6 +21,7 @@ export type SignaturePadValue = {
|
||||
};
|
||||
|
||||
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
|
||||
fullName?: string;
|
||||
value?: string;
|
||||
onChange?: (_value: SignaturePadValue) => void;
|
||||
|
||||
@@ -34,6 +35,7 @@ export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChang
|
||||
};
|
||||
|
||||
export const SignaturePad = ({
|
||||
fullName,
|
||||
value = '',
|
||||
onChange,
|
||||
disabled = false,
|
||||
@@ -168,7 +170,7 @@ export const SignaturePad = ({
|
||||
|
||||
<TabsContent
|
||||
value="draw"
|
||||
className="border-border aspect-signature-pad dark:bg-background relative flex items-center justify-center rounded-md border bg-neutral-50 text-center"
|
||||
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
|
||||
>
|
||||
<SignaturePadDraw
|
||||
className="h-full w-full"
|
||||
@@ -179,15 +181,19 @@ export const SignaturePad = ({
|
||||
|
||||
<TabsContent
|
||||
value="text"
|
||||
className="border-border aspect-signature-pad dark:bg-background relative flex items-center justify-center rounded-md border bg-neutral-50 text-center"
|
||||
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
|
||||
>
|
||||
<SignaturePadType value={typedSignature} onChange={onTypedSignatureChange} />
|
||||
<SignaturePadType
|
||||
value={typedSignature}
|
||||
defaultValue={fullName}
|
||||
onChange={onTypedSignatureChange}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="image"
|
||||
className={cn(
|
||||
'border-border aspect-signature-pad dark:bg-background relative rounded-md border bg-neutral-50',
|
||||
'relative aspect-signature-pad rounded-md border border-border bg-neutral-50 dark:bg-background',
|
||||
{
|
||||
'bg-white': imageSignature,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user