diff --git a/apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx b/apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx index d77fdd552..fc347d189 100644 --- a/apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx +++ b/apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx @@ -1,16 +1,17 @@ -/* eslint-disable unused-imports/no-unused-imports */ - -/* eslint-disable @typescript-eslint/consistent-type-imports */ - -/* eslint-disable unused-imports/no-unused-vars */ 'use client'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; -import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc'; -import type { DocumentData, Field, Recipient, Template, User } from '@documenso/prisma/client'; +import type { + DocumentData, + Field, + Recipient, + Template, + TemplateDocumentMeta, + User, +} from '@documenso/prisma/client'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Card, CardContent } from '@documenso/ui/primitives/card'; @@ -26,13 +27,9 @@ import type { TAddTemplateFieldsFormSchema } from '@documenso/ui/primitives/temp import { AddTemplatePlaceholderRecipientsFormPartial } from '@documenso/ui/primitives/template-flow/add-template-placeholder-recipients'; import type { TAddTemplatePlacholderRecipientsFormSchema } from '@documenso/ui/primitives/template-flow/add-template-placeholder-recipients.types'; import { AddTemplateSettingsFormPartial } from '@documenso/ui/primitives/template-flow/add-template-settings'; -import { TTemplateSettingsFormSchema } from '@documenso/ui/primitives/template-flow/add-template-settings.types'; +import type { TTemplateSettingsFormSchema } from '@documenso/ui/primitives/template-flow/add-template-settings.types'; import { useToast } from '@documenso/ui/primitives/use-toast'; -/* eslint-disable unused-imports/no-unused-imports */ -/* eslint-disable @typescript-eslint/consistent-type-imports */ -/* eslint-disable unused-imports/no-unused-vars */ - export type EditTemplateFormProps = { className?: string; user: User; @@ -40,6 +37,7 @@ export type EditTemplateFormProps = { recipients: Recipient[]; fields: Field[]; documentData: DocumentData; + documentMeta: TemplateDocumentMeta | null; templateRootPath: string; }; @@ -54,16 +52,17 @@ export const EditTemplateForm = ({ user: _user, documentData, templateRootPath, + documentMeta, }: EditTemplateFormProps) => { const { toast } = useToast(); const router = useRouter(); - const [step, setStep] = useState('signers'); + const [step, setStep] = useState('settings'); const documentFlow: Record = { settings: { - title: 'General', - description: 'Configure general settings for the document.', + title: 'Settings', + description: 'Configure general settings for the template.', stepIndex: 1, }, signers: { @@ -82,6 +81,8 @@ export const EditTemplateForm = ({ const { mutateAsync: addTemplateFields } = trpc.field.addTemplateFields.useMutation(); const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation(); + const { mutateAsync: setSettingsForTemplate } = + trpc.template.setSettingsForTemplate.useMutation(); const onAddTemplatePlaceholderFormSubmit = async ( data: TAddTemplatePlacholderRecipientsFormSchema, @@ -105,32 +106,28 @@ export const EditTemplateForm = ({ }; const onAddTemplateSettingsFormSubmit = async (data: TTemplateSettingsFormSchema) => { - // try { - // const { timezone, dateFormat, redirectUrl } = data.meta; - // await setSettingsForDocument({ - // documentId: document.id, - // data: { - // title: data.title, - // globalAccessAuth: data.globalAccessAuth ?? null, - // globalActionAuth: data.globalActionAuth ?? null, - // }, - // meta: { - // timezone, - // dateFormat, - // redirectUrl, - // }, - // }); - // // Router refresh is here to clear the router cache for when navigating to /documents. - // router.refresh(); - // setStep('signers'); - // } catch (err) { - // console.error(err); - // toast({ - // title: 'Error', - // description: 'An error occurred while updating the document settings.', - // variant: 'destructive', - // }); - // } + try { + const { subject, message, timezone, dateFormat, redirectUrl } = data.meta; + await setSettingsForTemplate({ + templateId: template.id, + meta: { + subject, + message, + timezone, + dateFormat, + redirectUrl, + }, + }); + router.refresh(); + setStep('signers'); + } catch (err) { + console.error(err); + toast({ + title: 'Error', + description: 'An error occurred while updating the template settings.', + variant: 'destructive', + }); + } }; const onAddFieldsFormSubmit = async (data: TAddTemplateFieldsFormSchema) => { @@ -182,11 +179,12 @@ export const EditTemplateForm = ({ setCurrentStep={(step) => setStep(EditTemplateSteps[step - 1])} > diff --git a/packages/lib/server-only/template-document-meta/upsert-template-document-meta.ts b/packages/lib/server-only/template-document-meta/upsert-template-document-meta.ts index 7128a4220..7430d86a8 100644 --- a/packages/lib/server-only/template-document-meta/upsert-template-document-meta.ts +++ b/packages/lib/server-only/template-document-meta/upsert-template-document-meta.ts @@ -21,6 +21,14 @@ export const upsertTemplateDocumentMeta = async ({ password, redirectUrl, }: CreateTemplateDocumentMetaOptions) => { + const templateDocumentMeta = await prisma.templateDocumentMeta.findFirstOrThrow({ + where: { + templateId: templateId, + }, + include: { + template: true, + }, + }); return await prisma.$transaction(async (tx) => { const upsertedTemplateDocumentMeta = await tx.templateDocumentMeta.upsert({ where: { diff --git a/packages/trpc/server/template-router/router.ts b/packages/trpc/server/template-router/router.ts index 4ed567b2b..f1b181af9 100644 --- a/packages/trpc/server/template-router/router.ts +++ b/packages/trpc/server/template-router/router.ts @@ -1,6 +1,7 @@ import { TRPCError } from '@trpc/server'; import { getServerLimits } from '@documenso/ee/server-only/limits/server'; +import { upsertTemplateDocumentMeta } from '@documenso/lib/server-only/template-document-meta/upsert-template-document-meta'; import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template'; import { createTemplate } from '@documenso/lib/server-only/template/create-template'; import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template'; @@ -12,6 +13,7 @@ import { ZCreateTemplateMutationSchema, ZDeleteTemplateMutationSchema, ZDuplicateTemplateMutationSchema, + ZSetSettingsForTemplateMutationSchema, } from './schema'; export const templateRouter = router({ @@ -104,4 +106,27 @@ export const templateRouter = router({ }); } }), + setSettingsForTemplate: authenticatedProcedure + .input(ZSetSettingsForTemplateMutationSchema) + .mutation(async ({ input }) => { + try { + const { meta, templateId } = input; + + return await upsertTemplateDocumentMeta({ + templateId, + subject: meta.subject, + message: meta.message, + dateFormat: meta.dateFormat, + timezone: meta.timezone, + redirectUrl: meta.redirectUrl, + }); + } catch (err) { + console.error(err); + + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'We were unable to set template settings. Please try again later.', + }); + } + }), }); diff --git a/packages/trpc/server/template-router/schema.ts b/packages/trpc/server/template-router/schema.ts index 3f16d7b39..b6a2e809b 100644 --- a/packages/trpc/server/template-router/schema.ts +++ b/packages/trpc/server/template-router/schema.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; +import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { RecipientRole } from '@documenso/prisma/client'; export const ZCreateTemplateMutationSchema = z.object({ @@ -22,6 +23,22 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({ .optional(), }); +export const ZSetSettingsForTemplateMutationSchema = z.object({ + templateId: z.number(), + meta: z.object({ + subject: z.string().optional(), + message: z.string().optional(), + timezone: z.string().optional(), + dateFormat: z.string().optional(), + redirectUrl: z + .string() + .optional() + .refine((value) => value === undefined || value === '' || URL_REGEX.test(value), { + message: 'Please enter a valid URL', + }), + }), +}); + export const ZDuplicateTemplateMutationSchema = z.object({ templateId: z.number(), teamId: z.number().optional(), diff --git a/packages/ui/primitives/template-flow/add-template-settings.tsx b/packages/ui/primitives/template-flow/add-template-settings.tsx index 7c8b82192..321e6b287 100644 --- a/packages/ui/primitives/template-flow/add-template-settings.tsx +++ b/packages/ui/primitives/template-flow/add-template-settings.tsx @@ -7,11 +7,9 @@ import { InfoIcon } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; -import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth'; import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; -import { DocumentAccessAuth, DocumentActionAuth } from '@documenso/lib/types/document-auth'; -import { DocumentStatus, type Field, type Recipient, SendStatus } from '@documenso/prisma/client'; -import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; +import type { Template, TemplateDocumentMeta } from '@documenso/prisma/client'; +import { type Recipient, SendStatus } from '@documenso/prisma/client'; import { Accordion, AccordionContent, @@ -32,47 +30,42 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, - DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from '../document-flow/document-flow-root'; -import { ShowFieldItem } from '../document-flow/show-field-item'; import type { DocumentFlowStep } from '../document-flow/types'; import { Input } from '../input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select'; import { useStep } from '../stepper'; +import { Textarea } from '../textarea'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import type { TTemplateSettingsFormSchema } from './add-template-settings.types'; import { ZTemplateSettingsFormSchema } from './add-template-settings.types'; export type AddSettingsFormProps = { + template: Template; documentFlow: DocumentFlowStep; recipients: Recipient[]; - fields: Field[]; - isDocumentEnterprise: boolean; - isDocumentPdfLoaded: boolean; - document: DocumentWithData; + documentMeta: TemplateDocumentMeta | null; onSubmit: (_data: TTemplateSettingsFormSchema) => void; }; export const AddTemplateSettingsFormPartial = ({ documentFlow, recipients, - fields, - isDocumentEnterprise, - isDocumentPdfLoaded, - document, + documentMeta, + template, onSubmit, }: AddSettingsFormProps) => { const form = useForm({ resolver: zodResolver(ZTemplateSettingsFormSchema), defaultValues: { - title: document.title, - globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined, - globalActionAuth: documentAuthOption?.globalActionAuth || undefined, + title: template.title, meta: { - timezone: document.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE, - dateFormat: document.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT, - redirectUrl: document.documentMeta?.redirectUrl ?? '', + subject: documentMeta?.subject ?? '', + message: documentMeta?.message ?? '', + timezone: documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE, + dateFormat: documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT, + redirectUrl: documentMeta?.redirectUrl ?? '', }, }, }); @@ -83,8 +76,6 @@ export const AddTemplateSettingsFormPartial = ({ (recipient) => recipient.sendStatus === SendStatus.SENT, ); - // We almost always want to set the timezone to the user's local timezone to avoid confusion - // when the document is signed. useEffect(() => { if (!form.formState.touchedFields.meta?.timezone && !documentHasBeenSent) { form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone); @@ -93,17 +84,7 @@ export const AddTemplateSettingsFormPartial = ({ return ( <> - - - {isDocumentPdfLoaded && - fields.map((field, index) => ( - - ))} -
( Title - - + )} /> - ( - - - Document access - - - - - - -

- Document access -

- -

The authentication required for recipients to view the document.

- -
    -
  • - Require account - The recipient must be signed in to - view the document -
  • -
  • - None - The document can be accessed directly by the URL - sent to the recipient -
  • -
-
-
-
- - - - -
- )} - /> - - {isDocumentEnterprise && ( - ( - - - Recipient action authentication - - - - - - -

- Global recipient action authentication -

- -

- The authentication required for recipients to sign the signature field. -

- -

- This can be overriden by setting the authentication requirements - directly on each recipient in the next step. -

- -
    -
  • - Require account - The recipient must be signed in -
  • -
  • - Require passkey - The recipient must have an account - and passkey configured via their settings -
  • -
  • - Require 2FA - The recipient must have an account and - 2FA enabled via their settings -
  • -
  • - None - No authentication required -
  • -
-
-
-
- - - - -
- )} - /> - )} - @@ -293,6 +144,67 @@ export const AddTemplateSettingsFormPartial = ({ )} /> + ( + + + Subject{' '} + + + + + + + Add email subject + + + + + + + + + + )} + /> + + ( + + + Message{' '} + + + + + + + Add message to send in the email + + + + + +