mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
This PR is handles the changes required to support envelopes. The new envelope editor/signing page will be hidden during release. The core changes here is to migrate the documents and templates model to a centralized envelopes model. Even though Documents and Templates are removed, from the user perspective they will still exist as we remap envelopes to documents and templates.
165 lines
5.2 KiB
TypeScript
165 lines
5.2 KiB
TypeScript
import { useMemo, useState } from 'react';
|
|
|
|
import { msg } from '@lingui/core/macro';
|
|
import { useLingui } from '@lingui/react';
|
|
import { Trans } from '@lingui/react/macro';
|
|
import { useNavigate, useParams } from 'react-router';
|
|
import { match } from 'ts-pattern';
|
|
|
|
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
|
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
|
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
|
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';
|
|
import { cn } from '@documenso/ui/lib/utils';
|
|
import { DocumentDropzone } from '@documenso/ui/primitives/document-upload';
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from '@documenso/ui/primitives/tooltip';
|
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
|
|
|
import { useCurrentTeam } from '~/providers/team';
|
|
|
|
export type DocumentUploadButtonProps = {
|
|
className?: string;
|
|
};
|
|
|
|
export const DocumentUploadButton = ({ className }: DocumentUploadButtonProps) => {
|
|
const { _ } = useLingui();
|
|
const { toast } = useToast();
|
|
const { user } = useSession();
|
|
const { folderId } = useParams();
|
|
|
|
const team = useCurrentTeam();
|
|
|
|
const navigate = useNavigate();
|
|
const analytics = useAnalytics();
|
|
const organisation = useCurrentOrganisation();
|
|
|
|
const userTimezone =
|
|
TIME_ZONES.find((timezone) => timezone === Intl.DateTimeFormat().resolvedOptions().timeZone) ??
|
|
DEFAULT_DOCUMENT_TIME_ZONE;
|
|
|
|
const { quota, remaining, refreshLimits } = useLimits();
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const { mutateAsync: createDocument } = trpc.document.create.useMutation();
|
|
|
|
const disabledMessage = useMemo(() => {
|
|
if (organisation.subscription && remaining.documents === 0) {
|
|
return msg`Document upload disabled due to unpaid invoices`;
|
|
}
|
|
|
|
if (remaining.documents === 0) {
|
|
return msg`You have reached your document limit.`;
|
|
}
|
|
|
|
if (!user.emailVerified) {
|
|
return msg`Verify your email to upload documents.`;
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [remaining.documents, user.emailVerified, team]);
|
|
|
|
const onFileDrop = async (file: File) => {
|
|
try {
|
|
setIsLoading(true);
|
|
|
|
const response = await putPdfFile(file);
|
|
|
|
const { legacyDocumentId: id } = await createDocument({
|
|
title: file.name,
|
|
documentDataId: response.id,
|
|
timezone: userTimezone,
|
|
folderId: folderId ?? undefined,
|
|
});
|
|
|
|
void refreshLimits();
|
|
|
|
await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`);
|
|
|
|
toast({
|
|
title: _(msg`Document uploaded`),
|
|
description: _(msg`Your document has been uploaded successfully.`),
|
|
duration: 5000,
|
|
});
|
|
|
|
analytics.capture('App: Document Uploaded', {
|
|
userId: user.id,
|
|
documentId: id,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (err) {
|
|
const error = AppError.parseError(err);
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
const onFileDropRejected = () => {
|
|
toast({
|
|
title: _(msg`Your document failed to upload.`),
|
|
description: _(msg`File cannot be larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`),
|
|
duration: 5000,
|
|
variant: 'destructive',
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className={cn('relative', className)}>
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<div>
|
|
<DocumentDropzone
|
|
loading={isLoading}
|
|
disabled={remaining.documents === 0 || !user.emailVerified}
|
|
disabledMessage={disabledMessage}
|
|
onDrop={async (files) => onFileDrop(files[0])}
|
|
onDropRejected={onFileDropRejected}
|
|
/>
|
|
</div>
|
|
</TooltipTrigger>
|
|
|
|
{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>
|
|
</div>
|
|
);
|
|
};
|