feat: migrate templates and documents to envelope model

This commit is contained in:
David Nguyen
2025-09-11 18:23:38 +10:00
parent eec2307634
commit bf89bc781b
234 changed files with 8677 additions and 6054 deletions

View File

@ -3,7 +3,6 @@ import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Document } from '@prisma/client';
import { useNavigate } from 'react-router';
import { trpc } from '@documenso/trpc/react';
@ -22,10 +21,10 @@ import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type AdminDocumentDeleteDialogProps = {
document: Document;
envelopeId: string;
};
export const AdminDocumentDeleteDialog = ({ document }: AdminDocumentDeleteDialogProps) => {
export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@ -42,7 +41,7 @@ export const AdminDocumentDeleteDialog = ({ document }: AdminDocumentDeleteDialo
return;
}
await deleteDocument({ id: document.id, reason });
await deleteDocument({ id: envelopeId, reason });
toast({
title: _(msg`Document deleted`),

View File

@ -81,7 +81,7 @@ export const DocumentMoveToFolderDialog = ({
},
);
const { mutateAsync: moveDocumentToFolder } = trpc.folder.moveDocumentToFolder.useMutation();
const { mutateAsync: updateDocument } = trpc.document.update.useMutation();
useEffect(() => {
if (!open) {
@ -94,9 +94,11 @@ export const DocumentMoveToFolderDialog = ({
const onSubmit = async (data: TMoveDocumentFormSchema) => {
try {
await moveDocumentToFolder({
await updateDocument({
documentId,
folderId: data.folderId ?? null,
data: {
folderId: data.folderId ?? null,
},
});
const documentsPath = formatDocumentsPath(team.url);

View File

@ -4,15 +4,15 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { type Recipient, SigningStatus } from '@prisma/client';
import { type Recipient, SigningStatus, type Team, type User } from '@prisma/client';
import { History } from 'lucide-react';
import { useForm, useWatch } from 'react-hook-form';
import * as z from 'zod';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import type { TDocumentMany as TDocumentRow } from '@documenso/lib/types/document';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import type { Document } from '@documenso/prisma/types/document-legacy-schema';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@ -43,7 +43,11 @@ import { StackAvatar } from '../general/stack-avatar';
const FORM_ID = 'resend-email';
export type DocumentResendDialogProps = {
document: TDocumentRow;
document: Pick<Document, 'id' | 'userId' | 'teamId' | 'status'> & {
user: Pick<User, 'id' | 'name' | 'email'>;
recipients: Recipient[];
team: Pick<Team, 'id' | 'url'> | null;
};
recipients: Recipient[];
};

View File

@ -4,14 +4,14 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro';
import type { Template, TemplateDirectLink } from '@prisma/client';
import { TemplateType } from '@prisma/client';
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { CheckCircle2Icon, CircleIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { P, match } from 'ts-pattern';
import { z } from 'zod';
import { type Template } from '@documenso/prisma/types/template-legacy-schema';
import { trpc } from '@documenso/trpc/react';
import {
MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH,

View File

@ -54,7 +54,7 @@ export const TemplateCreateDialog = ({ folderId }: TemplateCreateDialogProps) =>
try {
const response = await putPdfFile(file);
const { id } = await createTemplate({
const { legacyTemplateId: id } = await createTemplate({
title: file.name,
templateDocumentDataId: response.id,
folderId: folderId,

View File

@ -1,9 +1,10 @@
import { useState } from 'react';
import { Trans } from '@lingui/react/macro';
import type { Recipient, Template, TemplateDirectLink } from '@prisma/client';
import type { Recipient, TemplateDirectLink } from '@prisma/client';
import { LinkIcon } from 'lucide-react';
import type { Template } from '@documenso/prisma/types/template-legacy-schema';
import { Button } from '@documenso/ui/primitives/button';
import { TemplateDirectLinkDialog } from '~/components/dialogs/template-direct-link-dialog';

View File

@ -3,12 +3,7 @@ import { useEffect, useMemo, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import {
type Recipient,
RecipientRole,
type Template,
type TemplateDirectLink,
} from '@prisma/client';
import { type Recipient, RecipientRole, type TemplateDirectLink } from '@prisma/client';
import { CircleDotIcon, CircleIcon, ClipboardCopyIcon, InfoIcon, LoaderIcon } from 'lucide-react';
import { Link, useRevalidator } from 'react-router';
import { P, match } from 'ts-pattern';
@ -20,6 +15,7 @@ import { DIRECT_TEMPLATE_RECIPIENT_EMAIL } from '@documenso/lib/constants/direct
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { DIRECT_TEMPLATE_DOCUMENTATION } from '@documenso/lib/constants/template';
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
import type { Template } from '@documenso/prisma/types/template-legacy-schema';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';

View File

@ -83,7 +83,7 @@ export function TemplateMoveToFolderDialog({
},
);
const { mutateAsync: moveTemplateToFolder } = trpc.folder.moveTemplateToFolder.useMutation();
const { mutateAsync: updateTemplate } = trpc.template.updateTemplate.useMutation();
useEffect(() => {
if (!isOpen) {
@ -96,9 +96,11 @@ export function TemplateMoveToFolderDialog({
const onSubmit = async (data: TMoveTemplateFormSchema) => {
try {
await moveTemplateToFolder({
await updateTemplate({
templateId,
folderId: data.folderId ?? null,
data: {
folderId: data.folderId ?? null,
},
});
toast({

View File

@ -118,6 +118,9 @@ export const ConfigureFieldsView = ({
sendStatus: signer.disabled ? SendStatus.SENT : SendStatus.NOT_SENT,
readStatus: signer.disabled ? ReadStatus.OPENED : ReadStatus.NOT_OPENED,
signingStatus: signer.disabled ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
// Todo: Envelopes - Dummy data
envelopeId: '',
}));
}, [configData.signers]);

View File

@ -15,12 +15,14 @@ import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
import { useThrottleFn } from '@documenso/lib/client-only/hooks/use-throttle-fn';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import type { DocumentField } from '@documenso/lib/server-only/field/get-fields-for-document';
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
import type { RecipientWithFields } from '@documenso/prisma/types/recipient-with-fields';
import { trpc } from '@documenso/trpc/react';
import { DocumentReadOnlyFields } from '@documenso/ui/components/document/document-read-only-fields';
import {
type DocumentField,
DocumentReadOnlyFields,
} from '@documenso/ui/components/document/document-read-only-fields';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { Button } from '@documenso/ui/primitives/button';
import { ElementVisible } from '@documenso/ui/primitives/element-visible';

View File

@ -1,6 +1,6 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { type Document, FieldType, type Passkey, type Recipient } from '@prisma/client';
import { type Envelope, FieldType, type Passkey, type Recipient } from '@prisma/client';
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth';
@ -26,9 +26,9 @@ type PasskeyData = {
export type DocumentSigningAuthContextValue = {
executeActionAuthProcedure: (_value: ExecuteActionAuthProcedureOptions) => Promise<void>;
documentAuthOptions: Document['authOptions'];
documentAuthOptions: Envelope['authOptions'];
documentAuthOption: TDocumentAuthOptions;
setDocumentAuthOptions: (_value: Document['authOptions']) => void;
setDocumentAuthOptions: (_value: Envelope['authOptions']) => void;
recipient: Recipient;
recipientAuthOption: TRecipientAuthOptions;
setRecipient: (_value: Recipient) => void;
@ -61,7 +61,7 @@ export const useRequiredDocumentSigningAuthContext = () => {
};
export interface DocumentSigningAuthProviderProps {
documentAuthOptions: Document['authOptions'];
documentAuthOptions: Envelope['authOptions'];
recipient: Recipient;
user?: SessionUser | null;
children: React.ReactNode;

View File

@ -3,12 +3,12 @@ import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import type { Document } from '@prisma/client';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { useSearchParams } from 'react-router';
import { z } from 'zod';
import type { Document } from '@documenso/prisma/types/document-legacy-schema';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {

View File

@ -4,6 +4,7 @@ import { Trans } from '@lingui/react/macro';
import type { DocumentData } from '@prisma/client';
import { DateTime } from 'luxon';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -23,6 +24,7 @@ export type DocumentCertificateQRViewProps = {
title: string;
documentData: DocumentData;
password?: string | null;
documentTeamUrl: string;
recipientCount?: number;
completedDate?: Date;
};
@ -32,29 +34,30 @@ export const DocumentCertificateQRView = ({
title,
documentData,
password,
documentTeamUrl,
recipientCount = 0,
completedDate,
}: DocumentCertificateQRViewProps) => {
const { data: documentUrl } = trpc.shareLink.getDocumentInternalUrlForQRCode.useQuery({
const { data: documentViaUser } = trpc.document.get.useQuery({
documentId,
});
const [isDialogOpen, setIsDialogOpen] = useState(() => !!documentUrl);
const [isDialogOpen, setIsDialogOpen] = useState(() => !!documentViaUser);
const formattedDate = completedDate
? DateTime.fromJSDate(completedDate).toLocaleString(DateTime.DATETIME_MED)
: '';
useEffect(() => {
if (documentUrl) {
if (documentViaUser) {
setIsDialogOpen(true);
}
}, [documentUrl]);
}, [documentViaUser]);
return (
<div className="mx-auto w-full max-w-screen-md">
{/* Dialog for internal document link */}
{documentUrl && (
{documentViaUser && (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
@ -72,7 +75,11 @@ export const DocumentCertificateQRView = ({
<DialogFooter className="flex flex-row justify-end gap-2">
<Button asChild>
<a href={documentUrl} target="_blank" rel="noopener noreferrer">
<a
href={`${formatDocumentsPath(documentTeamUrl)}/${documentViaUser.id}`}
target="_blank"
rel="noopener noreferrer"
>
<Trans>Go to document</Trans>
</a>
</Button>

View File

@ -64,7 +64,7 @@ export const DocumentDropZoneWrapper = ({ children, className }: DocumentDropZon
const response = await putPdfFile(file);
const { id } = await createDocument({
const { legacyDocumentId: id } = await createDocument({
title: file.name,
documentDataId: response.id,
timezone: userTimezone, // Note: When migrating to v2 document upload remember to pass this through as a 'userTimezone' field.

View File

@ -1,7 +1,7 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Document, Recipient, Team, User } from '@prisma/client';
import type { Recipient, Team, User } from '@prisma/client';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { CheckCircle, Download, EyeIcon, Pencil } from 'lucide-react';
import { Link } from 'react-router';
@ -11,6 +11,7 @@ import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import type { Document } from '@documenso/prisma/types/document-legacy-schema';
import { trpc as trpcClient } from '@documenso/trpc/client';
import { Button } from '@documenso/ui/primitives/button';
import { useToast } from '@documenso/ui/primitives/use-toast';

View File

@ -3,7 +3,7 @@ import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Document, Recipient, Team, User } from '@prisma/client';
import type { Recipient, Team, User } from '@prisma/client';
import { DocumentStatus } from '@prisma/client';
import {
Copy,
@ -19,6 +19,7 @@ import { Link, useNavigate } from 'react-router';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import { useSession } from '@documenso/lib/client-only/providers/session';
import type { TDocument } from '@documenso/lib/types/document';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc as trpcClient } from '@documenso/trpc/client';
@ -39,7 +40,7 @@ import { DocumentRecipientLinkCopyDialog } from '~/components/general/document/d
import { useCurrentTeam } from '~/providers/team';
export type DocumentPageViewDropdownProps = {
document: Document & {
document: TDocument & {
user: Pick<User, 'id' | 'name' | 'email'>;
recipients: Recipient[];
team: Pick<Team, 'id' | 'url'> | null;

View File

@ -3,10 +3,11 @@ import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Document, Recipient, User } from '@prisma/client';
import type { Recipient, User } from '@prisma/client';
import { DateTime } from 'luxon';
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import type { Document } from '@documenso/prisma/types/document-legacy-schema';
export type DocumentPageViewInformationProps = {
userId: number;

View File

@ -2,7 +2,7 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import type { Document, Recipient } from '@prisma/client';
import type { Recipient } from '@prisma/client';
import {
AlertTriangle,
CheckIcon,
@ -19,6 +19,7 @@ import { match } from 'ts-pattern';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { formatSigningLink } from '@documenso/lib/utils/recipients';
import type { Document } from '@documenso/prisma/types/document-legacy-schema';
import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button';
import { SignatureIcon } from '@documenso/ui/icons/signature';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';

View File

@ -75,7 +75,7 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
const response = await putPdfFile(file);
const { id } = await createDocument({
const { legacyDocumentId: id } = await createDocument({
title: file.name,
documentDataId: response.id,
timezone: userTimezone, // Note: When migrating to v2 document upload remember to pass this through as a 'userTimezone' field.

View File

@ -42,7 +42,7 @@ export const TemplateDropZoneWrapper = ({ children, className }: TemplateDropZon
const documentData = await putPdfFile(file);
const { id } = await createTemplate({
const { legacyTemplateId: id } = await createTemplate({
title: file.name,
templateDocumentDataId: documentData.id,
folderId: folderId ?? undefined,

View File

@ -3,10 +3,11 @@ import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Template, User } from '@prisma/client';
import type { User } from '@prisma/client';
import { DateTime } from 'luxon';
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import type { Template } from '@documenso/prisma/types/template-legacy-schema';
export type TemplatePageViewInformationProps = {
userId: number;

View File

@ -1,13 +1,14 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Recipient, Template } from '@prisma/client';
import type { Recipient } from '@prisma/client';
import { PenIcon, PlusIcon } from 'lucide-react';
import { Link } from 'react-router';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
import type { Template } from '@documenso/prisma/types/template-legacy-schema';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
export type TemplatePageViewRecipientsProps = {

View File

@ -2,7 +2,7 @@ import { useEffect, useMemo, useState, useTransition } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import type { Document, Role, Subscription } from '@prisma/client';
import type { Role, Subscription } from '@prisma/client';
import { Edit, Loader } from 'lucide-react';
import { Link } from 'react-router';
@ -20,7 +20,7 @@ type UserData = {
email: string;
roles: Role[];
subscriptions?: SubscriptionLite[] | null;
documents: DocumentLite[];
documentCount: number;
};
type SubscriptionLite = Pick<
@ -28,8 +28,6 @@ type SubscriptionLite = Pick<
'id' | 'status' | 'planId' | 'priceId' | 'createdAt' | 'periodEnd'
>;
type DocumentLite = Pick<Document, 'id'>;
type AdminDashboardUsersTableProps = {
users: UserData[];
totalPages: number;
@ -74,10 +72,7 @@ export const AdminDashboardUsersTable = ({
},
{
header: _(msg`Documents`),
accessorKey: 'documents',
cell: ({ row }) => {
return <div>{row.original.documents?.length}</div>;
},
accessorKey: 'documentCount',
},
{
header: '',

View File

@ -3,8 +3,7 @@ import { useMemo, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { TemplateDirectLink } from '@prisma/client';
import { TemplateType } from '@prisma/client';
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
import { EditIcon, FileIcon, LinkIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';

View File

@ -1,11 +1,12 @@
import { useState } from 'react';
import { Trans } from '@lingui/react/macro';
import type { Recipient, Template, TemplateDirectLink } from '@prisma/client';
import type { Recipient, TemplateDirectLink } from '@prisma/client';
import { Copy, Edit, FolderIcon, MoreHorizontal, Share2Icon, Trash2, Upload } from 'lucide-react';
import { Link } from 'react-router';
import { useSession } from '@documenso/lib/client-only/providers/session';
import type { Template } from '@documenso/prisma/types/template-legacy-schema';
import {
DropdownMenu,
DropdownMenuContent,