diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index 6bcd767e9..74afa80c0 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -2,6 +2,9 @@ import { createNextRoute } from '@ts-rest/next'; import { getServerLimits } from '@documenso/ee/server-only/limits/server'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; +import '@documenso/lib/constants/time-zones'; +import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { AppError } from '@documenso/lib/errors/app-error'; import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data'; import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta'; @@ -222,6 +225,36 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { }; } + const dateFormat = body.meta.dateFormat + ? DATE_FORMATS.find((format) => format.label === body.meta.dateFormat) + : DATE_FORMATS.find((format) => format.value === DEFAULT_DOCUMENT_DATE_FORMAT); + const timezone = body.meta.timezone + ? TIME_ZONES.find((tz) => tz === body.meta.timezone) + : DEFAULT_DOCUMENT_TIME_ZONE; + + const isDateFormatValid = body.meta.dateFormat + ? DATE_FORMATS.some((format) => format.label === dateFormat?.label) + : true; + const isTimeZoneValid = body.meta.timezone ? TIME_ZONES.includes(String(timezone)) : true; + + if (!isDateFormatValid) { + return { + status: 400, + body: { + message: 'Invalid date format. Please provide a valid date format', + }, + }; + } + + if (!isTimeZoneValid) { + return { + status: 400, + body: { + message: 'Invalid timezone. Please provide a valid timezone', + }, + }; + } + const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`; const { url, key } = await getPresignPostUrl(fileName, 'application/pdf'); @@ -244,7 +277,11 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { await upsertDocumentMeta({ documentId: document.id, userId: user.id, - ...body.meta, + subject: body.meta.subject, + message: body.meta.message, + timezone, + dateFormat: dateFormat?.value, + redirectUrl: body.meta.redirectUrl, requestMetadata: extractNextApiRequestMetadata(args.req), }); diff --git a/packages/api/v1/schema.ts b/packages/api/v1/schema.ts index dddc0a0e1..90d3f65bf 100644 --- a/packages/api/v1/schema.ts +++ b/packages/api/v1/schema.ts @@ -1,5 +1,9 @@ +import { extendZodWithOpenApi } from '@anatine/zod-openapi'; import { z } from 'zod'; +import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; +import '@documenso/lib/constants/time-zones'; +import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { ZUrlSchema } from '@documenso/lib/schemas/common'; import { DocumentDataType, @@ -11,6 +15,8 @@ import { TemplateType, } from '@documenso/prisma/client'; +extendZodWithOpenApi(z); + export const ZNoBodyMutationSchema = null; /** @@ -97,8 +103,19 @@ export const ZCreateDocumentMutationSchema = z.object({ .object({ subject: z.string(), message: z.string(), - timezone: z.string(), - dateFormat: z.string(), + timezone: z.string().default(DEFAULT_DOCUMENT_TIME_ZONE).openapi({ + description: + 'The timezone of the date. Must be one of the options listed in the list below.', + enum: TIME_ZONES, + }), + dateFormat: z + .string() + .default(DEFAULT_DOCUMENT_DATE_FORMAT) + .openapi({ + description: + 'The format of the date. Must be one of the options listed in the list below.', + enum: DATE_FORMATS.map((format) => format.value), + }), redirectUrl: z.string(), }) .partial(), diff --git a/packages/ui/primitives/document-flow/add-settings.tsx b/packages/ui/primitives/document-flow/add-settings.tsx index dddba289c..a95ca29db 100644 --- a/packages/ui/primitives/document-flow/add-settings.tsx +++ b/packages/ui/primitives/document-flow/add-settings.tsx @@ -82,8 +82,12 @@ export const AddSettingsFormPartial = ({ globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined, globalActionAuth: documentAuthOption?.globalActionAuth || undefined, meta: { - timezone: document.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE, - dateFormat: document.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT, + timezone: + TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ?? + DEFAULT_DOCUMENT_TIME_ZONE, + dateFormat: + DATE_FORMATS.find((format) => format.label === document.documentMeta?.dateFormat) + ?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT, redirectUrl: document.documentMeta?.redirectUrl ?? '', }, }, @@ -98,10 +102,20 @@ export const AddSettingsFormPartial = ({ // 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) { + if ( + !form.formState.touchedFields.meta?.timezone && + !documentHasBeenSent && + !document.documentMeta?.timezone + ) { form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone); } - }, [documentHasBeenSent, form, form.setValue, form.formState.touchedFields.meta?.timezone]); + }, [ + documentHasBeenSent, + form, + form.setValue, + form.formState.touchedFields.meta?.timezone, + document.documentMeta?.timezone, + ]); return ( <>