chore: template attachments

This commit is contained in:
Catalin Pit
2025-07-07 13:31:55 +03:00
parent 30b240cba2
commit e19da93ce2
19 changed files with 583 additions and 195 deletions

View File

@ -4,7 +4,6 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Trash } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import type { TDocument } from '@documenso/lib/types/document';
import { nanoid } from '@documenso/lib/universal/id';
import { AttachmentType } from '@documenso/prisma/generated/types';
import { trpc } from '@documenso/trpc/react';
@ -31,15 +30,15 @@ import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type AttachmentFormProps = {
document: TDocument;
documentId: number;
};
export const AttachmentForm = ({ document }: AttachmentFormProps) => {
export const AttachmentForm = ({ documentId }: AttachmentFormProps) => {
const { toast } = useToast();
const { data: attachmentsData, refetch: refetchAttachments } =
trpc.attachment.getAttachments.useQuery({
documentId: document.id,
trpc.attachment.getDocumentAttachments.useQuery({
documentId,
});
const { mutateAsync: setDocumentAttachments } =
@ -57,7 +56,7 @@ export const AttachmentForm = ({ document }: AttachmentFormProps) => {
const form = useForm<TSetDocumentAttachmentsSchema>({
resolver: zodResolver(ZSetDocumentAttachmentsSchema),
defaultValues: {
documentId: document.id,
documentId,
attachments: attachmentsData ?? defaultAttachments,
},
});
@ -85,7 +84,7 @@ export const AttachmentForm = ({ document }: AttachmentFormProps) => {
};
useEffect(() => {
if (attachmentsData) {
if (attachmentsData && attachmentsData.length > 0) {
form.setValue('attachments', attachmentsData);
}
}, [attachmentsData]);
@ -93,7 +92,7 @@ export const AttachmentForm = ({ document }: AttachmentFormProps) => {
const onSubmit = async (data: TSetDocumentAttachmentsSchema) => {
try {
await setDocumentAttachments({
documentId: document.id,
documentId,
attachments: data.attachments,
});
@ -118,9 +117,7 @@ export const AttachmentForm = ({ document }: AttachmentFormProps) => {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
Add Attachment
</Button>
<Button variant="outline">Add Attachment</Button>
</DialogTrigger>
<DialogContent position="center">
<DialogHeader>

View File

@ -0,0 +1,181 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trash } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { nanoid } from '@documenso/lib/universal/id';
import { AttachmentType } from '@documenso/prisma/generated/types';
import { trpc } from '@documenso/trpc/react';
import type { TSetTemplateAttachmentsSchema } from '@documenso/trpc/server/attachment-router/schema';
import { ZSetTemplateAttachmentsSchema } from '@documenso/trpc/server/attachment-router/schema';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type AttachmentFormProps = {
templateId: number;
};
export const AttachmentForm = ({ templateId }: AttachmentFormProps) => {
const { toast } = useToast();
const { data: attachmentsData, refetch: refetchAttachments } =
trpc.attachment.getTemplateAttachments.useQuery({
templateId,
});
const { mutateAsync: setTemplateAttachments } =
trpc.attachment.setTemplateAttachments.useMutation();
const defaultAttachments = [
{
id: nanoid(12),
label: '',
url: '',
type: AttachmentType.LINK,
},
];
const form = useForm<TSetTemplateAttachmentsSchema>({
resolver: zodResolver(ZSetTemplateAttachmentsSchema),
defaultValues: {
templateId,
attachments: attachmentsData ?? defaultAttachments,
},
});
const {
fields: attachments,
append: appendAttachment,
remove: removeAttachment,
} = useFieldArray({
control: form.control,
name: 'attachments',
});
const onAddAttachment = () => {
appendAttachment({
id: nanoid(12),
label: '',
url: '',
type: AttachmentType.LINK,
});
};
const onRemoveAttachment = (index: number) => {
removeAttachment(index);
};
useEffect(() => {
if (attachmentsData && attachmentsData.length > 0) {
form.setValue('attachments', attachmentsData);
}
}, [attachmentsData]);
const onSubmit = async (data: TSetTemplateAttachmentsSchema) => {
try {
await setTemplateAttachments({
templateId,
attachments: data.attachments,
});
toast({
title: 'Attachment(s) updated',
description: 'The attachment(s) have been updated successfully',
});
await refetchAttachments();
} catch (error) {
console.error(error);
toast({
title: 'Something went wrong',
description: 'We encountered an unknown error while attempting to create the attachments.',
variant: 'destructive',
duration: 5000,
});
}
};
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Add Attachment</Button>
</DialogTrigger>
<DialogContent position="center">
<DialogHeader>
<DialogTitle>Attachments</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
{attachments.map((attachment, index) => (
<div key={attachment.id} className="flex items-end gap-2">
<FormField
control={form.control}
name={`attachments.${index}.label`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel required>Label</FormLabel>
<FormControl>
<Input {...field} placeholder="Attachment label" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`attachments.${index}.url`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel required>URL</FormLabel>
<FormControl>
<Input {...field} placeholder="https://..." />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => onRemoveAttachment(index)}
>
<Trash className="h-4 w-4" />
</Button>
</div>
))}
</fieldset>
<DialogFooter className="mt-4">
<Button type="button" variant="outline" onClick={onAddAttachment}>
Add
</Button>
<Button type="submit">Save</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@ -11,7 +11,7 @@ import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { AttachmentForm } from '~/components/forms/attachment-form';
import { AttachmentForm } from '~/components/general/document/document-attachment-form';
import { DocumentEditForm } from '~/components/general/document/document-edit-form';
import { DocumentStatus } from '~/components/general/document/document-status';
import { LegacyFieldWarningPopover } from '~/components/general/legacy-field-warning-popover';
@ -138,11 +138,11 @@ export default function DocumentEditPage() {
{document.useLegacyFieldInsertion ? (
<div className="flex flex-col items-end gap-2 sm:flex-row sm:items-start">
<LegacyFieldWarningPopover type="document" documentId={document.id} />
<AttachmentForm document={document} />
<AttachmentForm documentId={document.id} />
</div>
) : (
<div>
<AttachmentForm document={document} />
<AttachmentForm documentId={document.id} />
</div>
)}
</div>

View File

@ -9,6 +9,7 @@ import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import { TemplateDirectLinkDialogWrapper } from '~/components/dialogs/template-direct-link-dialog-wrapper';
import { LegacyFieldWarningPopover } from '~/components/general/legacy-field-warning-popover';
import { AttachmentForm } from '~/components/general/template/template-attachment-form';
import { TemplateDirectLinkBadge } from '~/components/general/template/template-direct-link-badge';
import { TemplateEditForm } from '~/components/general/template/template-edit-form';
import { TemplateType } from '~/components/general/template/template-type';
@ -89,6 +90,7 @@ export default function TemplateEditPage() {
<div className="mt-2 flex items-center gap-2 sm:mt-0 sm:self-end">
<TemplateDirectLinkDialogWrapper template={template} />
<AttachmentForm templateId={template.id} />
{template.useLegacyFieldInsertion && (
<div>