Compare commits

..

2 Commits

Author SHA1 Message Date
07c852744b v1.9.0-rc.6 2025-01-08 20:18:09 +11:00
4fab98c633 feat: allow switching document when using templates (#1571)
Adds the ability to upload a custom document when using a template.

This is useful when you have a given fixed template with placeholder
values that you want to decorate with Documenso fields but will then
create a final specialised document when sending it out to a given
recipient.
2025-01-07 16:13:35 +11:00
139 changed files with 779 additions and 867 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@documenso/web",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.6",
"private": true,
"license": "AGPL-3.0",
"scripts": {

View File

@ -10,7 +10,7 @@ import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import type { Document, Recipient, User } from '@documenso/prisma/client';
export type DocumentPageViewInformationProps = {
userId: string;
userId: number;
document: Document & {
User: Pick<User, 'id' | 'name' | 'email'>;
Recipient: Recipient[];

View File

@ -16,7 +16,7 @@ import { cn } from '@documenso/ui/lib/utils';
export type DocumentPageViewRecentActivityProps = {
documentId: number;
userId: string;
userId: number;
};
export const DocumentPageViewRecentActivity = ({

View File

@ -10,7 +10,7 @@ import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import type { Template, User } from '@documenso/prisma/client';
export type TemplatePageViewInformationProps = {
userId: string;
userId: number;
template: Template & {
User: Pick<User, 'id' | 'name' | 'email'>;
};

View File

@ -7,15 +7,17 @@ import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { InfoIcon, Plus } from 'lucide-react';
import { InfoIcon, Plus, Upload, X } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import * as z from 'zod';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import {
TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX,
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
} from '@documenso/lib/constants/template';
import { AppError } from '@documenso/lib/errors/app-error';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import type { Recipient } from '@documenso/prisma/client';
import { DocumentDistributionMethod, DocumentSigningOrder } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
@ -50,6 +52,11 @@ import { useOptionalCurrentTeam } from '~/providers/team';
const ZAddRecipientsForNewDocumentSchema = z
.object({
distributeDocument: z.boolean(),
useCustomDocument: z.boolean().default(false),
customDocumentData: z
.any()
.refine((data) => data instanceof File || data === undefined)
.optional(),
recipients: z.array(
z.object({
id: z.number(),
@ -119,6 +126,8 @@ export function UseTemplateDialog({
resolver: zodResolver(ZAddRecipientsForNewDocumentSchema),
defaultValues: {
distributeDocument: false,
useCustomDocument: false,
customDocumentData: undefined,
recipients: recipients
.sort((a, b) => (a.signingOrder || 0) - (b.signingOrder || 0))
.map((recipient) => {
@ -145,11 +154,19 @@ export function UseTemplateDialog({
const onSubmit = async (data: TAddRecipientsForNewDocumentSchema) => {
try {
let customDocumentDataId: string | undefined = undefined;
if (data.useCustomDocument && data.customDocumentData) {
const customDocumentData = await putPdfFile(data.customDocumentData);
customDocumentDataId = customDocumentData.id;
}
const { id } = await createDocumentFromTemplate({
templateId,
teamId: team?.id,
recipients: data.recipients,
distributeDocument: data.distributeDocument,
customDocumentDataId,
});
toast({
@ -300,7 +317,6 @@ export function UseTemplateDialog({
/>
</div>
))}
</div>
{recipients.length > 0 && (
<div className="mt-4 flex flex-row items-center">
@ -331,8 +347,8 @@ export function UseTemplateDialog({
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
<p>
<Trans>
The document will be immediately sent to recipients if this is
checked.
The document will be immediately sent to recipients if this
is checked.
</Trans>
</p>
@ -358,7 +374,9 @@ export function UseTemplateDialog({
</TooltipTrigger>
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
<p>
<Trans>Create the document as pending and ready to sign.</Trans>
<Trans>
Create the document as pending and ready to sign.
</Trans>
</p>
<p>
@ -367,8 +385,8 @@ export function UseTemplateDialog({
<p className="mt-2">
<Trans>
We will generate signing links for you, which you can send to
the recipients through your method of choice.
We will generate signing links for you, which you can send
to the recipients through your method of choice.
</Trans>
</p>
</TooltipContent>
@ -382,7 +400,162 @@ export function UseTemplateDialog({
</div>
)}
<DialogFooter>
<FormField
control={form.control}
name="useCustomDocument"
render={({ field }) => (
<FormItem>
<div className="flex flex-row items-center">
<Checkbox
id="useCustomDocument"
className="h-5 w-5"
checked={field.value}
onCheckedChange={(checked) => {
field.onChange(checked);
if (!checked) {
form.setValue('customDocumentData', undefined);
}
}}
/>
<label
className="text-muted-foreground ml-2 flex items-center text-sm"
htmlFor="useCustomDocument"
>
<Trans>Upload custom document</Trans>
<Tooltip>
<TooltipTrigger type="button">
<InfoIcon className="mx-1 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
<p>
<Trans>
Upload a custom document to use instead of the template's default
document
</Trans>
</p>
</TooltipContent>
</Tooltip>
</label>
</div>
</FormItem>
)}
/>
{form.watch('useCustomDocument') && (
<div className="my-4">
<FormField
control={form.control}
name="customDocumentData"
render={({ field }) => (
<FormItem>
<FormControl>
<div className="w-full space-y-4">
<label
className={cn(
'text-muted-foreground hover:border-muted-foreground/50 group relative flex min-h-[150px] cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-gray-300 px-6 py-10 transition-colors',
{
'border-destructive hover:border-destructive':
form.formState.errors.customDocumentData,
},
)}
>
<div className="text-center">
{!field.value && (
<>
<Upload className="text-muted-foreground/50 mx-auto h-10 w-10" />
<div className="mt-4 flex text-sm leading-6">
<span className="text-muted-foreground relative">
<Trans>
<span className="text-primary font-semibold">
Click to upload
</span>{' '}
or drag and drop
</Trans>
</span>
</div>
<p className="text-muted-foreground/80 text-xs">
PDF files only
</p>
</>
)}
{field.value && (
<div className="text-muted-foreground space-y-1">
<p className="text-sm font-medium">{field.value.name}</p>
<p className="text-muted-foreground/60 text-xs">
{(field.value.size / (1024 * 1024)).toFixed(2)} MB
</p>
</div>
)}
</div>
<input
type="file"
className="absolute h-full w-full opacity-0"
accept=".pdf,application/pdf"
onChange={(e) => {
const file = e.target.files?.[0];
if (!file) {
field.onChange(undefined);
return;
}
if (file.type !== 'application/pdf') {
form.setError('customDocumentData', {
type: 'manual',
message: _(msg`Please select a PDF file`),
});
return;
}
if (file.size > APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024) {
form.setError('customDocumentData', {
type: 'manual',
message: _(
msg`File size exceeds the limit of ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB`,
),
});
return;
}
field.onChange(file);
}}
/>
{field.value && (
<div className="absolute right-2 top-2">
<Button
type="button"
variant="destructive"
className="h-6 w-6 p-0"
onClick={(e) => {
e.preventDefault();
field.onChange(undefined);
}}
>
<X className="h-4 w-4" />
<div className="sr-only">
<Trans>Clear file</Trans>
</div>
</Button>
</div>
)}
</label>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
</div>
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button type="button" variant="secondary">
<Trans>Close</Trans>

View File

@ -45,7 +45,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export type TransferTeamDialogProps = {
teamId: number;
teamName: string;
ownerUserId: string;
ownerUserId: number;
trigger?: React.ReactNode;
};
@ -98,7 +98,7 @@ export const TransferTeamDialog = ({
try {
await requestTeamOwnershipTransfer({
teamId,
newOwnerUserId,
newOwnerUserId: Number.parseInt(newOwnerUserId),
clearPaymentMethods,
});

View File

@ -34,7 +34,7 @@ import { UpdateTeamMemberDialog } from '../dialogs/update-team-member-dialog';
export type TeamMembersDataTableProps = {
currentUserTeamRole: TeamMemberRole;
teamOwneruserId: string;
teamOwnerUserId: number;
teamId: number;
teamName: string;
};

View File

@ -20,7 +20,7 @@ export type TeamsMemberPageDataTableProps = {
currentUserTeamRole: TeamMemberRole;
teamId: number;
teamName: string;
teamOwneruserId: string;
teamOwnerUserId: number;
};
export const TeamsMemberPageDataTable = ({

View File

@ -24,7 +24,7 @@ import { DocumentHistorySheetChanges } from './document-history-sheet-changes';
export type DocumentHistorySheetProps = {
documentId: number;
userId: string;
userId: number;
isMenuOpen?: boolean;
onMenuOpenChange?: (_value: boolean) => void;
children?: React.ReactNode;

191
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.6",
"workspaces": [
"apps/*",
"packages/*"
@ -15,12 +15,10 @@
"@documenso/pdf-sign": "^0.1.0",
"@documenso/prisma": "^0.0.0",
"@lingui/core": "^4.11.3",
"@prisma/client": "^6.1.0",
"inngest-cli": "^0.29.1",
"luxon": "^3.5.0",
"mupdf": "^1.0.0",
"next-runtime-env": "^3.2.0",
"prisma": "^6.1.0",
"react": "^18",
"zod": "3.24.1"
},
@ -135,7 +133,7 @@
},
"apps/web": {
"name": "@documenso/web",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.6",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/api": "*",
@ -5638,13 +5636,15 @@
}
},
"node_modules/@prisma/client": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.1.0.tgz",
"integrity": "sha512-AbQYc5+EJKm1Ydfq3KxwcGiy7wIbm4/QbjCKWWoNROtvy7d6a3gmAGkKjK0iUCzh+rHV8xDhD5Cge8ke/kiy5Q==",
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.4.2.tgz",
"integrity": "sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/engines-version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574"
},
"engines": {
"node": ">=18.18"
"node": ">=16.13"
},
"peerDependencies": {
"prisma": "*"
@ -5676,49 +5676,15 @@
}
},
"node_modules/@prisma/engines": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.1.0.tgz",
"integrity": "sha512-GnYJbCiep3Vyr1P/415ReYrgJUjP79fBNc1wCo7NP6Eia0CzL2Ot9vK7Infczv3oK7JLrCcawOSAxFxNFsAERQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.1.0",
"@prisma/engines-version": "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959",
"@prisma/fetch-engine": "6.1.0",
"@prisma/get-platform": "6.1.0"
}
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.4.2.tgz",
"integrity": "sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g==",
"hasInstallScript": true
},
"node_modules/@prisma/engines-version": {
"version": "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959.tgz",
"integrity": "sha512-PdJqmYM2Fd8K0weOOtQThWylwjsDlTig+8Pcg47/jszMuLL9iLIaygC3cjWJLda69siRW4STlCTMSgOjZzvKPQ==",
"license": "Apache-2.0"
},
"node_modules/@prisma/engines/node_modules/@prisma/debug": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.1.0.tgz",
"integrity": "sha512-0himsvcM4DGBTtvXkd2Tggv6sl2JyUYLzEGXXleFY+7Kp6rZeSS3hiTW9mwtUlXrwYbJP6pwlVNB7jYElrjWUg==",
"license": "Apache-2.0"
},
"node_modules/@prisma/engines/node_modules/@prisma/fetch-engine": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.1.0.tgz",
"integrity": "sha512-asdFi7TvPlEZ8CzSZ/+Du5wZ27q6OJbRSXh+S8ISZguu+S9KtS/gP7NeXceZyb1Jv1SM1S5YfiCv+STDsG6rrg==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.1.0",
"@prisma/engines-version": "6.1.0-21.11f085a2012c0f4778414c8db2651556ee0ef959",
"@prisma/get-platform": "6.1.0"
}
},
"node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.1.0.tgz",
"integrity": "sha512-ia8bNjboBoHkmKGGaWtqtlgQOhCi7+f85aOkPJKgNwWvYrT6l78KgojLekE8zMhVk0R9lWcifV0Pf8l3/15V0Q==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.1.0"
}
"version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz",
"integrity": "sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA=="
},
"node_modules/@prisma/fetch-engine": {
"version": "5.3.1",
@ -20122,6 +20088,14 @@
"node": ">=6"
}
},
"node_modules/kysely": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.3.tgz",
"integrity": "sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/language-subtag-registry": {
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
@ -26188,22 +26162,26 @@
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
},
"node_modules/prisma": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.1.0.tgz",
"integrity": "sha512-aFI3Yi+ApUxkwCJJwyQSwpyzUX7YX3ihzuHNHOyv4GJg3X5tQsmRaJEnZ+ZyfHpMtnyahhmXVfbTZ+lS8ZtfKw==",
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.4.2.tgz",
"integrity": "sha512-GDMZwZy7mysB2oXU+angQqJ90iaPFdD0rHaZNkn+dio5NRkGLmMqmXs31//tg/qXT3iB0cTQwnGGQNuirhSTZg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/engines": "6.1.0"
"@prisma/engines": "5.4.2"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=18.18"
"node": ">=16.13"
}
},
"optionalDependencies": {
"fsevents": "2.3.3"
"node_modules/prisma-extension-kysely": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/prisma-extension-kysely/-/prisma-extension-kysely-2.1.0.tgz",
"integrity": "sha512-s1hujYjrNzfQc9Z79s93464mkTkkt9ZPqPELDRFe9jEmiIlM/JRXZtqIyyN3FM0GDkN/BDPVtpPtdC52XnjAcQ==",
"peerDependencies": {
"@prisma/client": "latest"
}
},
"node_modules/prisma-kysely": {
@ -34092,6 +34070,36 @@
"zod": "^3.20.2"
}
},
"node_modules/zod-prisma-types": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/zod-prisma-types/-/zod-prisma-types-3.1.8.tgz",
"integrity": "sha512-5oe0ays3ur4u2GtuUqlhgCraKBcsuMaMI8o7VMV4YAnFeOuVid7K2zGvjI19V0ue9PeNF2ICyVREQVohaQm5dw==",
"dev": true,
"dependencies": {
"@prisma/generator-helper": "^5.14.0",
"code-block-writer": "^12.0.0",
"lodash": "^4.17.21",
"zod": "^3.23.8"
},
"bin": {
"zod-prisma-types": "dist/bin.js"
}
},
"node_modules/zod-prisma-types/node_modules/@prisma/debug": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
"dev": true
},
"node_modules/zod-prisma-types/node_modules/@prisma/generator-helper": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-5.22.0.tgz",
"integrity": "sha512-LwqcBQ5/QsuAaLNQZAIVIAJDJBMjHwMwn16e06IYx/3Okj/xEEfw9IvrqB2cJCl3b2mCBlh3eVH0w9WGmi4aHg==",
"dev": true,
"dependencies": {
"@prisma/debug": "5.22.0"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz",
@ -35458,10 +35466,10 @@
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@prisma/client": "^6.1.0",
"kysely": "^0.27.5",
"prisma": "^6.1.0",
"prisma-extension-kysely": "^3.0.0",
"@prisma/client": "5.4.2",
"kysely": "^0.27.3",
"prisma": "5.4.2",
"prisma-extension-kysely": "^2.1.0",
"ts-pattern": "^5.0.6"
},
"devDependencies": {
@ -35470,42 +35478,7 @@
"prisma-kysely": "^1.8.0",
"tsx": "^4.11.0",
"typescript": "5.2.2",
"zod-prisma-types": "^3.2.1"
}
},
"packages/prisma/node_modules/@prisma/debug": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.1.0.tgz",
"integrity": "sha512-0himsvcM4DGBTtvXkd2Tggv6sl2JyUYLzEGXXleFY+7Kp6rZeSS3hiTW9mwtUlXrwYbJP6pwlVNB7jYElrjWUg==",
"dev": true,
"license": "Apache-2.0"
},
"packages/prisma/node_modules/@prisma/generator-helper": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-6.1.0.tgz",
"integrity": "sha512-drHaTKRmRsz6esHk2dpn7aPoxfttoqkYWSaI7zXsL5YQz73jww1YgJpGbPgOUiblriJAtdT4o7mibMqnf8TOsA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.1.0"
}
},
"packages/prisma/node_modules/kysely": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.5.tgz",
"integrity": "sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"packages/prisma/node_modules/prisma-extension-kysely": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prisma-extension-kysely/-/prisma-extension-kysely-3.0.0.tgz",
"integrity": "sha512-J8gtIINuqsmbucei7PehAiuKvlPEe6SpRNXM6SdG7wXvY2aNxm5RnjnTwWITLTvzM3smarsKfMMfpMcNua82mA==",
"license": "MIT",
"peerDependencies": {
"@prisma/client": "latest"
"zod-prisma-types": "^3.1.8"
}
},
"packages/prisma/node_modules/ts-pattern": {
@ -35526,26 +35499,6 @@
"node": ">=14.17"
}
},
"packages/prisma/node_modules/zod-prisma-types": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/zod-prisma-types/-/zod-prisma-types-3.2.1.tgz",
"integrity": "sha512-qsOD8aMVx3Yg9gHctvhTRpavaJizt8xUda6qjwOcH7suvrirXax38tjs0ilKcY7GKbyY57q2rZ+uoSDnzVXpag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@prisma/generator-helper": "^6.0.1",
"code-block-writer": "^12.0.0",
"lodash": "^4.17.21",
"zod": "^3.23.8"
},
"bin": {
"zod-prisma-types": "dist/bin.js"
},
"peerDependencies": {
"@prisma/client": "^4.x.x || ^5.x.x || ^6.x.x",
"prisma": "^4.x.x || ^5.x.x || ^6.x.x"
}
},
"packages/signing": {
"name": "@documenso/signing",
"version": "0.0.0",

View File

@ -1,6 +1,6 @@
{
"private": true,
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.6",
"scripts": {
"build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web",
@ -70,15 +70,11 @@
"luxon": "^3.5.0",
"mupdf": "^1.0.0",
"next-runtime-env": "^3.2.0",
"@prisma/client": "^6.1.0",
"prisma": "^6.1.0",
"react": "^18",
"zod": "3.24.1"
},
"overrides": {
"next": "14.2.6",
"@prisma/client": "^6.1.0",
"prisma": "^6.1.0",
"zod": "3.24.1"
},
"trigger.dev": {

View File

@ -1,7 +1,11 @@
import { expect, test } from '@playwright/test';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { DocumentDataType } from '@documenso/prisma/client';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
@ -13,6 +17,20 @@ test.describe.configure({ mode: 'parallel' });
const enterprisePriceId = process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID || '';
// Create a temporary PDF file for testing
function createTempPdfFile() {
const tempDir = os.tmpdir();
const tempFilePath = path.join(tempDir, 'test.pdf');
// Create a simple PDF file with some content
const pdfContent = Buffer.from(
'%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj 2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj 3 0 obj<</Type/Page/MediaBox[0 0 612 792]/Parent 2 0 R>>endobj\nxref\n0 4\n0000000000 65535 f\n0000000009 00000 n\n0000000052 00000 n\n0000000101 00000 n\ntrailer<</Size 4/Root 1 0 R>>\nstartxref\n178\n%%EOF',
);
fs.writeFileSync(tempFilePath, pdfContent);
return tempFilePath;
}
/**
* 1. Create a template with all settings filled out
* 2. Create a document from the template
@ -283,3 +301,231 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
expect(recipientOneAuth.derivedRecipientAccessAuth).toEqual('ACCOUNT');
expect(recipientTwoAuth.derivedRecipientAccessAuth).toEqual('ACCOUNT');
});
/**
* This test verifies that we can create a document from a template using a custom document
* instead of the template's default document.
*/
test('[TEMPLATE]: should create a document from a template with custom document', async ({
page,
}) => {
const user = await seedUser();
const template = await seedBlankTemplate(user);
// Create a temporary PDF file for upload
const testPdfPath = createTempPdfFile();
const pdfContent = fs.readFileSync(testPdfPath).toString('base64');
try {
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
});
// Set template title
await page.getByLabel('Title').fill('TEMPLATE_WITH_CUSTOM_DOC');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// Add a signer
await page.getByPlaceholder('Email').fill('recipient@documenso.com');
await page.getByPlaceholder('Name').fill('Recipient');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Save template' }).click();
// Use template with custom document
await page.waitForURL('/templates');
await page.getByRole('button', { name: 'Use Template' }).click();
// Enable custom document upload and upload file
await page.getByLabel('Upload custom document').check();
await page.locator('input[type="file"]').setInputFiles(testPdfPath);
// Wait for upload to complete
await expect(page.getByText(path.basename(testPdfPath))).toBeVisible();
// Create document with custom document data
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the custom document data
await page.waitForURL(/documents/);
const documentId = Number(page.url().split('/').pop());
const document = await prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
include: {
documentData: true,
},
});
expect(document.title).toEqual('TEMPLATE_WITH_CUSTOM_DOC');
expect(document.documentData.type).toEqual(DocumentDataType.BYTES_64);
expect(document.documentData.data).toEqual(pdfContent);
expect(document.documentData.initialData).toEqual(pdfContent);
} finally {
// Clean up the temporary file
fs.unlinkSync(testPdfPath);
}
});
/**
* This test verifies that we can create a team document from a template using a custom document
* instead of the template's default document.
*/
test('[TEMPLATE]: should create a team document from a template with custom document', async ({
page,
}) => {
const { owner, ...team } = await seedTeam({
createTeamMembers: 2,
});
const template = await seedBlankTemplate(owner, {
createTemplateOptions: {
teamId: team.id,
},
});
// Create a temporary PDF file for upload
const testPdfPath = createTempPdfFile();
const pdfContent = fs.readFileSync(testPdfPath).toString('base64');
try {
await apiSignin({
page,
email: owner.email,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set template title
await page.getByLabel('Title').fill('TEAM_TEMPLATE_WITH_CUSTOM_DOC');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// Add a signer
await page.getByPlaceholder('Email').fill('recipient@documenso.com');
await page.getByPlaceholder('Name').fill('Recipient');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Save template' }).click();
// Use template with custom document
await page.waitForURL(`/t/${team.url}/templates`);
await page.getByRole('button', { name: 'Use Template' }).click();
// Enable custom document upload and upload file
await page.getByLabel('Upload custom document').check();
await page.locator('input[type="file"]').setInputFiles(testPdfPath);
// Wait for upload to complete
await expect(page.getByText(path.basename(testPdfPath))).toBeVisible();
// Create document with custom document data
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the custom document data
await page.waitForURL(/documents/);
const documentId = Number(page.url().split('/').pop());
const document = await prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
include: {
documentData: true,
},
});
expect(document.teamId).toEqual(team.id);
expect(document.title).toEqual('TEAM_TEMPLATE_WITH_CUSTOM_DOC');
expect(document.documentData.type).toEqual(DocumentDataType.BYTES_64);
expect(document.documentData.data).toEqual(pdfContent);
expect(document.documentData.initialData).toEqual(pdfContent);
} finally {
// Clean up the temporary file
fs.unlinkSync(testPdfPath);
}
});
/**
* This test verifies that when custom document upload is not enabled,
* the document uses the template's original document data.
*/
test('[TEMPLATE]: should create a document from a template using template document when custom document is not enabled', async ({
page,
}) => {
const user = await seedUser();
const template = await seedBlankTemplate(user);
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
});
// Set template title
await page.getByLabel('Title').fill('TEMPLATE_WITH_ORIGINAL_DOC');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// Add a signer
await page.getByPlaceholder('Email').fill('recipient@documenso.com');
await page.getByPlaceholder('Name').fill('Recipient');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Save template' }).click();
// Use template without custom document
await page.waitForURL('/templates');
await page.getByRole('button', { name: 'Use Template' }).click();
// Verify custom document upload is not checked by default
await expect(page.getByLabel('Upload custom document')).not.toBeChecked();
// Create document without custom document data
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the template's document data
await page.waitForURL(/documents/);
const documentId = Number(page.url().split('/').pop());
const document = await prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
include: {
documentData: true,
},
});
const templateWithData = await prisma.template.findFirstOrThrow({
where: {
id: template.id,
},
include: {
templateDocumentData: true,
},
});
expect(document.title).toEqual('TEMPLATE_WITH_ORIGINAL_DOC');
expect(document.documentData.data).toEqual(templateWithData.templateDocumentData.data);
expect(document.documentData.initialData).toEqual(
templateWithData.templateDocumentData.initialData,
);
expect(document.documentData.type).toEqual(templateWithData.templateDocumentData.type);
});

View File

@ -91,7 +91,7 @@ export const getStripeCustomerIdByUser = async (user: User) => {
return await getStripeCustomerByUser(user).then((session) => session.stripeCustomer.id);
};
const syncStripeCustomerSubscriptions = async (userId: string, stripeCustomerId: string) => {
const syncStripeCustomerSubscriptions = async (userId: number, stripeCustomerId: string) => {
const stripeSubscriptions = await stripe.subscriptions.list({
customer: stripeCustomerId,
});

View File

@ -6,7 +6,7 @@ import type { Subscription } from '@documenso/prisma/client';
import { getEnterprisePlanPriceIds } from '../stripe/get-enterprise-plan-prices';
export type IsUserEnterpriseOptions = {
userId: string;
userId: number;
teamId?: number;
};

View File

@ -126,7 +126,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
}
return {
id: user.id,
id: Number(user.id),
email: user.email,
name: user.name,
emailVerified: user.emailVerified?.toISOString() ?? null,
@ -140,7 +140,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
profile(profile) {
return {
id: profile.sub,
id: Number(profile.sub),
name: profile.name || `${profile.given_name} ${profile.family_name}`.trim(),
email: profile.email,
emailVerified: profile.email_verified ? new Date().toISOString() : null,
@ -274,7 +274,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
});
return {
id: user.id,
id: Number(user.id),
email: user.email,
name: user.name,
emailVerified: user.emailVerified?.toISOString() ?? null,
@ -308,7 +308,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
const { userId, email } = parsedCredential;
if (typeof userId !== 'string' || typeof email !== 'string') {
if (typeof userId !== 'number' || typeof email !== 'string') {
throw new AppError(AppErrorCode.INVALID_REQUEST);
}
@ -323,7 +323,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
}
return {
id: user.id,
id: Number(user.id),
email: user.email,
name: user.name,
emailVerified: user.emailVerified?.toISOString() ?? null,
@ -340,7 +340,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
} satisfies JWT;
if (!merged.email || typeof merged.emailVerified !== 'string') {
const userId = merged.id ?? token.sub;
const userId = Number(merged.id ?? token.sub);
const retrieved = await prisma.user.findFirst({
where: {
@ -367,7 +367,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
const user = await prisma.user.update({
where: {
id: merged.id,
id: Number(merged.id),
},
data: {
lastSignedIn: merged.lastSignedIn,
@ -384,7 +384,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
await prisma.user.update({
where: {
id: merged.id,
id: Number(merged.id),
},
data: {
emailVerified: merged.emailVerified,
@ -407,7 +407,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
return {
...session,
user: {
id: token.id,
id: Number(token.id),
name: token.name,
email: token.email,
emailVerified: token.emailVerified ?? null,

View File

@ -9,7 +9,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
import { getAuthenticatorOptions } from '../../utils/authenticator';
type CreatePasskeyAuthenticationOptions = {
userId: string;
userId: number;
/**
* The ID of the passkey to request authentication for.

View File

@ -8,7 +8,7 @@ import { PASSKEY_TIMEOUT } from '../../constants/auth';
import { getAuthenticatorOptions } from '../../utils/authenticator';
type CreatePasskeyRegistrationOptions = {
userId: string;
userId: number;
};
export const createPasskeyRegistrationOptions = async ({

View File

@ -10,7 +10,7 @@ import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getAuthenticatorOptions } from '../../utils/authenticator';
type CreatePasskeyOptions = {
userId: string;
userId: number;
passkeyName: string;
verificationResponse: RegistrationResponseJSON;
requestMetadata?: RequestMetadata;

View File

@ -4,7 +4,7 @@ import { UserSecurityAuditLogType } from '@documenso/prisma/client';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
export interface DeletePasskeyOptions {
userId: string;
userId: number;
passkeyId: string;
requestMetadata?: RequestMetadata;
}

View File

@ -5,7 +5,7 @@ import { Prisma } from '@documenso/prisma/client';
import type { FindResultResponse } from '../../types/search-params';
export interface FindPasskeysOptions {
userId: string;
userId: number;
query?: string;
page?: number;
perPage?: number;

View File

@ -11,7 +11,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
export interface SendConfirmationEmailProps {
userId: string;
userId: number;
}
export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailProps) => {

View File

@ -11,7 +11,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
export interface SendForgotPasswordOptions {
userId: string;
userId: number;
}
export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => {

View File

@ -8,7 +8,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
export interface SendResetPasswordOptions {
userId: string;
userId: number;
}
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {

View File

@ -4,7 +4,7 @@ import { UserSecurityAuditLogType } from '@documenso/prisma/client';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
export interface UpdateAuthenticatorsOptions {
userId: string;
userId: number;
passkeyId: string;
name: string;
requestMetadata?: RequestMetadata;

View File

@ -25,7 +25,7 @@ export type CreateDocumentMetaOptions = {
distributionMethod?: DocumentDistributionMethod;
typedSignatureEnabled?: boolean;
language?: SupportedLanguageCodes;
userId: string;
userId: number;
requestMetadata: RequestMetadata;
};

View File

@ -21,7 +21,7 @@ import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
export type CreateDocumentOptions = {
title: string;
externalId?: string | null;
userId: string;
userId: number;
teamId?: number;
documentDataId: string;
formValues?: Record<string, string | number | boolean>;

View File

@ -29,7 +29,7 @@ import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-t
export type DeleteDocumentOptions = {
id: number;
userId: string;
userId: number;
teamId?: number;
requestMetadata?: RequestMetadata;
};

View File

@ -7,7 +7,7 @@ import { getDocumentWhereInput } from './get-document-by-id';
export interface DuplicateDocumentOptions {
documentId: number;
userId: string;
userId: number;
teamId?: number;
}

View File

@ -7,7 +7,7 @@ import type { FindResultResponse } from '../../types/search-params';
import { parseDocumentAuditLogData } from '../../utils/document-audit-logs';
export interface FindDocumentAuditLogsOptions {
userId: string;
userId: number;
documentId: number;
page?: number;
perPage?: number;

View File

@ -27,7 +27,7 @@ import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-token
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
export type FindDocumentsOptions = {
userId: string;
userId: number;
teamId?: number;
templateId?: number;
source?: DocumentSource;

View File

@ -10,7 +10,7 @@ import { getTeamById } from '../team/get-team';
export type GetDocumentByIdOptions = {
documentId: number;
userId: string;
userId: number;
teamId?: number;
};
@ -58,7 +58,7 @@ export const getDocumentById = async ({ documentId, userId, teamId }: GetDocumen
export type GetDocumentWhereInputOptions = {
documentId: number;
userId: string;
userId: number;
teamId?: number;
/**

View File

@ -14,7 +14,7 @@ import { getDocumentWhereInput } from './get-document-by-id';
export type GetDocumentWithDetailsByIdOptions = {
documentId: number;
userId: string;
userId: number;
teamId?: number;
};

View File

@ -170,7 +170,7 @@ type GetTeamCountsOption = {
teamEmail?: string;
senderIds?: number[];
currentUserEmail: string;
userId: string;
userId: number;
createdAt: Prisma.DocumentWhereInput['createdAt'];
currentTeamMemberRole?: TeamMemberRole;
search?: string;

View File

@ -124,7 +124,7 @@ type VerifyPasskeyOptions = {
/**
* The ID of the user who initiated the request.
*/
userId: string;
userId: number;
/**
* The secondary ID of the verification token.

View File

@ -11,7 +11,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
export type MoveDocumentToTeamOptions = {
documentId: number;
teamId: number;
userId: string;
userId: number;
requestMetadata?: RequestMetadata;
};

View File

@ -26,7 +26,7 @@ import { getDocumentWhereInput } from './get-document-by-id';
export type ResendDocumentOptions = {
documentId: number;
userId: string;
userId: number;
recipients: number[];
teamId?: number;
requestMetadata: RequestMetadata;

View File

@ -8,7 +8,7 @@ import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
export type SearchDocumentsWithKeywordOptions = {
query: string;
userId: string;
userId: number;
limit?: number;
};

View File

@ -28,7 +28,7 @@ import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
export type SendDocumentOptions = {
documentId: number;
userId: string;
userId: number;
teamId?: number;
sendEmail?: boolean;
requestMetadata?: RequestMetadata;

View File

@ -18,7 +18,7 @@ import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../t
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
export type UpdateDocumentSettingsOptions = {
userId: string;
userId: number;
teamId?: number;
documentId: number;
data: {

View File

@ -7,7 +7,7 @@ import { prisma } from '@documenso/prisma';
export type UpdateDocumentOptions = {
documentId: number;
data: Prisma.DocumentUpdateInput;
userId: string;
userId: number;
teamId?: number;
};

View File

@ -6,7 +6,7 @@ import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-
import { prisma } from '@documenso/prisma';
export type UpdateTitleOptions = {
userId: string;
userId: number;
teamId?: number;
documentId: number;
title: string;

View File

@ -16,7 +16,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
export type CreateFieldOptions = {
documentId: number;
userId: string;
userId: number;
teamId?: number;
recipientId: number;
type: FieldType;

View File

@ -7,7 +7,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
export type DeleteFieldOptions = {
fieldId: number;
documentId: number;
userId: string;
userId: number;
teamId?: number;
requestMetadata?: RequestMetadata;
};

View File

@ -6,7 +6,7 @@ import { FieldSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type GetFieldByIdOptions = {
userId: string;
userId: number;
teamId?: number;
fieldId: number;
documentId?: number;

View File

@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
export interface GetFieldsForDocumentOptions {
documentId: number;
userId: string;
userId: number;
}
export type DocumentField = Awaited<ReturnType<typeof getFieldsForDocument>>[number];

View File

@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
export interface GetFieldsForTemplateOptions {
templateId: number;
userId: string;
userId: number;
}
export const getFieldsForTemplate = async ({ templateId, userId }: GetFieldsForTemplateOptions) => {

View File

@ -30,7 +30,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
import { canRecipientFieldsBeModified } from '../../utils/recipients';
export interface SetFieldsForDocumentOptions {
userId: string;
userId: number;
documentId: number;
fields: FieldData[];
requestMetadata?: RequestMetadata;

View File

@ -19,7 +19,7 @@ import { FieldType } from '@documenso/prisma/client';
import { FieldSchema } from '@documenso/prisma/generated/zod';
export type SetFieldsForTemplateOptions = {
userId: string;
userId: number;
templateId: number;
fields: {
id?: number | null;

View File

@ -9,7 +9,7 @@ import { createDocumentAuditLogData, diffFieldChanges } from '../../utils/docume
export type UpdateFieldOptions = {
fieldId: number;
documentId: number;
userId: string;
userId: number;
teamId?: number;
recipientId?: number;
type?: FieldType;

View File

@ -6,7 +6,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
export type SetAvatarImageOptions = {
userId: string;
userId: number;
teamId?: number | null;
bytes?: string | null;
requestMetadata?: RequestMetadata;

View File

@ -14,7 +14,7 @@ type TimeConstants = typeof timeConstants & {
};
type CreateApiTokenInput = {
userId: string;
userId: number;
teamId?: number;
tokenName: string;
expiresIn: string | null;

View File

@ -3,7 +3,7 @@ import { TeamMemberRole } from '@documenso/prisma/client';
export type DeleteTokenByIdOptions = {
id: number;
userId: string;
userId: number;
teamId?: number;
};

View File

@ -3,7 +3,7 @@ import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
export type GetUserTokensOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -1,7 +1,7 @@
import { prisma } from '@documenso/prisma';
export type GetUserTokensOptions = {
userId: string;
userId: number;
};
export const getUserTokens = async ({ userId }: GetUserTokensOptions) => {

View File

@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
export type GetApiTokenByIdOptions = {
id: number;
userId: string;
userId: number;
};
export const getApiTokenById = async ({ id, userId }: GetApiTokenByIdOptions) => {

View File

@ -8,7 +8,7 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
export type DeleteRecipientOptions = {
documentId: number;
recipientId: number;
userId: string;
userId: number;
teamId?: number;
requestMetadata?: RequestMetadata;
};

View File

@ -7,7 +7,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
export type GetRecipientByIdOptions = {
recipientId: number;
userId: string;
userId: number;
teamId?: number;
};

View File

@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
export interface GetRecipientsForDocumentOptions {
documentId: number;
userId: string;
userId: number;
teamId?: number;
}

View File

@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
export interface GetRecipientsForTemplateOptions {
templateId: number;
userId: string;
userId: number;
}
export const getRecipientsForTemplate = async ({

View File

@ -34,7 +34,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export interface SetRecipientsForDocumentOptions {
userId: string;
userId: number;
teamId?: number;
documentId: number;
recipients: RecipientData[];

View File

@ -19,7 +19,7 @@ import { nanoid } from '../../universal/id';
import { createRecipientAuthOptions } from '../../utils/document-auth';
export type SetRecipientsForTemplateOptions = {
userId: string;
userId: number;
teamId?: number;
templateId: number;
recipients: {

View File

@ -20,7 +20,7 @@ export type UpdateRecipientOptions = {
role?: RecipientRole;
signingOrder?: number | null;
actionAuth?: TRecipientActionAuthTypes | null;
userId: string;
userId: number;
teamId?: number;
requestMetadata?: RequestMetadata;
};

View File

@ -11,7 +11,7 @@ export type CreateSharingIdOptions =
}
| {
documentId: number;
userId: string;
userId: number;
};
export const createOrGetShareLink = async ({ documentId, ...options }: CreateSharingIdOptions) => {

View File

@ -3,7 +3,7 @@ import { prisma } from '@documenso/prisma';
import type { TSiteSettingSchema } from './schema';
export type UpsertSiteSettingOptions = TSiteSettingSchema & {
userId: string;
userId: number;
};
export const upsertSiteSetting = async ({

View File

@ -4,7 +4,7 @@ import { prisma } from '@documenso/prisma';
import { SubscriptionStatus } from '@documenso/prisma/client';
export type GetActiveSubscriptionsByUserIdOptions = {
userId: string;
userId: number;
};
export const getActiveSubscriptionsByUserId = async ({

View File

@ -3,7 +3,7 @@
import { prisma } from '@documenso/prisma';
export type GetSubscriptionsByUserIdOptions = {
userId: string;
userId: number;
};
export const getSubscriptionsByUserId = async ({ userId }: GetSubscriptionsByUserIdOptions) => {

View File

@ -6,7 +6,7 @@ import { TeamMemberInviteStatus } from '@documenso/prisma/client';
import { jobs } from '../../jobs/client';
export type AcceptTeamInvitationOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -4,7 +4,7 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
import { prisma } from '@documenso/prisma';
export type CreateTeamBillingPortalOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -5,7 +5,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
export type CreateTeamPendingCheckoutSession = {
userId: string;
userId: number;
pendingTeamId: number;
interval: 'monthly' | 'yearly';
};

View File

@ -19,7 +19,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type CreateTeamEmailVerificationOptions = {
userId: string;
userId: number;
teamId: number;
data: {
email: string;

View File

@ -20,7 +20,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type CreateTeamMemberInvitesOptions = {
userId: string;
userId: number;
userName: string;
teamId: number;
invitations: TCreateTeamMemberInvitesMutationSchema['invitations'];

View File

@ -16,7 +16,7 @@ export type CreateTeamOptions = {
/**
* ID of the user creating the Team.
*/
userId: string;
userId: number;
/**
* Name of the team to display.

View File

@ -1,7 +1,7 @@
import { prisma } from '@documenso/prisma';
export type DeclineTeamInvitationOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -2,7 +2,7 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
import { prisma } from '@documenso/prisma';
export type DeleteTeamEmailVerificationOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -14,7 +14,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type DeleteTeamEmailOptions = {
userId: string;
userId: number;
userEmail: string;
teamId: number;
};

View File

@ -6,7 +6,7 @@ export type DeleteTeamMemberInvitationsOptions = {
/**
* The ID of the user who is initiating this action.
*/
userId: string;
userId: number;
/**
* The ID of the team to remove members from.

View File

@ -9,7 +9,7 @@ export type DeleteTeamMembersOptions = {
/**
* The ID of the user who is initiating this action.
*/
userId: string;
userId: number;
/**
* The ID of the team to remove members from.

View File

@ -1,7 +1,7 @@
import { prisma } from '@documenso/prisma';
export type DeleteTeamPendingOptions = {
userId: string;
userId: number;
pendingTeamId: number;
};

View File

@ -6,7 +6,7 @@ export type DeleteTeamTransferRequestOptions = {
/**
* The ID of the user deleting the transfer.
*/
userId: string;
userId: number;
/**
* The ID of the team whose team transfer request should be deleted.

View File

@ -17,7 +17,7 @@ import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
export type DeleteTeamOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -4,7 +4,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
export interface FindTeamInvoicesOptions {
userId: string;
userId: number;
teamId: number;
}

View File

@ -10,7 +10,7 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams';
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
export interface FindTeamMemberInvitesOptions {
userId: string;
userId: number;
teamId: number;
query?: string;
page?: number;

View File

@ -10,7 +10,7 @@ import type { FindResultResponse } from '../../types/search-params';
import { ZFindResultResponse } from '../../types/search-params';
export interface FindTeamMembersOptions {
userId: string;
userId: number;
teamId: number;
query?: string;
page?: number;

View File

@ -8,7 +8,7 @@ import { TeamPendingSchema } from '@documenso/prisma/generated/zod';
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
export interface FindTeamsPendingOptions {
userId: string;
userId: number;
query?: string;
page?: number;
perPage?: number;

View File

@ -5,7 +5,7 @@ import { Prisma } from '@documenso/prisma/client';
import type { FindResultResponse } from '../../types/search-params';
export interface FindTeamsOptions {
userId: string;
userId: number;
query?: string;
page?: number;
perPage?: number;

View File

@ -4,7 +4,7 @@ import { prisma } from '@documenso/prisma';
import { TeamMemberSchema, UserSchema } from '@documenso/prisma/generated/zod';
export type GetTeamMembersOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -5,7 +5,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
import { updateTeamPublicProfile } from './update-team-public-profile';
export type GetTeamPublicProfileOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -70,7 +70,7 @@ export const getTeamById = async ({
};
export type GetTeamByUrlOptions = {
userId: string;
userId: number;
teamUrl: string;
};

View File

@ -4,7 +4,7 @@ import { prisma } from '@documenso/prisma';
import { TeamMemberSchema, TeamSchema } from '@documenso/prisma/generated/zod';
export type GetTeamsOptions = {
userId: string;
userId: number;
};
export const ZGetTeamsResponseSchema = TeamSchema.extend({

View File

@ -8,7 +8,7 @@ export type LeaveTeamOptions = {
/**
* The ID of the user who is leaving the team.
*/
userId: string;
userId: number;
/**
* The ID of the team the user is leaving.

View File

@ -16,7 +16,7 @@ export type RequestTeamOwnershipTransferOptions = {
/**
* The ID of the user initiating the transfer.
*/
userId: string;
userId: number;
/**
* The name of the user initiating the transfer.
@ -31,7 +31,7 @@ export type RequestTeamOwnershipTransferOptions = {
/**
* The user ID of the new owner.
*/
newOwneruserId: string;
newOwnerUserId: number;
/**
* Whether to clear any current payment methods attached to the team.

View File

@ -6,7 +6,7 @@ import { prisma } from '@documenso/prisma';
import { sendTeamEmailVerificationEmail } from './create-team-email-verification';
export type ResendTeamMemberInvitationOptions = {
userId: string;
userId: number;
teamId: number;
};

View File

@ -8,7 +8,7 @@ export type ResendTeamMemberInvitationOptions = {
/**
* The ID of the user who is initiating this action.
*/
userId: string;
userId: number;
/**
* The name of the user who is initiating this action.

View File

@ -5,7 +5,7 @@ import { TeamMemberRole } from '@documenso/prisma/client';
import { TeamGlobalSettingsSchema } from '@documenso/prisma/generated/zod';
export type UpdateTeamBrandingSettingsOptions = {
userId: string;
userId: number;
teamId: number;
settings: {

View File

@ -8,7 +8,7 @@ import { TeamGlobalSettingsSchema } from '@documenso/prisma/generated/zod';
import type { SupportedLanguageCodes } from '../../constants/i18n';
export type UpdateTeamDocumentSettingsOptions = {
userId: string;
userId: number;
teamId: number;
settings: {

View File

@ -3,7 +3,7 @@ import { prisma } from '@documenso/prisma';
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams';
export type UpdateTeamEmailOptions = {
userId: string;
userId: number;
teamId: number;
data: {
name: string;

View File

@ -5,7 +5,7 @@ import { prisma } from '@documenso/prisma';
import type { TeamMemberRole } from '@documenso/prisma/client';
export type UpdateTeamMemberOptions = {
userId: string;
userId: number;
teamId: number;
teamMemberId: number;
data: {

View File

@ -1,7 +1,7 @@
import { prisma } from '@documenso/prisma';
export type UpdatePublicProfileOptions = {
userId: string;
userId: number;
teamId: number;
data: {
bio?: string;

View File

@ -6,7 +6,7 @@ import { prisma } from '@documenso/prisma';
import { Prisma } from '@documenso/prisma/client';
export type UpdateTeamOptions = {
userId: string;
userId: number;
teamId: number;
data: {
name?: string;

View File

@ -4,7 +4,7 @@ import { DocumentSource, type RecipientRole } from '@documenso/prisma/client';
export type CreateDocumentFromTemplateLegacyOptions = {
templateId: number;
userId: string;
userId: number;
teamId?: number;
recipients?: {
name?: string;

Some files were not shown because too many files have changed in this diff Show More