mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 09:25:08 +10:00
fix: add tests
This commit is contained in:
@@ -6,7 +6,7 @@ import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentStatus, FieldType, RecipientRole } from '@prisma/client';
|
||||
import { FileTextIcon, SparklesIcon } from 'lucide-react';
|
||||
import { Link, useRevalidator, useSearchParams } from 'react-router';
|
||||
import { useRevalidator, useSearchParams } from 'react-router';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
@@ -75,7 +75,7 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
|
||||
const scrollableContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { envelope, editorFields, relativePath, editorConfig } = useCurrentEnvelopeEditor();
|
||||
const { envelope, editorFields, navigateToStep, editorConfig } = useCurrentEnvelopeEditor();
|
||||
|
||||
const { currentEnvelopeItem } = useCurrentEnvelopeRender();
|
||||
|
||||
@@ -172,10 +172,8 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
</AlertDescription>
|
||||
</div>
|
||||
|
||||
<Button asChild variant="outline">
|
||||
<Link to={`${relativePath.editorPath}`}>
|
||||
<Trans>Add Recipients</Trans>
|
||||
</Link>
|
||||
<Button variant="outline" onClick={() => void navigateToStep('upload')}>
|
||||
<Trans>Add Recipients</Trans>
|
||||
</Button>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -141,6 +141,7 @@ export const EnvelopeEditorRecipientForm = () => {
|
||||
},
|
||||
{
|
||||
enabled: debouncedRecipientSearchQuery.length > 1,
|
||||
retry: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
+9
-7
@@ -175,7 +175,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
const { t, i18n } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { envelope, updateEnvelopeAsync, editorConfig } = useCurrentEnvelopeEditor();
|
||||
const { envelope, updateEnvelopeAsync, editorConfig, isEmbedded } = useCurrentEnvelopeEditor();
|
||||
|
||||
const { settings } = editorConfig;
|
||||
|
||||
@@ -286,11 +286,13 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
|
||||
setOpen(false);
|
||||
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`Envelope updated`,
|
||||
duration: 5000,
|
||||
});
|
||||
if (!isEmbedded) {
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`Envelope updated`,
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
@@ -348,7 +350,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
<DialogContent className="flex w-full !max-w-5xl flex-row gap-0 p-0">
|
||||
{/* Sidebar. */}
|
||||
<div className="flex w-80 flex-col border-r bg-accent/20">
|
||||
<DialogHeader className="p-6 pb-4">
|
||||
<DialogHeader className="p-6 pb-4" data-testid="envelope-editor-settings-dialog-header">
|
||||
<DialogTitle>
|
||||
<Trans>Document Settings</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { DocumentStatus } from '@prisma/client';
|
||||
import { FileWarningIcon, GripVerticalIcon, Loader2 } from 'lucide-react';
|
||||
import { X } from 'lucide-react';
|
||||
import { ErrorCode as DropzoneErrorCode, type FileRejection } from 'react-dropzone';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import {
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
} from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import type { TEditorEnvelope } from '@documenso/lib/types/envelope-editor';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { PRESIGNED_ENVELOPE_ITEM_ID_PREFIX } from '@documenso/lib/utils/embed-config';
|
||||
import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope';
|
||||
@@ -53,7 +53,7 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
const { maximumEnvelopeItemCount, remaining } = useLimits();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { envelope, setLocalEnvelope, relativePath, editorFields, editorConfig, isEmbedded } =
|
||||
const { envelope, setLocalEnvelope, editorFields, editorConfig, isEmbedded, navigateToStep } =
|
||||
useCurrentEnvelopeEditor();
|
||||
|
||||
const { envelopeItems: uploadConfig } = editorConfig;
|
||||
@@ -108,21 +108,23 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
);
|
||||
|
||||
const onFileDrop = async (files: File[]) => {
|
||||
const newUploadingFiles: (LocalFile & { file: File; data: Uint8Array<ArrayBuffer> | null })[] =
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
return {
|
||||
id: nanoid(),
|
||||
envelopeItemId: isEmbedded ? `${PRESIGNED_ENVELOPE_ITEM_ID_PREFIX}${nanoid()}` : null,
|
||||
title: file.name,
|
||||
file,
|
||||
isUploading: isEmbedded ? false : true,
|
||||
// Clone the buffer so it can be read multiple times (File.arrayBuffer() consumes the stream once)
|
||||
data: isEmbedded ? new Uint8Array((await file.arrayBuffer()).slice(0)) : null,
|
||||
isError: false,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const newUploadingFiles: (LocalFile & {
|
||||
file: File;
|
||||
data: TEditorEnvelope['envelopeItems'][number]['data'] | null;
|
||||
})[] = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
return {
|
||||
id: nanoid(),
|
||||
envelopeItemId: isEmbedded ? `${PRESIGNED_ENVELOPE_ITEM_ID_PREFIX}${nanoid()}` : null,
|
||||
title: file.name,
|
||||
file,
|
||||
isUploading: isEmbedded ? false : true,
|
||||
// Clone the buffer so it can be read multiple times (File.arrayBuffer() consumes the stream once)
|
||||
data: isEmbedded ? new Uint8Array((await file.arrayBuffer()).slice(0)) : null,
|
||||
isError: false,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
setLocalFiles((prev) => [...prev, ...newUploadingFiles]);
|
||||
|
||||
@@ -194,7 +196,9 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
* Hide the envelope item from the list on deletion.
|
||||
*/
|
||||
const onFileDelete = (envelopeItemId: string) => {
|
||||
setLocalFiles((prev) => prev.filter((uploadingFile) => uploadingFile.id !== envelopeItemId));
|
||||
setLocalFiles((prev) =>
|
||||
prev.filter((uploadingFile) => uploadingFile.envelopeItemId !== envelopeItemId),
|
||||
);
|
||||
|
||||
const fieldsWithoutDeletedItem = envelope.fields.filter(
|
||||
(field) => field.envelopeItemId !== envelopeItemId,
|
||||
@@ -476,13 +480,10 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
|
||||
{/* Recipients Section */}
|
||||
<EnvelopeEditorRecipientForm />
|
||||
|
||||
{editorConfig.general.allowAddFieldsStep && (
|
||||
<div className="flex justify-end">
|
||||
<Button asChild>
|
||||
<Link to={`${relativePath.editorPath}?step=addFields`}>
|
||||
<Trans>Add Fields</Trans>
|
||||
</Link>
|
||||
<Button type="button" onClick={() => void navigateToStep('addFields')}>
|
||||
<Trans>Add Fields</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import type { EnvelopeEditorStep } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
||||
@@ -43,8 +44,6 @@ import EnvelopeEditorHeader from './envelope-editor-header';
|
||||
import { EnvelopeEditorPreviewPage } from './envelope-editor-preview-page';
|
||||
import { EnvelopeEditorUploadPage } from './envelope-editor-upload-page';
|
||||
|
||||
type EnvelopeEditorStep = 'upload' | 'addFields' | 'preview';
|
||||
|
||||
type EnvelopeEditorStepData = {
|
||||
id: string;
|
||||
title: MessageDescriptor;
|
||||
@@ -83,14 +82,12 @@ export const EnvelopeEditor = () => {
|
||||
editorConfig,
|
||||
isDocument,
|
||||
isTemplate,
|
||||
isAutosaving,
|
||||
flushAutosave,
|
||||
relativePath,
|
||||
syncEnvelope,
|
||||
navigateToStep,
|
||||
} = useCurrentEnvelopeEditor();
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [isStepLoading, setIsStepLoading] = useState(false);
|
||||
|
||||
const {
|
||||
general: {
|
||||
@@ -130,47 +127,25 @@ export const EnvelopeEditor = () => {
|
||||
}));
|
||||
}, [editorConfig]);
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<EnvelopeEditorStep>(() => {
|
||||
const searchParamStep = searchParams.get('step') as EnvelopeEditorStep | undefined;
|
||||
const [currentStep, setCurrentStep] = useState<{ step: EnvelopeEditorStep; isLoading: boolean }>(
|
||||
() => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const searchParamStep = searchParams.get('step') as EnvelopeEditorStep | undefined;
|
||||
|
||||
// Empty URL param equals upload, otherwise use the step URL param
|
||||
if (!searchParamStep) {
|
||||
return 'upload';
|
||||
}
|
||||
// Empty URL param equals upload, otherwise use the step URL param
|
||||
if (!searchParamStep) {
|
||||
return { step: 'upload', isLoading: false };
|
||||
}
|
||||
|
||||
const validSteps: EnvelopeEditorStep[] = ['upload', 'addFields', 'preview'];
|
||||
const validSteps: EnvelopeEditorStep[] = ['upload', 'addFields', 'preview'];
|
||||
|
||||
if (validSteps.includes(searchParamStep)) {
|
||||
return searchParamStep;
|
||||
}
|
||||
if (validSteps.includes(searchParamStep)) {
|
||||
return { step: searchParamStep, isLoading: false };
|
||||
}
|
||||
|
||||
return 'upload';
|
||||
});
|
||||
|
||||
const navigateToStep = (step: EnvelopeEditorStep) => {
|
||||
setCurrentStep(step);
|
||||
|
||||
void flushAutosave();
|
||||
|
||||
if (!isStepLoading && isAutosaving) {
|
||||
setIsStepLoading(true);
|
||||
}
|
||||
|
||||
// Update URL params: empty for upload, otherwise set the step
|
||||
if (step === 'upload') {
|
||||
setSearchParams((prev) => {
|
||||
const newParams = new URLSearchParams(prev);
|
||||
newParams.delete('step');
|
||||
return newParams;
|
||||
});
|
||||
} else {
|
||||
setSearchParams((prev) => {
|
||||
const newParams = new URLSearchParams(prev);
|
||||
newParams.set('step', step);
|
||||
return newParams;
|
||||
});
|
||||
}
|
||||
};
|
||||
return { step: 'upload', isLoading: false };
|
||||
},
|
||||
);
|
||||
|
||||
// Watch the URL params and setStep if the step changes.
|
||||
useEffect(() => {
|
||||
@@ -178,20 +153,19 @@ export const EnvelopeEditor = () => {
|
||||
|
||||
const foundStep = envelopeEditorSteps.find((step) => step.id === stepParam);
|
||||
|
||||
if (foundStep && foundStep.id !== currentStep) {
|
||||
if (foundStep && foundStep.id !== currentStep.step) {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
navigateToStep(foundStep.id as EnvelopeEditorStep);
|
||||
void navigateToStep(foundStep.id as EnvelopeEditorStep).then(() => {
|
||||
setCurrentStep({
|
||||
step: foundStep.id as EnvelopeEditorStep,
|
||||
isLoading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAutosaving) {
|
||||
setIsStepLoading(false);
|
||||
}
|
||||
}, [isAutosaving]);
|
||||
|
||||
const currentStepData =
|
||||
envelopeEditorSteps.find((step) => step.id === currentStep) || envelopeEditorSteps[0];
|
||||
envelopeEditorSteps.find((step) => step.id === currentStep.step) || envelopeEditorSteps[0];
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-gray-50 dark:bg-background">
|
||||
@@ -285,11 +259,12 @@ export const EnvelopeEditor = () => {
|
||||
>
|
||||
{envelopeEditorSteps.map((step) => {
|
||||
const Icon = step.icon;
|
||||
const isActive = currentStep === step.id;
|
||||
const isActive = currentStep.step === step.id;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={step.id}
|
||||
data-testid={`envelope-editor-step-${step.id}`}
|
||||
type="button"
|
||||
className={cn(
|
||||
`cursor-pointer rounded-lg text-left transition-colors ${
|
||||
@@ -301,7 +276,7 @@ export const EnvelopeEditor = () => {
|
||||
'p-3': !minimizeLeftSidebar,
|
||||
},
|
||||
)}
|
||||
onClick={() => navigateToStep(step.id as EnvelopeEditorStep)}
|
||||
onClick={() => void navigateToStep(step.id as EnvelopeEditorStep)}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
@@ -574,10 +549,13 @@ export const EnvelopeEditor = () => {
|
||||
</div>
|
||||
|
||||
{/* Main Content - Changes based on current step */}
|
||||
<AnimateGenericFadeInOut className="flex-1 overflow-y-auto" key={currentStep}>
|
||||
<AnimateGenericFadeInOut
|
||||
className="flex-1 overflow-y-auto"
|
||||
key={currentStep.isLoading ? `loading-${currentStep.step}` : currentStep.step}
|
||||
>
|
||||
{match({
|
||||
currentStep,
|
||||
isStepLoading,
|
||||
isStepLoading: currentStep.isLoading,
|
||||
currentStep: currentStep.step,
|
||||
allowUploadAndRecipientStep,
|
||||
allowAddFieldsStep,
|
||||
allowPreviewStep,
|
||||
|
||||
Reference in New Issue
Block a user