mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
feat: migrate nextjs to rr7
This commit is contained in:
@ -1,11 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import { FieldType, RecipientRole, SendStatus } from '@prisma/client';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -31,8 +30,6 @@ import {
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
@ -64,13 +61,6 @@ import { Form, FormControl, FormField, FormItem, FormLabel } from '../form/form'
|
||||
import { useStep } from '../stepper';
|
||||
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
||||
|
||||
const fontCaveat = Caveat({
|
||||
weight: ['500'],
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
variable: '--font-caveat',
|
||||
});
|
||||
|
||||
const MIN_HEIGHT_PX = 12;
|
||||
const MIN_WIDTH_PX = 36;
|
||||
|
||||
@ -438,6 +428,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
VIEWER: [],
|
||||
SIGNER: [],
|
||||
APPROVER: [],
|
||||
ASSISTANT: [],
|
||||
};
|
||||
|
||||
recipients.forEach((recipient) => {
|
||||
@ -447,10 +438,25 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
return recipientsByRole;
|
||||
}, [recipients]);
|
||||
|
||||
useEffect(() => {
|
||||
const recipientsByRoleToDisplay = recipients.filter(
|
||||
(recipient) =>
|
||||
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
|
||||
);
|
||||
|
||||
setSelectedSigner(
|
||||
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ??
|
||||
recipientsByRoleToDisplay[0],
|
||||
);
|
||||
}, [recipients]);
|
||||
|
||||
const recipientsByRoleToDisplay = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return (Object.entries(recipientsByRole) as [RecipientRole, Recipient[]][]).filter(
|
||||
([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER,
|
||||
([role]) =>
|
||||
role !== RecipientRole.CC &&
|
||||
role !== RecipientRole.VIEWER &&
|
||||
role !== RecipientRole.ASSISTANT,
|
||||
);
|
||||
}, [recipientsByRole]);
|
||||
|
||||
@ -699,8 +705,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
fontCaveat.className,
|
||||
'text-muted-foreground group-data-[selected]:text-foreground font-signature flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
)}
|
||||
>
|
||||
<Trans>Signature</Trans>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { FieldType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
|
||||
export const ZAddTemplateFieldsFormSchema = z.object({
|
||||
fields: z.array(
|
||||
|
||||
@ -1,27 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { TemplateDirectLink } from '@prisma/client';
|
||||
import { DocumentSigningOrder, type Field, type Recipient, RecipientRole } from '@prisma/client';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVerticalIcon, Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { generateRecipientPlaceholder } from '@documenso/lib/utils/templates';
|
||||
import type { TemplateDirectLink } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
type Field,
|
||||
type Recipient,
|
||||
RecipientRole,
|
||||
} from '@documenso/prisma/client';
|
||||
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
||||
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
@ -29,6 +23,7 @@ import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { toast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { Checkbox } from '../checkbox';
|
||||
import {
|
||||
@ -39,6 +34,7 @@ import {
|
||||
DocumentFlowFormContainerStep,
|
||||
} from '../document-flow/document-flow-root';
|
||||
import { ShowFieldItem } from '../document-flow/show-field-item';
|
||||
import { SigningOrderConfirmation } from '../document-flow/signing-order-confirmation';
|
||||
import type { DocumentFlowStep } from '../document-flow/types';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form';
|
||||
import { useStep } from '../stepper';
|
||||
@ -71,9 +67,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
const $sensorApi = useRef<SensorAPI | null>(null);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { data: session } = useSession();
|
||||
|
||||
const user = session?.user;
|
||||
const { user } = useSession();
|
||||
|
||||
const [placeholderRecipientCount, setPlaceholderRecipientCount] = useState(() =>
|
||||
recipients.length > 1 ? recipients.length + 1 : 2,
|
||||
@ -174,8 +168,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
const onAddPlaceholderSelfRecipient = () => {
|
||||
appendSigner({
|
||||
formId: nanoid(12),
|
||||
name: user?.name ?? '',
|
||||
email: user?.email ?? '',
|
||||
name: user.name ?? '',
|
||||
email: user.email ?? '',
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
|
||||
});
|
||||
@ -213,41 +207,30 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
|
||||
const items = Array.from(watchedSigners);
|
||||
const [reorderedSigner] = items.splice(result.source.index, 1);
|
||||
|
||||
const insertIndex = result.destination.index;
|
||||
|
||||
items.splice(insertIndex, 0, reorderedSigner);
|
||||
|
||||
const updatedSigners = items.map((item, index) => ({
|
||||
...item,
|
||||
const updatedSigners = items.map((signer, index) => ({
|
||||
...signer,
|
||||
signingOrder: index + 1,
|
||||
}));
|
||||
|
||||
updatedSigners.forEach((item, index) => {
|
||||
const keys: (keyof typeof item)[] = [
|
||||
'formId',
|
||||
'nativeId',
|
||||
'email',
|
||||
'name',
|
||||
'role',
|
||||
'signingOrder',
|
||||
'actionAuth',
|
||||
];
|
||||
keys.forEach((key) => {
|
||||
form.setValue(`signers.${index}.${key}` as const, item[key]);
|
||||
});
|
||||
});
|
||||
form.setValue('signers', updatedSigners);
|
||||
|
||||
const currentLength = form.getValues('signers').length;
|
||||
if (currentLength > updatedSigners.length) {
|
||||
for (let i = updatedSigners.length; i < currentLength; i++) {
|
||||
form.unregister(`signers.${i}`);
|
||||
}
|
||||
const lastSigner = updatedSigners[updatedSigners.length - 1];
|
||||
if (lastSigner.role === RecipientRole.ASSISTANT) {
|
||||
toast({
|
||||
title: _(msg`Warning: Assistant as last signer`),
|
||||
description: _(
|
||||
msg`Having an assistant as the last signer means they will be unable to take any action as there are no subsequent signers to assist.`,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
await form.trigger('signers');
|
||||
},
|
||||
[form, watchedSigners],
|
||||
[form, watchedSigners, toast],
|
||||
);
|
||||
|
||||
const triggerDragAndDrop = useCallback(
|
||||
@ -308,26 +291,94 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
|
||||
const handleSigningOrderChange = useCallback(
|
||||
(index: number, newOrderString: string) => {
|
||||
const newOrder = parseInt(newOrderString, 10);
|
||||
|
||||
if (!newOrderString.trim()) {
|
||||
const trimmedOrderString = newOrderString.trim();
|
||||
if (!trimmedOrderString) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isNaN(newOrder)) {
|
||||
form.setValue(`signers.${index}.signingOrder`, index + 1);
|
||||
const newOrder = Number(trimmedOrderString);
|
||||
if (!Number.isInteger(newOrder) || newOrder < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newIndex = newOrder - 1;
|
||||
if (index !== newIndex) {
|
||||
updateSigningOrders(newIndex, index);
|
||||
triggerDragAndDrop(index, newIndex);
|
||||
const currentSigners = form.getValues('signers');
|
||||
const signer = currentSigners[index];
|
||||
|
||||
// Remove signer from current position and insert at new position
|
||||
const remainingSigners = currentSigners.filter((_, idx) => idx !== index);
|
||||
const newPosition = Math.min(Math.max(0, newOrder - 1), currentSigners.length - 1);
|
||||
remainingSigners.splice(newPosition, 0, signer);
|
||||
|
||||
const updatedSigners = remainingSigners.map((s, idx) => ({
|
||||
...s,
|
||||
signingOrder: idx + 1,
|
||||
}));
|
||||
|
||||
form.setValue('signers', updatedSigners);
|
||||
|
||||
if (signer.role === RecipientRole.ASSISTANT && newPosition === remainingSigners.length - 1) {
|
||||
toast({
|
||||
title: _(msg`Warning: Assistant as last signer`),
|
||||
description: _(
|
||||
msg`Having an assistant as the last signer means they will be unable to take any action as there are no subsequent signers to assist.`,
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
[form, triggerDragAndDrop, updateSigningOrders],
|
||||
[form, toast],
|
||||
);
|
||||
|
||||
const handleRoleChange = useCallback(
|
||||
(index: number, role: RecipientRole) => {
|
||||
const currentSigners = form.getValues('signers');
|
||||
const signingOrder = form.getValues('signingOrder');
|
||||
|
||||
// Handle parallel to sequential conversion for assistants
|
||||
if (role === RecipientRole.ASSISTANT && signingOrder === DocumentSigningOrder.PARALLEL) {
|
||||
form.setValue('signingOrder', DocumentSigningOrder.SEQUENTIAL);
|
||||
toast({
|
||||
title: _(msg`Signing order is enabled.`),
|
||||
description: _(msg`You cannot add assistants when signing order is disabled.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSigners = currentSigners.map((signer, idx) => ({
|
||||
...signer,
|
||||
role: idx === index ? role : signer.role,
|
||||
signingOrder: idx + 1,
|
||||
}));
|
||||
|
||||
form.setValue('signers', updatedSigners);
|
||||
|
||||
if (role === RecipientRole.ASSISTANT && index === updatedSigners.length - 1) {
|
||||
toast({
|
||||
title: _(msg`Warning: Assistant as last signer`),
|
||||
description: _(
|
||||
msg`Having an assistant as the last signer means they will be unable to take any action as there are no subsequent signers to assist.`,
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
[form, toast],
|
||||
);
|
||||
|
||||
const [showSigningOrderConfirmation, setShowSigningOrderConfirmation] = useState(false);
|
||||
|
||||
const handleSigningOrderDisable = useCallback(() => {
|
||||
setShowSigningOrderConfirmation(false);
|
||||
|
||||
const currentSigners = form.getValues('signers');
|
||||
const updatedSigners = currentSigners.map((signer) => ({
|
||||
...signer,
|
||||
role: signer.role === RecipientRole.ASSISTANT ? RecipientRole.SIGNER : signer.role,
|
||||
}));
|
||||
|
||||
form.setValue('signers', updatedSigners);
|
||||
form.setValue('signingOrder', DocumentSigningOrder.PARALLEL);
|
||||
}, [form]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentFlowFormContainerHeader
|
||||
@ -353,11 +404,19 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
{...field}
|
||||
id="signingOrder"
|
||||
checked={field.value === DocumentSigningOrder.SEQUENTIAL}
|
||||
onCheckedChange={(checked) =>
|
||||
onCheckedChange={(checked) => {
|
||||
if (
|
||||
!checked &&
|
||||
watchedSigners.some((s) => s.role === RecipientRole.ASSISTANT)
|
||||
) {
|
||||
setShowSigningOrderConfirmation(true);
|
||||
return;
|
||||
}
|
||||
|
||||
field.onChange(
|
||||
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
@ -556,7 +615,10 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
<FormControl>
|
||||
<RecipientRoleSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
onValueChange={(value) =>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
handleRoleChange(index, value as RecipientRole)
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
hideCCRecipients={isSignerDirectRecipient(signer)}
|
||||
/>
|
||||
@ -677,6 +739,12 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
onGoNextClick={() => void onFormSubmit()}
|
||||
/>
|
||||
</DocumentFlowFormContainerFooter>
|
||||
|
||||
<SigningOrderConfirmation
|
||||
open={showSigningOrderConfirmation}
|
||||
onOpenChange={setShowSigningOrderConfirmation}
|
||||
onConfirm={handleSigningOrderDisable}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
||||
import { DocumentDistributionMethod, type Field, type Recipient } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
@ -16,8 +16,6 @@ import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { DocumentDistributionMethod, type Field, type Recipient } from '@documenso/prisma/client';
|
||||
import type { TDocumentMetaDateFormat } from '@documenso/trpc/server/document-router/schema';
|
||||
import {
|
||||
DocumentGlobalAuthAccessSelect,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { DocumentVisibility } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
@ -9,14 +11,12 @@ import {
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { DocumentVisibility } from '@documenso/prisma/client';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '@documenso/trpc/server/document-router/schema';
|
||||
|
||||
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
|
||||
import { DocumentDistributionMethod } from '.prisma/client';
|
||||
|
||||
export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||
|
||||
Reference in New Issue
Block a user