mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 16:51:38 +10:00
282 lines
8.5 KiB
TypeScript
282 lines
8.5 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { Loader } from 'lucide-react';
|
|
import { useForm } from 'react-hook-form';
|
|
|
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
|
import type { Field, Recipient } from '@documenso/prisma/client';
|
|
import { DocumentStatus } from '@documenso/prisma/client';
|
|
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
|
import { trpc } from '@documenso/trpc/react';
|
|
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
|
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
|
|
|
import { Button } from '../button';
|
|
import { FormErrorMessage } from '../form/form-error-message';
|
|
import { Input } from '../input';
|
|
import { Label } from '../label';
|
|
import { useStep } from '../stepper';
|
|
import { Textarea } from '../textarea';
|
|
import { type TAddSubjectFormSchema, ZAddSubjectFormSchema } from './add-subject.types';
|
|
import {
|
|
DocumentFlowFormContainerActions,
|
|
DocumentFlowFormContainerContent,
|
|
DocumentFlowFormContainerFooter,
|
|
DocumentFlowFormContainerHeader,
|
|
DocumentFlowFormContainerStep,
|
|
} from './document-flow-root';
|
|
import { ShowFieldItem } from './show-field-item';
|
|
import type { DocumentFlowStep } from './types';
|
|
|
|
export type AddSubjectFormProps = {
|
|
documentFlow: DocumentFlowStep;
|
|
recipients: Recipient[];
|
|
fields: Field[];
|
|
document: DocumentWithData;
|
|
onSubmit: (_data: TAddSubjectFormSchema) => void;
|
|
isDocumentPdfLoaded: boolean;
|
|
};
|
|
|
|
export const AddSubjectFormPartial = ({
|
|
documentFlow,
|
|
recipients: recipients,
|
|
fields: fields,
|
|
document,
|
|
onSubmit,
|
|
isDocumentPdfLoaded,
|
|
}: AddSubjectFormProps) => {
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
setValue,
|
|
formState: { errors, isSubmitting },
|
|
} = useForm<TAddSubjectFormSchema>({
|
|
defaultValues: {
|
|
meta: {
|
|
subject: document.documentMeta?.subject ?? '',
|
|
message: document.documentMeta?.message ?? '',
|
|
},
|
|
},
|
|
resolver: zodResolver(ZAddSubjectFormSchema),
|
|
});
|
|
|
|
const [oldEmailData, setOldEmailData] = useState<{ subject: string; message: string } | null>(
|
|
document.documentMeta
|
|
? {
|
|
subject: document.documentMeta.subject ?? '',
|
|
message: document.documentMeta.message ?? '',
|
|
}
|
|
: null,
|
|
);
|
|
|
|
const [changedFields, setChangedFields] = useState<{ subject: boolean; message: boolean }>({
|
|
subject: false,
|
|
message: false,
|
|
});
|
|
|
|
const { toast } = useToast();
|
|
const router = useRouter();
|
|
const utils = trpc.useUtils();
|
|
|
|
const { mutateAsync: setEmailSettingsForDocument } =
|
|
trpc.document.setDocumentEmailSettings.useMutation({
|
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
|
onSuccess: () => {
|
|
const data = utils.document.getDocumentWithDetailsById.getData({
|
|
id: document.id,
|
|
});
|
|
|
|
setOldEmailData({
|
|
subject: data?.documentMeta?.subject ?? '',
|
|
message: data?.documentMeta?.message ?? '',
|
|
});
|
|
},
|
|
});
|
|
|
|
const handleOnBlur = async (field: 'subject' | 'message', value: string) => {
|
|
if (value == oldEmailData?.[field]) return;
|
|
|
|
try {
|
|
await setEmailSettingsForDocument({
|
|
documentId: document.id,
|
|
[field]: value,
|
|
});
|
|
|
|
setChangedFields((prev) => ({ ...prev, [field]: true }));
|
|
router.refresh();
|
|
|
|
toast({
|
|
title: 'Email settings updated',
|
|
description: 'The email settings for the document have been updated.',
|
|
duration: 5000,
|
|
});
|
|
} catch (e) {
|
|
console.error(e);
|
|
|
|
toast({
|
|
title: 'Error',
|
|
description: 'An error occurred while updating the email settings.',
|
|
duration: 5000,
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleUndoButton = async (field: 'subject' | 'message') => {
|
|
if (!oldEmailData) return;
|
|
|
|
try {
|
|
await setEmailSettingsForDocument({
|
|
documentId: document.id,
|
|
[field]: oldEmailData[field],
|
|
});
|
|
|
|
setValue(`meta.${field}`, oldEmailData[field]);
|
|
|
|
setOldEmailData((prev) => (prev ? { ...prev, [field]: undefined } : null));
|
|
setChangedFields((prev) => ({ ...prev, [field]: false }));
|
|
router.refresh();
|
|
|
|
toast({
|
|
title: 'Change reverted',
|
|
description: `The latest change to the ${field} has been reverted.`,
|
|
duration: 5000,
|
|
});
|
|
|
|
if (oldEmailData.subject === undefined && oldEmailData.message === undefined) {
|
|
setOldEmailData(null);
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
toast({
|
|
title: 'Error',
|
|
description: `An error occurred while undoing the latest change to the ${field}.`,
|
|
duration: 5000,
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const timeout = setTimeout(() => {
|
|
setChangedFields({ subject: false, message: false });
|
|
}, 5000);
|
|
|
|
return () => {
|
|
clearTimeout(timeout);
|
|
};
|
|
}, [oldEmailData]);
|
|
|
|
const onFormSubmit = handleSubmit(onSubmit);
|
|
const { currentStep, totalSteps, previousStep } = useStep();
|
|
|
|
return (
|
|
<>
|
|
<DocumentFlowFormContainerHeader
|
|
title={documentFlow.title}
|
|
description={documentFlow.description}
|
|
/>
|
|
|
|
<DocumentFlowFormContainerContent>
|
|
<div className="flex flex-col">
|
|
{isDocumentPdfLoaded &&
|
|
fields.map((field, index) => (
|
|
<ShowFieldItem key={index} field={field} recipients={recipients} />
|
|
))}
|
|
|
|
<div className="flex flex-col gap-y-4">
|
|
<div>
|
|
<Label htmlFor="subject">
|
|
Subject <span className="text-muted-foreground">(Optional)</span>
|
|
</Label>
|
|
|
|
<Input
|
|
id="subject"
|
|
className="bg-background mt-2"
|
|
disabled={isSubmitting}
|
|
{...register('meta.subject', {
|
|
onBlur: (event) => {
|
|
void handleOnBlur('subject', event.target.value);
|
|
},
|
|
})}
|
|
/>
|
|
|
|
{changedFields.subject && oldEmailData && (
|
|
<div className="mt-2 flex items-center">
|
|
<Button
|
|
className="-ml-2"
|
|
size="sm"
|
|
variant="link"
|
|
onClick={() => void handleUndoButton('subject')}
|
|
>
|
|
<span className="text-xs">Undo change</span>
|
|
<Loader className="ml-1 h-4 w-4 animate-spin text-gray-500" />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
<FormErrorMessage className="mt-2" error={errors.meta?.subject} />
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="message">
|
|
Message <span className="text-muted-foreground">(Optional)</span>
|
|
</Label>
|
|
|
|
<Textarea
|
|
id="message"
|
|
className="bg-background mt-2 h-32 resize-none"
|
|
disabled={isSubmitting}
|
|
{...register('meta.message', {
|
|
onBlur: (event) => {
|
|
void handleOnBlur('message', event.target.value);
|
|
},
|
|
})}
|
|
/>
|
|
|
|
{changedFields.message && oldEmailData && (
|
|
<div className="mt-2 flex items-center">
|
|
<Button
|
|
className="-ml-2"
|
|
size="sm"
|
|
variant="link"
|
|
onClick={() => void handleUndoButton('message')}
|
|
>
|
|
<span className="text-xs">Undo change</span>
|
|
<Loader className="ml-1 h-4 w-4 animate-spin text-gray-500" />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
<FormErrorMessage
|
|
className="mt-2"
|
|
error={typeof errors.meta?.message !== 'string' ? errors.meta?.message : undefined}
|
|
/>
|
|
</div>
|
|
<DocumentSendEmailMessageHelper />
|
|
</div>
|
|
</div>
|
|
</DocumentFlowFormContainerContent>
|
|
|
|
<DocumentFlowFormContainerFooter>
|
|
<DocumentFlowFormContainerStep
|
|
title={documentFlow.title}
|
|
step={currentStep}
|
|
maxStep={totalSteps}
|
|
/>
|
|
|
|
<DocumentFlowFormContainerActions
|
|
loading={isSubmitting}
|
|
disabled={isSubmitting}
|
|
goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'}
|
|
onGoBackClick={previousStep}
|
|
onGoNextClick={() => void onFormSubmit()}
|
|
/>
|
|
</DocumentFlowFormContainerFooter>
|
|
</>
|
|
);
|
|
};
|