Files
sign/apps/web/src/app/(dashboard)/documents/upload-document.tsx

165 lines
5.0 KiB
TypeScript
Raw Normal View History

2023-06-09 18:21:18 +10:00
'use client';
import { useMemo, useState } from 'react';
2023-06-09 18:21:18 +10:00
import { useRouter } from 'next/navigation';
2024-08-27 20:34:39 +09:00
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
2023-06-09 18:21:18 +10:00
import { Loader } from 'lucide-react';
import { useSession } from 'next-auth/react';
2023-06-09 18:21:18 +10:00
2023-10-15 20:26:32 +11:00
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { AppError } from '@documenso/lib/errors/app-error';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
2023-10-15 20:26:32 +11:00
import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
2023-06-09 18:21:18 +10:00
import { cn } from '@documenso/ui/lib/utils';
import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone';
2023-06-09 18:21:18 +10:00
import { useToast } from '@documenso/ui/primitives/use-toast';
export type UploadDocumentProps = {
className?: string;
team?: {
id: number;
url: string;
};
2023-06-09 18:21:18 +10:00
};
export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
2023-06-09 18:21:18 +10:00
const router = useRouter();
const analytics = useAnalytics();
const userTimezone =
TIME_ZONES.find((timezone) => timezone === Intl.DateTimeFormat().resolvedOptions().timeZone) ??
DEFAULT_DOCUMENT_TIME_ZONE;
const { data: session } = useSession();
2023-06-09 18:21:18 +10:00
2024-08-27 20:34:39 +09:00
const { _ } = useLingui();
const { toast } = useToast();
const { quota, remaining, refreshLimits } = useLimits();
2023-10-15 20:26:32 +11:00
const [isLoading, setIsLoading] = useState(false);
const { mutateAsync: createDocument } = trpc.document.createDocument.useMutation();
2023-06-09 18:21:18 +10:00
const disabledMessage = useMemo(() => {
if (remaining.documents === 0) {
return team
2024-08-27 20:34:39 +09:00
? msg`Document upload disabled due to unpaid invoices`
: msg`You have reached your document limit.`;
}
if (!session?.user.emailVerified) {
2024-08-27 20:34:39 +09:00
return msg`Verify your email to upload documents.`;
}
2024-08-27 20:34:39 +09:00
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [remaining.documents, session?.user.emailVerified, team]);
2023-06-09 18:21:18 +10:00
const onFileDrop = async (file: File) => {
try {
setIsLoading(true);
const { type, data } = await putPdfFile(file);
const { id: documentDataId } = await createDocumentData({
type,
data,
});
2023-06-09 18:21:18 +10:00
const { id } = await createDocument({
title: file.name,
documentDataId,
timezone: userTimezone,
2023-06-09 18:21:18 +10:00
});
void refreshLimits();
2023-06-09 18:21:18 +10:00
toast({
2024-08-27 20:34:39 +09:00
title: _(msg`Document uploaded`),
description: _(msg`Your document has been uploaded successfully.`),
2023-06-09 18:21:18 +10:00
duration: 5000,
});
analytics.capture('App: Document Uploaded', {
userId: session?.user.id,
documentId: id,
timestamp: new Date().toISOString(),
});
2024-02-12 17:30:23 +11:00
router.push(`${formatDocumentsPath(team?.url)}/${id}/edit`);
} catch (err) {
const error = AppError.parseError(err);
2023-06-09 18:21:18 +10:00
console.error(err);
if (error.code === 'INVALID_DOCUMENT_FILE') {
toast({
2024-08-27 20:34:39 +09:00
title: _(msg`Invalid file`),
description: _(msg`You cannot upload encrypted PDFs`),
variant: 'destructive',
});
} else if (err instanceof TRPCClientError) {
2023-10-15 20:26:32 +11:00
toast({
2024-08-27 20:34:39 +09:00
title: _(msg`Error`),
description: err.message,
2023-10-15 20:26:32 +11:00
variant: 'destructive',
});
} else {
toast({
2024-08-27 20:34:39 +09:00
title: _(msg`Error`),
description: _(msg`An error occurred while uploading your document.`),
2023-10-15 20:26:32 +11:00
variant: 'destructive',
});
}
} finally {
setIsLoading(false);
2023-06-09 18:21:18 +10:00
}
};
const onFileDropRejected = () => {
toast({
2024-08-27 20:34:39 +09:00
title: _(msg`Your document failed to upload.`),
description: _(msg`File cannot be larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`),
duration: 5000,
variant: 'destructive',
});
};
2023-06-09 18:21:18 +10:00
return (
<div className={cn('relative', className)}>
2023-10-15 20:26:32 +11:00
<DocumentDropzone
2024-02-16 11:02:04 +00:00
className="h-[min(400px,50vh)]"
disabled={remaining.documents === 0 || !session?.user.emailVerified}
disabledMessage={disabledMessage}
2023-10-15 20:26:32 +11:00
onDrop={onFileDrop}
onDropRejected={onFileDropRejected}
2023-10-15 20:26:32 +11:00
/>
<div className="absolute -bottom-6 right-0">
{team?.id === undefined &&
remaining.documents > 0 &&
Number.isFinite(remaining.documents) && (
<p className="text-muted-foreground/60 text-xs">
2024-08-27 20:34:39 +09:00
<Trans>
{remaining.documents} of {quota.documents} documents remaining this month.
</Trans>
</p>
)}
2023-10-15 20:26:32 +11:00
</div>
2023-06-09 18:21:18 +10:00
{isLoading && (
2023-10-15 20:26:32 +11:00
<div className="bg-background/50 absolute inset-0 flex items-center justify-center rounded-lg">
2023-09-20 02:18:35 +00:00
<Loader className="text-muted-foreground h-12 w-12 animate-spin" />
2023-06-09 18:21:18 +10:00
</div>
)}
</div>
);
};