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),