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 5d9fe78aa..9b051bbad 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx
@@ -1,18 +1,15 @@
'use client';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import {
- type DocumentData,
- type DocumentMeta,
- DocumentStatus,
- type Field,
- type Recipient,
- type User,
-} from '@documenso/prisma/client';
-import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
+ DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
+ SKIP_QUERY_BATCH_META,
+} from '@documenso/lib/constants/trpc';
+import { DocumentStatus } from '@documenso/prisma/client';
+import type { DocumentWithDetails } from '@documenso/prisma/types/document';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
@@ -34,12 +31,7 @@ import { useOptionalCurrentTeam } from '~/providers/team';
export type EditDocumentFormProps = {
className?: string;
- user: User;
- document: DocumentWithData;
- recipients: Recipient[];
- documentMeta: DocumentMeta | null;
- fields: Field[];
- documentData: DocumentData;
+ initialDocument: DocumentWithDetails;
documentRootPath: string;
};
@@ -48,12 +40,7 @@ const EditDocumentSteps: EditDocumentStep[] = ['title', 'signers', 'fields', 'su
export const EditDocumentForm = ({
className,
- document,
- recipients,
- fields,
- documentMeta,
- user: _user,
- documentData,
+ initialDocument,
documentRootPath,
}: EditDocumentFormProps) => {
const { toast } = useToast();
@@ -62,10 +49,74 @@ export const EditDocumentForm = ({
const searchParams = useSearchParams();
const team = useOptionalCurrentTeam();
- const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation();
- const { mutateAsync: addFields } = trpc.field.addFields.useMutation();
- const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation();
- const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation();
+ const utils = trpc.useUtils();
+
+ const { data: document, refetch: refetchDocument } =
+ trpc.document.getDocumentWithDetailsById.useQuery(
+ {
+ id: initialDocument.id,
+ teamId: team?.id,
+ },
+ {
+ initialData: initialDocument,
+ ...SKIP_QUERY_BATCH_META,
+ },
+ );
+
+ const { Recipient: recipients, Field: fields } = document;
+
+ const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation({
+ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
+ onSuccess: (newData) => {
+ utils.document.getDocumentWithDetailsById.setData(
+ {
+ id: initialDocument.id,
+ teamId: team?.id,
+ },
+ (oldData) => ({ ...(oldData || initialDocument), ...newData }),
+ );
+ },
+ });
+
+ const { mutateAsync: addFields } = trpc.field.addFields.useMutation({
+ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
+ onSuccess: (newFields) => {
+ utils.document.getDocumentWithDetailsById.setData(
+ {
+ id: initialDocument.id,
+ teamId: team?.id,
+ },
+ (oldData) => ({ ...(oldData || initialDocument), Field: newFields }),
+ );
+ },
+ });
+
+ const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
+ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
+ onSuccess: (newRecipients) => {
+ utils.document.getDocumentWithDetailsById.setData(
+ {
+ id: initialDocument.id,
+ teamId: team?.id,
+ },
+ (oldData) => ({ ...(oldData || initialDocument), Recipient: newRecipients }),
+ );
+ },
+ });
+
+ const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation({
+ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
+ onSuccess: (newData) => {
+ utils.document.getDocumentWithDetailsById.setData(
+ {
+ id: initialDocument.id,
+ teamId: team?.id,
+ },
+ (oldData) => ({ ...(oldData || initialDocument), ...newData }),
+ );
+ },
+ });
+
const { mutateAsync: setPasswordForDocument } =
trpc.document.setPasswordForDocument.useMutation();
@@ -112,13 +163,13 @@ export const EditDocumentForm = ({
const onAddTitleFormSubmit = async (data: TAddTitleFormSchema) => {
try {
- // Custom invocation server action
await addTitle({
documentId: document.id,
teamId: team?.id,
title: data.title,
});
+ // Router refresh is here to clear the router cache for when navigating to /documents.
router.refresh();
setStep('signers');
@@ -135,14 +186,15 @@ export const EditDocumentForm = ({
const onAddSignersFormSubmit = async (data: TAddSignersFormSchema) => {
try {
- // Custom invocation server action
await addSigners({
documentId: document.id,
teamId: team?.id,
signers: data.signers,
});
+ // Router refresh is here to clear the router cache for when navigating to /documents.
router.refresh();
+
setStep('fields');
} catch (err) {
console.error(err);
@@ -157,13 +209,14 @@ export const EditDocumentForm = ({
const onAddFieldsFormSubmit = async (data: TAddFieldsFormSchema) => {
try {
- // Custom invocation server action
await addFields({
documentId: document.id,
fields: data.fields,
});
+ // Router refresh is here to clear the router cache for when navigating to /documents.
router.refresh();
+
setStep('subject');
} catch (err) {
console.error(err);
@@ -219,6 +272,15 @@ export const EditDocumentForm = ({
const currentDocumentFlow = documentFlow[step];
+ /**
+ * Refresh the data in the background when steps change.
+ */
+ useEffect(() => {
+ void refetchDocument();
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [step]);
+
return (
diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx
index 69122312e..c18337641 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx
@@ -5,9 +5,7 @@ import { ChevronLeft, Users2 } from 'lucide-react';
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
-import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
-import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
-import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
+import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { symmetricDecrypt } from '@documenso/lib/universal/crypto';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import type { Team } from '@documenso/prisma/client';
@@ -37,13 +35,13 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
const { user } = await getRequiredServerComponentSession();
- const document = await getDocumentById({
+ const document = await getDocumentWithDetailsById({
id: documentId,
userId: user.id,
teamId: team?.id,
}).catch(() => null);
- if (!document || !document.documentData) {
+ if (!document) {
redirect(documentRootPath);
}
@@ -51,7 +49,7 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
redirect(`${documentRootPath}/${documentId}`);
}
- const { documentData, documentMeta } = document;
+ const { documentMeta, Recipient: recipients } = document;
if (documentMeta?.password) {
const key = DOCUMENSO_ENCRYPTION_KEY;
@@ -70,18 +68,6 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
documentMeta.password = securePassword;
}
- const [recipients, fields] = await Promise.all([
- getRecipientsForDocument({
- documentId,
- userId: user.id,
- teamId: team?.id,
- }),
- getFieldsForDocument({
- documentId,
- userId: user.id,
- }),
- ]);
-
return (
@@ -109,12 +95,7 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
diff --git a/apps/web/src/app/(signing)/sign/[token]/date-field.tsx b/apps/web/src/app/(signing)/sign/[token]/date-field.tsx
index ce34a55fd..a06e7f2f9 100644
--- a/apps/web/src/app/(signing)/sign/[token]/date-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/date-field.tsx
@@ -11,6 +11,7 @@ import {
convertToLocalSystemFormat,
} from '@documenso/lib/constants/date-formats';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
+import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
@@ -38,12 +39,12 @@ export const DateField = ({
const [isPending, startTransition] = useTransition();
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
- trpc.field.signFieldWithToken.useMutation();
+ trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const {
mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
- } = trpc.field.removeSignedFieldWithToken.useMutation();
+ } = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
diff --git a/apps/web/src/app/(signing)/sign/[token]/email-field.tsx b/apps/web/src/app/(signing)/sign/[token]/email-field.tsx
index 4d52ca50a..d81116c21 100644
--- a/apps/web/src/app/(signing)/sign/[token]/email-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/email-field.tsx
@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
+import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
@@ -29,12 +30,12 @@ export const EmailField = ({ field, recipient }: EmailFieldProps) => {
const [isPending, startTransition] = useTransition();
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
- trpc.field.signFieldWithToken.useMutation();
+ trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const {
mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
- } = trpc.field.removeSignedFieldWithToken.useMutation();
+ } = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
diff --git a/apps/web/src/app/(signing)/sign/[token]/name-field.tsx b/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
index 44de2fc36..9fd72da2d 100644
--- a/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
+import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
@@ -34,12 +35,12 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
const [isPending, startTransition] = useTransition();
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
- trpc.field.signFieldWithToken.useMutation();
+ trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const {
mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
- } = trpc.field.removeSignedFieldWithToken.useMutation();
+ } = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
diff --git a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
index 220d3084a..ed0e0adcd 100644
--- a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
+import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
@@ -35,12 +36,12 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
const [isPending, startTransition] = useTransition();
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
- trpc.field.signFieldWithToken.useMutation();
+ trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const {
mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
- } = trpc.field.removeSignedFieldWithToken.useMutation();
+ } = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const { Signature: signature } = field;
diff --git a/apps/web/src/app/(signing)/sign/[token]/text-field.tsx b/apps/web/src/app/(signing)/sign/[token]/text-field.tsx
index 0b91fa283..4e444a09e 100644
--- a/apps/web/src/app/(signing)/sign/[token]/text-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/text-field.tsx
@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
+import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
@@ -30,12 +31,12 @@ export const TextField = ({ field, recipient }: TextFieldProps) => {
const [isPending, startTransition] = useTransition();
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
- trpc.field.signFieldWithToken.useMutation();
+ trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const {
mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
- } = trpc.field.removeSignedFieldWithToken.useMutation();
+ } = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
diff --git a/apps/web/src/components/(dashboard)/common/command-menu.tsx b/apps/web/src/components/(dashboard)/common/command-menu.tsx
index 3fe42a4c4..bdc6c2064 100644
--- a/apps/web/src/components/(dashboard)/common/command-menu.tsx
+++ b/apps/web/src/components/(dashboard)/common/command-menu.tsx
@@ -14,6 +14,10 @@ import {
SETTINGS_PAGE_SHORTCUT,
TEMPLATES_PAGE_SHORTCUT,
} from '@documenso/lib/constants/keyboard-shortcuts';
+import {
+ DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
+ SKIP_QUERY_BATCH_META,
+} from '@documenso/lib/constants/trpc';
import type { Document, Recipient } from '@documenso/prisma/client';
import { trpc as trpcReact } from '@documenso/trpc/react';
import {
@@ -82,6 +86,10 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
},
{
keepPreviousData: true,
+ // Do not batch this due to relatively long request time compared to
+ // other queries which are generally batched with this.
+ ...SKIP_QUERY_BATCH_META,
+ ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
},
);
diff --git a/packages/lib/constants/trpc.ts b/packages/lib/constants/trpc.ts
new file mode 100644
index 000000000..0ff76cc21
--- /dev/null
+++ b/packages/lib/constants/trpc.ts
@@ -0,0 +1,25 @@
+/**
+ * For TRPC useQueries that should not be batched with other queries.
+ */
+export const SKIP_QUERY_BATCH_META = {
+ trpc: {
+ context: {
+ skipBatch: true,
+ },
+ },
+};
+
+/**
+ * For TRPC useQueries and useMutations to adjust the logic on when query invalidation
+ * should occur.
+ *
+ * When used in:
+ * - useQuery: Will not invalidate the given query when a mutation occurs.
+ * - useMutation: Will not trigger invalidation on all queries when mutation succeeds.
+ *
+ */
+export const DO_NOT_INVALIDATE_QUERY_ON_MUTATION = {
+ meta: {
+ doNotInvalidateQueryOnMutation: true,
+ },
+};
diff --git a/packages/lib/server-only/document/get-document-with-details-by-id.ts b/packages/lib/server-only/document/get-document-with-details-by-id.ts
new file mode 100644
index 000000000..af30e1f33
--- /dev/null
+++ b/packages/lib/server-only/document/get-document-with-details-by-id.ts
@@ -0,0 +1,32 @@
+import { prisma } from '@documenso/prisma';
+import type { DocumentWithDetails } from '@documenso/prisma/types/document';
+
+import { getDocumentWhereInput } from './get-document-by-id';
+
+export type GetDocumentWithDetailsByIdOptions = {
+ id: number;
+ userId: number;
+ teamId?: number;
+};
+
+export const getDocumentWithDetailsById = async ({
+ id,
+ userId,
+ teamId,
+}: GetDocumentWithDetailsByIdOptions): Promise => {
+ const documentWhereInput = await getDocumentWhereInput({
+ documentId: id,
+ userId,
+ teamId,
+ });
+
+ return await prisma.document.findFirstOrThrow({
+ where: documentWhereInput,
+ include: {
+ documentData: true,
+ documentMeta: true,
+ Recipient: true,
+ Field: true,
+ },
+ });
+};
diff --git a/packages/lib/server-only/field/set-fields-for-document.ts b/packages/lib/server-only/field/set-fields-for-document.ts
index 7916de554..a5b1cfd8b 100644
--- a/packages/lib/server-only/field/set-fields-for-document.ts
+++ b/packages/lib/server-only/field/set-fields-for-document.ts
@@ -5,7 +5,7 @@ import {
diffFieldChanges,
} from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
-import type { FieldType } from '@documenso/prisma/client';
+import type { Field, FieldType } from '@documenso/prisma/client';
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
export interface SetFieldsForDocumentOptions {
@@ -29,7 +29,7 @@ export const setFieldsForDocument = async ({
documentId,
fields,
requestMetadata,
-}: SetFieldsForDocumentOptions) => {
+}: SetFieldsForDocumentOptions): Promise => {
const document = await prisma.document.findFirst({
where: {
id: documentId,
@@ -99,7 +99,7 @@ export const setFieldsForDocument = async ({
});
const persistedFields = await prisma.$transaction(async (tx) => {
- await Promise.all(
+ return await Promise.all(
linkedFields.map(async (field) => {
const fieldSignerEmail = field.signerEmail.toLowerCase();
@@ -218,5 +218,13 @@ export const setFieldsForDocument = async ({
});
}
- return persistedFields;
+ // Filter out fields that have been removed or have been updated.
+ const filteredFields = existingFields.filter((field) => {
+ const isRemoved = removedFields.find((removedField) => removedField.id === field.id);
+ const isUpdated = persistedFields.find((persistedField) => persistedField.id === field.id);
+
+ return !isRemoved && !isUpdated;
+ });
+
+ return [...filteredFields, ...persistedFields];
};
diff --git a/packages/lib/server-only/recipient/set-recipients-for-document.ts b/packages/lib/server-only/recipient/set-recipients-for-document.ts
index 2505e5261..f9f8426bc 100644
--- a/packages/lib/server-only/recipient/set-recipients-for-document.ts
+++ b/packages/lib/server-only/recipient/set-recipients-for-document.ts
@@ -6,6 +6,7 @@ import {
diffRecipientChanges,
} from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
+import type { Recipient } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client';
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
@@ -28,7 +29,7 @@ export const setRecipientsForDocument = async ({
documentId,
recipients,
requestMetadata,
-}: SetRecipientsForDocumentOptions) => {
+}: SetRecipientsForDocumentOptions): Promise => {
const document = await prisma.document.findFirst({
where: {
id: documentId,
@@ -226,5 +227,17 @@ export const setRecipientsForDocument = async ({
});
}
- return persistedRecipients;
+ // Filter out recipients that have been removed or have been updated.
+ const filteredRecipients: Recipient[] = existingRecipients.filter((recipient) => {
+ const isRemoved = removedRecipients.find(
+ (removedRecipient) => removedRecipient.id === recipient.id,
+ );
+ const isUpdated = persistedRecipients.find(
+ (persistedRecipient) => persistedRecipient.id === recipient.id,
+ );
+
+ return !isRemoved && !isUpdated;
+ });
+
+ return [...filteredRecipients, ...persistedRecipients];
};
diff --git a/packages/prisma/types/document.ts b/packages/prisma/types/document.ts
index 35a6a33b5..5bbc53b55 100644
--- a/packages/prisma/types/document.ts
+++ b/packages/prisma/types/document.ts
@@ -1,4 +1,10 @@
-import { Document, Recipient } from '@documenso/prisma/client';
+import type {
+ Document,
+ DocumentData,
+ DocumentMeta,
+ Field,
+ Recipient,
+} from '@documenso/prisma/client';
export type DocumentWithRecipientAndSender = Omit & {
recipient: Recipient;
@@ -10,3 +16,10 @@ export type DocumentWithRecipientAndSender = Omit & {
subject: string;
description: string;
};
+
+export type DocumentWithDetails = Document & {
+ documentData: DocumentData;
+ documentMeta: DocumentMeta | null;
+ Recipient: Recipient[];
+ Field: Field[];
+};
diff --git a/packages/trpc/client/index.ts b/packages/trpc/client/index.ts
index 39e1ed511..a91021f97 100644
--- a/packages/trpc/client/index.ts
+++ b/packages/trpc/client/index.ts
@@ -1,16 +1,22 @@
-import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
+import { createTRPCProxyClient, httpBatchLink, httpLink, splitLink } from '@trpc/client';
import SuperJSON from 'superjson';
import { getBaseUrl } from '@documenso/lib/universal/get-base-url';
-import { AppRouter } from '../server/router';
+import type { AppRouter } from '../server/router';
export const trpc = createTRPCProxyClient({
transformer: SuperJSON,
links: [
- httpBatchLink({
- url: `${getBaseUrl()}/api/trpc`,
+ splitLink({
+ condition: (op) => op.context.skipBatch === true,
+ true: httpLink({
+ url: `${getBaseUrl()}/api/trpc`,
+ }),
+ false: httpBatchLink({
+ url: `${getBaseUrl()}/api/trpc`,
+ }),
}),
],
});
diff --git a/packages/trpc/react/index.tsx b/packages/trpc/react/index.tsx
index f1ac7afb9..0a6028b08 100644
--- a/packages/trpc/react/index.tsx
+++ b/packages/trpc/react/index.tsx
@@ -4,7 +4,7 @@ import { useState } from 'react';
import type { QueryClientConfig } from '@tanstack/react-query';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { httpBatchLink } from '@trpc/client';
+import { httpBatchLink, httpLink, splitLink } from '@trpc/client';
import { createTRPCReact } from '@trpc/react-query';
import SuperJSON from 'superjson';
@@ -12,12 +12,22 @@ import { getBaseUrl } from '@documenso/lib/universal/get-base-url';
import type { AppRouter } from '../server/router';
+export { getQueryKey } from '@trpc/react-query';
+
export const trpc = createTRPCReact({
- unstable_overrides: {
+ overrides: {
useMutation: {
async onSuccess(opts) {
await opts.originalFn();
- await opts.queryClient.invalidateQueries();
+
+ if (opts.meta.doNotInvalidateQueryOnMutation) {
+ return;
+ }
+
+ // Invalidate all queries besides ones that specify not to in the meta data.
+ await opts.queryClient.invalidateQueries({
+ predicate: (query) => !query?.meta?.doNotInvalidateQueryOnMutation,
+ });
},
},
},
@@ -55,8 +65,14 @@ export function TrpcProvider({ children }: TrpcProviderProps) {
transformer: SuperJSON,
links: [
- httpBatchLink({
- url: `${getBaseUrl()}/api/trpc`,
+ splitLink({
+ condition: (op) => op.context.skipBatch === true,
+ true: httpLink({
+ url: `${getBaseUrl()}/api/trpc`,
+ }),
+ false: httpBatchLink({
+ url: `${getBaseUrl()}/api/trpc`,
+ }),
}),
],
}),
diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts
index 26b547ac9..70cf15291 100644
--- a/packages/trpc/server/document-router/router.ts
+++ b/packages/trpc/server/document-router/router.ts
@@ -9,6 +9,7 @@ import { duplicateDocumentById } from '@documenso/lib/server-only/document/dupli
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
+import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
@@ -23,6 +24,7 @@ import {
ZFindDocumentAuditLogsQuerySchema,
ZGetDocumentByIdQuerySchema,
ZGetDocumentByTokenQuerySchema,
+ ZGetDocumentWithDetailsByIdQuerySchema,
ZResendDocumentMutationSchema,
ZSearchDocumentsMutationSchema,
ZSendDocumentMutationSchema,
@@ -66,6 +68,24 @@ export const documentRouter = router({
}
}),
+ getDocumentWithDetailsById: authenticatedProcedure
+ .input(ZGetDocumentWithDetailsByIdQuerySchema)
+ .query(async ({ input, ctx }) => {
+ try {
+ return await getDocumentWithDetailsById({
+ ...input,
+ userId: ctx.user.id,
+ });
+ } catch (err) {
+ console.error(err);
+
+ throw new TRPCError({
+ code: 'BAD_REQUEST',
+ message: 'We were unable to find this document. Please try again later.',
+ });
+ }
+ }),
+
createDocument: authenticatedProcedure
.input(ZCreateDocumentMutationSchema)
.mutation(async ({ input, ctx }) => {
diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts
index 34ddf1a5c..065552ee2 100644
--- a/packages/trpc/server/document-router/schema.ts
+++ b/packages/trpc/server/document-router/schema.ts
@@ -29,6 +29,15 @@ export const ZGetDocumentByTokenQuerySchema = z.object({
export type TGetDocumentByTokenQuerySchema = z.infer;
+export const ZGetDocumentWithDetailsByIdQuerySchema = z.object({
+ id: z.number().min(1),
+ teamId: z.number().min(1).optional(),
+});
+
+export type TGetDocumentWithDetailsByIdQuerySchema = z.infer<
+ typeof ZGetDocumentWithDetailsByIdQuerySchema
+>;
+
export const ZCreateDocumentMutationSchema = z.object({
title: z.string().min(1),
documentDataId: z.string().min(1),