Files
sign/apps/remix/app/components/general/document-signing/document-signing-name-field.tsx

233 lines
6.9 KiB
TypeScript
Raw Normal View History

2025-01-02 15:33:37 +11:00
import { useState } from 'react';
2023-08-17 19:56:18 +10:00
2025-01-02 15:33:37 +11:00
import { msg } from '@lingui/core/macro';
2024-08-27 20:34:39 +09:00
import { useLingui } from '@lingui/react';
2025-01-02 15:33:37 +11:00
import { Trans } from '@lingui/react/macro';
import { type Recipient } from '@prisma/client';
2023-08-17 19:56:18 +10:00
import { Loader } from 'lucide-react';
2025-01-02 15:33:37 +11:00
import { useRevalidator } from 'react-router';
2023-08-17 19:56:18 +10:00
fix: update document flow fetch logic (#1039) ## Description **Fixes issues with mismatching state between document steps.** For example, editing a recipient and proceeding to the next step may not display the updated recipient. And going back will display the old recipient instead of the updated values. **This PR also improves mutation and query speeds by adding logic to bypass query invalidation.** ```ts export const trpc = createTRPCReact<AppRouter>({ unstable_overrides: { useMutation: { async onSuccess(opts) { await opts.originalFn(); // This forces mutations to wait for all the queries on the page to reload, and in // this case one of the queries is `searchDocument` for the command overlay, which // on average takes ~500ms. This means that every single mutation must wait for this. await opts.queryClient.invalidateQueries(); }, }, }, }); ``` I've added workarounds to allow us to bypass things such as batching and invalidating queries. But I think we should instead remove this and update all the mutations where a query is required for a more optimised system. ## Example benchmarks Using stg-app vs this preview there's an average 50% speed increase across mutations. **Set signer step:** Average old speed: ~1100ms Average new speed: ~550ms **Set recipient step:** Average old speed: ~1200ms Average new speed: ~600ms **Set fields step:** Average old speed: ~1200ms Average new speed: ~600ms ## Related Issue This will resolve #470 ## Changes Made - Added ability to skip batch queries - Added a state to store the required document data. - Refetch the data between steps if/when required - Optimise mutations and queries ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have followed the project's coding style guidelines. --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-03-26 21:12:41 +08:00
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
2024-03-28 13:13:29 +08:00
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
2023-08-17 19:56:18 +10:00
import { trpc } from '@documenso/trpc/react';
import type {
TRemovedSignedFieldWithTokenMutationSchema,
TSignFieldWithTokenMutationSchema,
} from '@documenso/trpc/server/field-router/schema';
2023-08-17 19:56:18 +10:00
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
2025-01-02 15:33:37 +11:00
import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider';
import { DocumentSigningFieldContainer } from './document-signing-field-container';
import { useRequiredDocumentSigningContext } from './document-signing-provider';
2023-08-17 19:56:18 +10:00
2025-01-02 15:33:37 +11:00
export type DocumentSigningNameFieldProps = {
2023-08-17 19:56:18 +10:00
field: FieldWithSignature;
recipient: Recipient;
onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void;
onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void;
2023-08-17 19:56:18 +10:00
};
2025-01-02 15:33:37 +11:00
export const DocumentSigningNameField = ({
field,
recipient,
onSignField,
onUnsignField,
}: DocumentSigningNameFieldProps) => {
2024-08-27 20:34:39 +09:00
const { _ } = useLingui();
2023-08-17 19:56:18 +10:00
const { toast } = useToast();
2025-01-02 15:33:37 +11:00
const { revalidate } = useRevalidator();
2023-08-17 19:56:18 +10:00
const { fullName: providedFullName, setFullName: setProvidedFullName } =
2025-01-02 15:33:37 +11:00
useRequiredDocumentSigningContext();
2024-03-28 13:13:29 +08:00
2025-01-02 15:33:37 +11:00
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
2023-08-17 19:56:18 +10:00
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
fix: update document flow fetch logic (#1039) ## Description **Fixes issues with mismatching state between document steps.** For example, editing a recipient and proceeding to the next step may not display the updated recipient. And going back will display the old recipient instead of the updated values. **This PR also improves mutation and query speeds by adding logic to bypass query invalidation.** ```ts export const trpc = createTRPCReact<AppRouter>({ unstable_overrides: { useMutation: { async onSuccess(opts) { await opts.originalFn(); // This forces mutations to wait for all the queries on the page to reload, and in // this case one of the queries is `searchDocument` for the command overlay, which // on average takes ~500ms. This means that every single mutation must wait for this. await opts.queryClient.invalidateQueries(); }, }, }, }); ``` I've added workarounds to allow us to bypass things such as batching and invalidating queries. But I think we should instead remove this and update all the mutations where a query is required for a more optimised system. ## Example benchmarks Using stg-app vs this preview there's an average 50% speed increase across mutations. **Set signer step:** Average old speed: ~1100ms Average new speed: ~550ms **Set recipient step:** Average old speed: ~1200ms Average new speed: ~600ms **Set fields step:** Average old speed: ~1200ms Average new speed: ~600ms ## Related Issue This will resolve #470 ## Changes Made - Added ability to skip batch queries - Added a state to store the required document data. - Refetch the data between steps if/when required - Optimise mutations and queries ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have followed the project's coding style guidelines. --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-03-26 21:12:41 +08:00
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
2023-08-17 19:56:18 +10:00
const {
mutateAsync: removeSignedFieldWithToken,
isPending: isRemoveSignedFieldWithTokenLoading,
fix: update document flow fetch logic (#1039) ## Description **Fixes issues with mismatching state between document steps.** For example, editing a recipient and proceeding to the next step may not display the updated recipient. And going back will display the old recipient instead of the updated values. **This PR also improves mutation and query speeds by adding logic to bypass query invalidation.** ```ts export const trpc = createTRPCReact<AppRouter>({ unstable_overrides: { useMutation: { async onSuccess(opts) { await opts.originalFn(); // This forces mutations to wait for all the queries on the page to reload, and in // this case one of the queries is `searchDocument` for the command overlay, which // on average takes ~500ms. This means that every single mutation must wait for this. await opts.queryClient.invalidateQueries(); }, }, }, }); ``` I've added workarounds to allow us to bypass things such as batching and invalidating queries. But I think we should instead remove this and update all the mutations where a query is required for a more optimised system. ## Example benchmarks Using stg-app vs this preview there's an average 50% speed increase across mutations. **Set signer step:** Average old speed: ~1100ms Average new speed: ~550ms **Set recipient step:** Average old speed: ~1200ms Average new speed: ~600ms **Set fields step:** Average old speed: ~1200ms Average new speed: ~600ms ## Related Issue This will resolve #470 ## Changes Made - Added ability to skip batch queries - Added a state to store the required document data. - Refetch the data between steps if/when required - Optimise mutations and queries ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have followed the project's coding style guidelines. --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-03-26 21:12:41 +08:00
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
2023-08-17 19:56:18 +10:00
2025-01-02 15:33:37 +11:00
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
2023-08-17 19:56:18 +10:00
const [showFullNameModal, setShowFullNameModal] = useState(false);
const [localFullName, setLocalFullName] = useState('');
2024-03-28 13:13:29 +08:00
const onPreSign = () => {
if (!providedFullName) {
setShowFullNameModal(true);
return false;
}
return true;
};
/**
* When the user clicks the sign button in the dialog where they enter their full name.
*/
const onDialogSignClick = () => {
setShowFullNameModal(false);
setProvidedFullName(localFullName);
void executeActionAuthProcedure({
onReauthFormSubmit: async (authOptions) => await onSign(authOptions, localFullName),
actionTarget: field.type,
});
};
const onSign = async (authOptions?: TRecipientActionAuth, name?: string) => {
2023-08-17 19:56:18 +10:00
try {
2024-03-28 13:13:29 +08:00
const value = name || providedFullName;
if (!value) {
2023-08-17 19:56:18 +10:00
setShowFullNameModal(true);
return;
}
const payload: TSignFieldWithTokenMutationSchema = {
2023-08-17 19:56:18 +10:00
token: recipient.token,
fieldId: field.id,
2024-03-28 13:13:29 +08:00
value,
2023-08-17 19:56:18 +10:00
isBase64: false,
2024-03-28 13:13:29 +08:00
authOptions,
};
if (onSignField) {
await onSignField(payload);
return;
}
await signFieldWithToken(payload);
2023-08-17 19:56:18 +10:00
2025-01-02 15:33:37 +11:00
await revalidate();
2023-08-17 19:56:18 +10:00
} catch (err) {
2024-03-28 13:13:29 +08:00
const error = AppError.parseError(err);
if (error.code === AppErrorCode.UNAUTHORIZED) {
throw error;
}
2023-08-17 19:56:18 +10:00
console.error(err);
toast({
2024-08-27 20:34:39 +09:00
title: _(msg`Error`),
description: _(msg`An error occurred while signing the document.`),
2023-08-17 19:56:18 +10:00
variant: 'destructive',
});
}
};
const onRemove = async () => {
try {
const payload: TRemovedSignedFieldWithTokenMutationSchema = {
2023-08-17 19:56:18 +10:00
token: recipient.token,
fieldId: field.id,
};
if (onUnsignField) {
await onUnsignField(payload);
return;
}
await removeSignedFieldWithToken(payload);
2023-08-17 19:56:18 +10:00
2025-01-02 15:33:37 +11:00
await revalidate();
2023-08-17 19:56:18 +10:00
} catch (err) {
console.error(err);
toast({
2024-08-27 20:34:39 +09:00
title: _(msg`Error`),
description: _(msg`An error occurred while removing the signature.`),
2023-08-17 19:56:18 +10:00
variant: 'destructive',
});
}
};
return (
2025-01-02 15:33:37 +11:00
<DocumentSigningFieldContainer
2024-03-28 13:13:29 +08:00
field={field}
onPreSign={onPreSign}
onSign={onSign}
onRemove={onRemove}
type="Name"
>
2023-08-17 19:56:18 +10:00
{isLoading && (
2023-09-23 13:25:39 +10:00
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
2023-08-17 19:56:18 +10:00
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
</div>
)}
{!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
2024-08-27 20:34:39 +09:00
<Trans>Name</Trans>
</p>
2023-08-17 19:56:18 +10:00
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{field.customText}
</p>
)}
2023-08-17 19:56:18 +10:00
<Dialog open={showFullNameModal} onOpenChange={setShowFullNameModal}>
<DialogContent>
<DialogTitle>
2024-08-27 20:34:39 +09:00
<Trans>
Sign as
<div>
{recipient.name} <div className="text-muted-foreground">({recipient.email})</div>
</div>
</Trans>
2023-08-17 19:56:18 +10:00
</DialogTitle>
<div>
2024-08-27 20:34:39 +09:00
<Label htmlFor="signature">
<Trans>Full Name</Trans>
</Label>
2023-08-17 19:56:18 +10:00
<Input
type="text"
className="mt-2"
value={localFullName}
onChange={(e) => setLocalFullName(e.target.value.trimStart())}
2023-08-17 19:56:18 +10:00
/>
</div>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
2023-08-17 19:56:18 +10:00
variant="secondary"
onClick={() => {
setShowFullNameModal(false);
setLocalFullName('');
}}
>
2024-08-27 20:34:39 +09:00
<Trans>Cancel</Trans>
2023-08-17 19:56:18 +10:00
</Button>
<Button
type="button"
className="flex-1"
disabled={!localFullName}
2024-03-28 13:13:29 +08:00
onClick={() => onDialogSignClick()}
2023-08-17 19:56:18 +10:00
>
2024-08-27 20:34:39 +09:00
<Trans>Sign</Trans>
2023-08-17 19:56:18 +10:00
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
2025-01-02 15:33:37 +11:00
</DocumentSigningFieldContainer>
2023-08-17 19:56:18 +10:00
);
};