documenso/apps/remix/app/components/general/document/document-upload.tsx

165 lines
5.3 KiB
TypeScript
Raw Normal View History

import { useMemo, useState } from 'react';
2025-01-02 04:33:37 +00:00
import { msg } from '@lingui/core/macro';
2024-08-27 11:34:39 +00:00
import { useLingui } from '@lingui/react';
2025-01-02 04:33:37 +00:00
import { Trans } from '@lingui/react/macro';
2025-05-01 16:46:59 +00:00
import { useNavigate, useParams } from 'react-router';
import { match } from 'ts-pattern';
2023-06-09 08:21:18 +00:00
2023-10-15 09:26:32 +00:00
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
2025-06-10 01:49:52 +00:00
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
2025-01-02 04:33:37 +00:00
import { useSession } from '@documenso/lib/client-only/providers/session';
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, AppErrorCode } from '@documenso/lib/errors/app-error';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
2023-06-09 08:21:18 +00:00
import { cn } from '@documenso/ui/lib/utils';
2025-05-01 16:46:59 +00:00
import { DocumentDropzone } from '@documenso/ui/primitives/document-upload';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@documenso/ui/primitives/tooltip';
2023-06-09 08:21:18 +00:00
import { useToast } from '@documenso/ui/primitives/use-toast';
2025-06-10 01:49:52 +00:00
import { useCurrentTeam } from '~/providers/team';
2025-01-02 04:33:37 +00:00
export type DocumentUploadDropzoneProps = {
2023-06-09 08:21:18 +00:00
className?: string;
};
2025-01-02 04:33:37 +00:00
export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const { user } = useSession();
2025-05-01 16:46:59 +00:00
const { folderId } = useParams();
2025-01-02 04:33:37 +00:00
2025-06-10 01:49:52 +00:00
const team = useCurrentTeam();
2025-01-02 04:33:37 +00:00
const navigate = useNavigate();
const analytics = useAnalytics();
2025-06-10 01:49:52 +00:00
const organisation = useCurrentOrganisation();
2025-01-02 04:33:37 +00:00
const userTimezone =
TIME_ZONES.find((timezone) => timezone === Intl.DateTimeFormat().resolvedOptions().timeZone) ??
DEFAULT_DOCUMENT_TIME_ZONE;
const { quota, remaining, refreshLimits } = useLimits();
2023-10-15 09:26:32 +00:00
const [isLoading, setIsLoading] = useState(false);
2025-08-24 22:23:12 +00:00
const { mutateAsync: createDocument } = trpc.document.create.useMutation();
2023-06-09 08:21:18 +00:00
const disabledMessage = useMemo(() => {
2025-06-10 01:49:52 +00:00
if (organisation.subscription && remaining.documents === 0) {
return msg`Document upload disabled due to unpaid invoices`;
}
if (remaining.documents === 0) {
2025-06-10 01:49:52 +00:00
return msg`You have reached your document limit.`;
}
2025-01-02 04:33:37 +00:00
if (!user.emailVerified) {
2024-08-27 11:34:39 +00:00
return msg`Verify your email to upload documents.`;
}
2024-08-27 11:34:39 +00:00
// eslint-disable-next-line react-hooks/exhaustive-deps
2025-01-02 04:33:37 +00:00
}, [remaining.documents, user.emailVerified, team]);
2023-06-09 08:21:18 +00:00
const onFileDrop = async (file: File) => {
try {
setIsLoading(true);
2025-01-02 04:33:37 +00:00
const response = await putPdfFile(file);
2023-06-09 08:21:18 +00:00
const { id } = await createDocument({
title: file.name,
2025-01-02 04:33:37 +00:00
documentDataId: response.id,
timezone: userTimezone, // Note: When migrating to v2 document upload remember to pass this through as a 'userTimezone' field.
2025-05-01 16:46:59 +00:00
folderId: folderId ?? undefined,
2023-06-09 08:21:18 +00:00
});
void refreshLimits();
2025-06-10 01:49:52 +00:00
await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`);
2023-06-09 08:21:18 +00:00
toast({
2024-08-27 11:34:39 +00:00
title: _(msg`Document uploaded`),
description: _(msg`Your document has been uploaded successfully.`),
2023-06-09 08:21:18 +00:00
duration: 5000,
});
analytics.capture('App: Document Uploaded', {
2025-01-02 04:33:37 +00:00
userId: user.id,
documentId: id,
timestamp: new Date().toISOString(),
});
} catch (err) {
const error = AppError.parseError(err);
2023-06-09 08:21:18 +00:00
console.error(err);
const errorMessage = match(error.code)
.with('INVALID_DOCUMENT_FILE', () => msg`You cannot upload encrypted PDFs`)
.with(
AppErrorCode.LIMIT_EXCEEDED,
() => msg`You have reached your document limit for this month. Please upgrade your plan.`,
)
.otherwise(() => msg`An error occurred while uploading your document.`);
toast({
title: _(msg`Error`),
description: _(errorMessage),
variant: 'destructive',
duration: 7500,
});
} finally {
setIsLoading(false);
2023-06-09 08:21:18 +00:00
}
};
const onFileDropRejected = () => {
toast({
2024-08-27 11:34:39 +00: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 08:21:18 +00:00
return (
<div className={cn('relative', className)}>
2025-05-01 16:46:59 +00:00
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>
<DocumentDropzone
2025-06-10 01:49:52 +00:00
loading={isLoading}
2025-05-01 16:46:59 +00:00
disabled={remaining.documents === 0 || !user.emailVerified}
disabledMessage={disabledMessage}
onDrop={onFileDrop}
onDropRejected={onFileDropRejected}
/>
</div>
</TooltipTrigger>
2025-06-10 01:49:52 +00:00
2025-05-01 16:46:59 +00:00
{team?.id === undefined &&
remaining.documents > 0 &&
Number.isFinite(remaining.documents) && (
<TooltipContent>
<p className="text-sm">
<Trans>
{remaining.documents} of {quota.documents} documents remaining this month.
</Trans>
</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
2023-06-09 08:21:18 +00:00
</div>
);
};