diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
index e6cbd6fd4..813458062 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
@@ -151,7 +151,7 @@ export const EditDocumentForm = ({
};
const onAddSubjectFormSubmit = async (data: TAddSubjectFormSchema) => {
- const { subject, message, timezone, dateFormat } = data.meta;
+ const { subject, message, timezone, dateFormat, redirectUrl } = data.meta;
try {
await sendDocument({
@@ -159,8 +159,9 @@ export const EditDocumentForm = ({
meta: {
subject,
message,
- timezone,
dateFormat,
+ timezone,
+ redirectUrl,
},
});
diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx
index 7105baafd..7e6cf26b8 100644
--- a/apps/web/src/app/(signing)/sign/[token]/form.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx
@@ -26,9 +26,10 @@ export type SigningFormProps = {
document: Document;
recipient: Recipient;
fields: Field[];
+ redirectUrl?: string | null;
};
-export const SigningForm = ({ document, recipient, fields }: SigningFormProps) => {
+export const SigningForm = ({ document, recipient, fields, redirectUrl }: SigningFormProps) => {
const router = useRouter();
const analytics = useAnalytics();
const { data: session } = useSession();
@@ -74,7 +75,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
timestamp: new Date().toISOString(),
});
- router.push(`/sign/${recipient.token}/complete`);
+ redirectUrl ? router.push(redirectUrl) : router.push(`/sign/${recipient.token}/complete`);
};
return (
diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx
index 7e025593c..9a7e8acbe 100644
--- a/apps/web/src/app/(signing)/sign/[token]/page.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx
@@ -8,7 +8,6 @@ import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
-import { getDocumentMetaByDocumentId } from '@documenso/lib/server-only/document/get-document-meta-by-document-id';
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
@@ -49,15 +48,13 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
viewedDocument({ token }).catch(() => null),
]);
- const documentMeta = await getDocumentMetaByDocumentId({ id: document!.id }).catch(() => null);
-
if (!document || !document.documentData || !recipient) {
return notFound();
}
const truncatedTitle = truncateTitle(document.title);
- const { documentData } = document;
+ const { documentData, documentMeta } = document;
const { user } = await getServerComponentSession();
@@ -65,7 +62,9 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
document.status === DocumentStatus.COMPLETED ||
recipient.signingStatus === SigningStatus.SIGNED
) {
- redirect(`/sign/${token}/complete`);
+ documentMeta?.redirectUrl
+ ? redirect(documentMeta.redirectUrl)
+ : redirect(`/sign/${token}/complete`);
}
if (documentMeta?.password) {
@@ -133,7 +132,12 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
-
+
diff --git a/package-lock.json b/package-lock.json
index aae034c57..c4d4c1e5d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14503,6 +14503,7 @@
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz",
"integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==",
+ "peer": true,
"engines": {
"node": ">=6.0.0"
}
@@ -19495,14 +19496,14 @@
"@react-email/section": "0.0.10",
"@react-email/tailwind": "0.0.9",
"@react-email/text": "0.0.6",
- "nodemailer": "^6.9.3",
+ "nodemailer": "^6.9.9",
"react-email": "^1.9.5",
"resend": "^2.0.0"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
"@documenso/tsconfig": "*",
- "@types/nodemailer": "^6.4.8",
+ "@types/nodemailer": "^6.4.14",
"tsup": "^7.1.0"
}
},
@@ -19520,6 +19521,14 @@
"node": ">=16.0.0"
}
},
+ "packages/email/node_modules/nodemailer": {
+ "version": "6.9.9",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz",
+ "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"packages/eslint-config": {
"name": "@documenso/eslint-config",
"version": "0.0.0",
diff --git a/packages/email/package.json b/packages/email/package.json
index d41a4c24c..984ea3d4c 100644
--- a/packages/email/package.json
+++ b/packages/email/package.json
@@ -35,14 +35,14 @@
"@react-email/section": "0.0.10",
"@react-email/tailwind": "0.0.9",
"@react-email/text": "0.0.6",
- "nodemailer": "^6.9.3",
+ "nodemailer": "^6.9.9",
"react-email": "^1.9.5",
"resend": "^2.0.0"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
"@documenso/tsconfig": "*",
- "@types/nodemailer": "^6.4.8",
+ "@types/nodemailer": "^6.4.14",
"tsup": "^7.1.0"
}
}
diff --git a/packages/lib/constants/url-regex.ts b/packages/lib/constants/url-regex.ts
new file mode 100644
index 000000000..259ce070d
--- /dev/null
+++ b/packages/lib/constants/url-regex.ts
@@ -0,0 +1,2 @@
+export const URL_REGEX =
+ /^(https?):\/\/(?:www\.)?[a-zA-Z0-9-]+\.[a-zA-Z0-9()]{2,}(?:\/[a-zA-Z0-9-._?&=/]*)?$/i;
diff --git a/packages/lib/server-only/document-meta/upsert-document-meta.ts b/packages/lib/server-only/document-meta/upsert-document-meta.ts
index 3e6cd75be..7bd6d93cc 100644
--- a/packages/lib/server-only/document-meta/upsert-document-meta.ts
+++ b/packages/lib/server-only/document-meta/upsert-document-meta.ts
@@ -9,6 +9,7 @@ export type CreateDocumentMetaOptions = {
timezone?: string;
password?: string;
dateFormat?: string;
+ redirectUrl?: string;
userId: number;
};
@@ -20,6 +21,7 @@ export const upsertDocumentMeta = async ({
documentId,
userId,
password,
+ redirectUrl,
}: CreateDocumentMetaOptions) => {
await prisma.document.findFirstOrThrow({
where: {
@@ -48,17 +50,19 @@ export const upsertDocumentMeta = async ({
create: {
subject,
message,
+ password,
dateFormat,
timezone,
- password,
documentId,
+ redirectUrl,
},
update: {
subject,
message,
- dateFormat,
password,
+ dateFormat,
timezone,
+ redirectUrl,
},
});
};
diff --git a/packages/lib/server-only/document/duplicate-document-by-id.ts b/packages/lib/server-only/document/duplicate-document-by-id.ts
index 5ca848bb3..4e6a7bd87 100644
--- a/packages/lib/server-only/document/duplicate-document-by-id.ts
+++ b/packages/lib/server-only/document/duplicate-document-by-id.ts
@@ -39,6 +39,7 @@ export const duplicateDocumentById = async ({
dateFormat: true,
password: true,
timezone: true,
+ redirectUrl: true,
},
},
},
diff --git a/packages/lib/server-only/document/get-document-by-token.ts b/packages/lib/server-only/document/get-document-by-token.ts
index 62c8a5ca1..18f9a5161 100644
--- a/packages/lib/server-only/document/get-document-by-token.ts
+++ b/packages/lib/server-only/document/get-document-by-token.ts
@@ -27,6 +27,7 @@ export const getDocumentAndSenderByToken = async ({
include: {
User: true,
documentData: true,
+ documentMeta: true,
},
});
diff --git a/packages/prisma/migrations/20240206111230_add_document_meta_redirect_url/migration.sql b/packages/prisma/migrations/20240206111230_add_document_meta_redirect_url/migration.sql
new file mode 100644
index 000000000..0eb8a1175
--- /dev/null
+++ b/packages/prisma/migrations/20240206111230_add_document_meta_redirect_url/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "DocumentMeta" ADD COLUMN "redirectUrl" TEXT;
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index fc128efc1..ff2d12319 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -199,6 +199,7 @@ model DocumentMeta {
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
documentId Int @unique
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
+ redirectUrl String?
}
enum ReadStatus {
diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts
index 5940d971d..d0ff48941 100644
--- a/packages/trpc/server/document-router/router.ts
+++ b/packages/trpc/server/document-router/router.ts
@@ -215,13 +215,14 @@ export const documentRouter = router({
try {
const { documentId, meta } = input;
- if (meta.message || meta.subject || meta.timezone || meta.dateFormat) {
+ if (meta.message || meta.subject || meta.timezone || meta.dateFormat || meta.redirectUrl) {
await upsertDocumentMeta({
documentId,
subject: meta.subject,
message: meta.message,
dateFormat: meta.dateFormat,
timezone: meta.timezone,
+ redirectUrl: meta.redirectUrl,
userId: ctx.user.id,
});
}
diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts
index f8d008f50..fceb6413f 100644
--- a/packages/trpc/server/document-router/schema.ts
+++ b/packages/trpc/server/document-router/schema.ts
@@ -1,5 +1,6 @@
import { z } from 'zod';
+import { URL_REGEX } from '@documenso/lib/constants/url-regex';
import { DocumentStatus, FieldType, RecipientRole } from '@documenso/prisma/client';
export const ZGetDocumentByIdQuerySchema = z.object({
@@ -73,6 +74,12 @@ export const ZSendDocumentMutationSchema = z.object({
message: z.string(),
timezone: z.string(),
dateFormat: z.string(),
+ redirectUrl: z
+ .string()
+ .optional()
+ .refine((value) => value === undefined || URL_REGEX.test(value), {
+ message: 'Please enter a valid URL',
+ }),
}),
});
diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx
index b3bbcbe92..40e42e3b3 100644
--- a/packages/ui/primitives/document-flow/add-subject.tsx
+++ b/packages/ui/primitives/document-flow/add-subject.tsx
@@ -2,6 +2,8 @@
import { useEffect } from 'react';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Info } from 'lucide-react';
import { Controller, useForm } from 'react-hook-form';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
@@ -23,6 +25,7 @@ import {
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { Combobox } from '../combobox';
import { FormErrorMessage } from '../form/form-error-message';
@@ -30,7 +33,7 @@ import { Input } from '../input';
import { Label } from '../label';
import { useStep } from '../stepper';
import { Textarea } from '../textarea';
-import type { TAddSubjectFormSchema } from './add-subject.types';
+import { type TAddSubjectFormSchema, ZAddSubjectFormSchema } from './add-subject.types';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
@@ -69,8 +72,10 @@ export const AddSubjectFormPartial = ({
message: document.documentMeta?.message ?? '',
timezone: document.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
dateFormat: document.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
+ redirectUrl: document.documentMeta?.redirectUrl ?? '',
},
},
+ resolver: zodResolver(ZAddSubjectFormSchema),
});
const onFormSubmit = handleSubmit(onSubmit);
@@ -163,64 +168,94 @@ export const AddSubjectFormPartial = ({
- {hasDateField && (
-
-
-
- Advanced Options
-
+
+
+
+ Advanced Options
+
-
-
-
+
+ {hasDateField && (
+ <>
+
+
- (
-
+ )}
+ />
+
+
+
+
+
+ (
+ value && onChange(value)}
+ disabled={documentHasBeenSent}
+ />
+ )}
+ />
+
+ >
+ )}
+
+
+
-
-
-
-
- (
- value && onChange(value)}
- disabled={documentHasBeenSent}
- />
- )}
- />
-
-
-
-
- )}
+
+
+
+
diff --git a/packages/ui/primitives/document-flow/add-subject.types.ts b/packages/ui/primitives/document-flow/add-subject.types.ts
index ea14f4c0f..fd4175368 100644
--- a/packages/ui/primitives/document-flow/add-subject.types.ts
+++ b/packages/ui/primitives/document-flow/add-subject.types.ts
@@ -2,6 +2,7 @@ import { z } from 'zod';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
+import { URL_REGEX } from '@documenso/lib/constants/url-regex';
export const ZAddSubjectFormSchema = z.object({
meta: z.object({
@@ -9,6 +10,12 @@ export const ZAddSubjectFormSchema = z.object({
message: z.string(),
timezone: z.string().optional().default(DEFAULT_DOCUMENT_TIME_ZONE),
dateFormat: z.string().optional().default(DEFAULT_DOCUMENT_DATE_FORMAT),
+ redirectUrl: z
+ .string()
+ .optional()
+ .refine((value) => value === undefined || URL_REGEX.test(value), {
+ message: 'Please enter a valid URL',
+ }),
}),
});