mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
feat: implement Drag-n-Drop for templates (#1791)
This commit is contained in:
@ -0,0 +1,129 @@
|
|||||||
|
import { type ReactNode, useState } from 'react';
|
||||||
|
|
||||||
|
import { msg } from '@lingui/core/macro';
|
||||||
|
import { useLingui } from '@lingui/react';
|
||||||
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import { useNavigate, useParams } from 'react-router';
|
||||||
|
|
||||||
|
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||||
|
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
|
||||||
|
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||||
|
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||||
|
import { trpc } from '@documenso/trpc/react';
|
||||||
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
import { useCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
|
export interface TemplateDropZoneWrapperProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TemplateDropZoneWrapper = ({ children, className }: TemplateDropZoneWrapperProps) => {
|
||||||
|
const { _ } = useLingui();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { folderId } = useParams();
|
||||||
|
|
||||||
|
const team = useCurrentTeam();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const { mutateAsync: createTemplate } = trpc.template.createTemplate.useMutation();
|
||||||
|
|
||||||
|
const onFileDrop = async (file: File) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const documentData = await putPdfFile(file);
|
||||||
|
|
||||||
|
const { id } = await createTemplate({
|
||||||
|
title: file.name,
|
||||||
|
templateDocumentDataId: documentData.id,
|
||||||
|
folderId: folderId ?? undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: _(msg`Template uploaded`),
|
||||||
|
description: _(
|
||||||
|
msg`Your template has been uploaded successfully. You will be redirected to the template page.`,
|
||||||
|
),
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await navigate(`${formatTemplatesPath(team.url)}/${id}/edit`);
|
||||||
|
} catch {
|
||||||
|
toast({
|
||||||
|
title: _(msg`Something went wrong`),
|
||||||
|
description: _(msg`Please try again later.`),
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileDropRejected = () => {
|
||||||
|
toast({
|
||||||
|
title: _(msg`Your template failed to upload.`),
|
||||||
|
description: _(msg`File cannot be larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`),
|
||||||
|
duration: 5000,
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
|
accept: {
|
||||||
|
'application/pdf': ['.pdf'],
|
||||||
|
},
|
||||||
|
//disabled: isUploadDisabled,
|
||||||
|
multiple: false,
|
||||||
|
maxSize: megabytesToBytes(APP_DOCUMENT_UPLOAD_SIZE_LIMIT),
|
||||||
|
onDrop: ([acceptedFile]) => {
|
||||||
|
if (acceptedFile) {
|
||||||
|
void onFileDrop(acceptedFile);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDropRejected: () => {
|
||||||
|
void onFileDropRejected();
|
||||||
|
},
|
||||||
|
noClick: true,
|
||||||
|
noDragEventsBubbling: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...getRootProps()} className={cn('relative min-h-screen', className)}>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{children}
|
||||||
|
|
||||||
|
{isDragActive && (
|
||||||
|
<div className="bg-muted/60 fixed left-0 top-0 z-[9999] h-full w-full backdrop-blur-[4px]">
|
||||||
|
<div className="pointer-events-none flex h-full w-full flex-col items-center justify-center">
|
||||||
|
<h2 className="text-foreground text-2xl font-semibold">
|
||||||
|
<Trans>Upload Template</Trans>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground text-md mt-4">
|
||||||
|
<Trans>Drag and drop your PDF file here</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<div className="bg-muted/30 absolute inset-0 z-50 backdrop-blur-[2px]">
|
||||||
|
<div className="pointer-events-none flex h-1/2 w-full flex-col items-center justify-center">
|
||||||
|
<Loader className="text-primary h-12 w-12 animate-spin" />
|
||||||
|
<p className="text-foreground mt-8 font-medium">
|
||||||
|
<Trans>Uploading template...</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -12,6 +12,7 @@ import { FolderGrid } from '~/components/general/folder/folder-grid';
|
|||||||
import { TemplatesTable } from '~/components/tables/templates-table';
|
import { TemplatesTable } from '~/components/tables/templates-table';
|
||||||
import { useCurrentTeam } from '~/providers/team';
|
import { useCurrentTeam } from '~/providers/team';
|
||||||
import { appMetaTags } from '~/utils/meta';
|
import { appMetaTags } from '~/utils/meta';
|
||||||
|
import { TemplateDropZoneWrapper } from '~/components/general/template/template-drop-zone-wrapper';
|
||||||
|
|
||||||
export function meta() {
|
export function meta() {
|
||||||
return appMetaTags('Templates');
|
return appMetaTags('Templates');
|
||||||
@ -36,6 +37,7 @@ export default function TemplatesPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<TemplateDropZoneWrapper>
|
||||||
<div className="mx-auto max-w-screen-xl px-4 md:px-8">
|
<div className="mx-auto max-w-screen-xl px-4 md:px-8">
|
||||||
<FolderGrid type={FolderType.TEMPLATE} parentId={folderId ?? null} />
|
<FolderGrid type={FolderType.TEMPLATE} parentId={folderId ?? null} />
|
||||||
|
|
||||||
@ -65,7 +67,8 @@ export default function TemplatesPage() {
|
|||||||
|
|
||||||
<p className="mt-2 max-w-[50ch]">
|
<p className="mt-2 max-w-[50ch]">
|
||||||
<Trans>
|
<Trans>
|
||||||
You have not yet created any templates. To create a template please upload one.
|
You have not yet created any templates. To create a template please upload
|
||||||
|
one.
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -82,5 +85,6 @@ export default function TemplatesPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</TemplateDropZoneWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -379,10 +379,11 @@ test('[TEAMS]: can create a template inside a template folder', async ({ page })
|
|||||||
.filter({ hasText: /^Upload Template DocumentDrag & drop your PDF here\.$/ })
|
.filter({ hasText: /^Upload Template DocumentDrag & drop your PDF here\.$/ })
|
||||||
.nth(2)
|
.nth(2)
|
||||||
.click();
|
.click();
|
||||||
await page.locator('input[type="file"]').waitFor({ state: 'attached' });
|
await page.locator('input[type="file"]').nth(0).waitFor({ state: 'attached' });
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.locator('input[type="file"]')
|
.locator('input[type="file"]')
|
||||||
|
.nth(0)
|
||||||
.setInputFiles(path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'));
|
.setInputFiles(path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'));
|
||||||
|
|
||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(3000);
|
||||||
|
|||||||
Reference in New Issue
Block a user