Files
documenso/packages/ui/primitives/document-dropzone.tsx
T

168 lines
7.8 KiB
TypeScript

import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { getAllowedUploadMimeTypes } from '@documenso/lib/constants/document-conversion';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { AlertTriangle, Plus } from 'lucide-react';
import type { FileRejection } from 'react-dropzone';
import { useDropzone } from 'react-dropzone';
import { Link } from 'react-router';
import {
DocumentDropzoneCardCenterVariants,
DocumentDropzoneCardLeftVariants,
DocumentDropzoneCardRightVariants,
DocumentDropzoneContainerVariants,
DocumentDropzoneDisabledCardCenterVariants,
DocumentDropzoneDisabledCardLeftVariants,
DocumentDropzoneDisabledCardRightVariants,
} from '../lib/document-dropzone-constants';
import { cn } from '../lib/utils';
import { Button } from './button';
import { Card, CardContent } from './card';
export type DocumentDropzoneProps = {
className?: string;
allowMultiple?: boolean;
disabled?: boolean;
disabledHeading?: MessageDescriptor;
disabledMessage?: MessageDescriptor;
onDrop?: (_file: File[]) => void | Promise<void>;
onDropRejected?: (fileRejections: FileRejection[]) => void;
type?: 'document' | 'template';
maxFiles?: number;
[key: string]: unknown;
};
export const DocumentDropzone = ({
className,
allowMultiple,
onDrop,
onDropRejected,
disabled,
disabledHeading,
disabledMessage = msg`You cannot upload documents at this time.`,
type = 'document',
maxFiles,
...props
}: DocumentDropzoneProps) => {
const { _ } = useLingui();
const organisation = useCurrentOrganisation();
const { getRootProps, getInputProps } = useDropzone({
accept: getAllowedUploadMimeTypes(),
multiple: allowMultiple,
disabled,
onDrop: (acceptedFiles) => {
if (acceptedFiles.length > 0 && onDrop) {
void onDrop(acceptedFiles);
}
},
onDropRejected,
maxFiles,
maxSize: megabytesToBytes(APP_DOCUMENT_UPLOAD_SIZE_LIMIT),
});
const heading = {
document: disabled ? disabledHeading || msg`You have reached your document limit.` : msg`Add a document`,
template: msg`Upload Template Document`,
};
return (
<motion.div variants={DocumentDropzoneContainerVariants} initial="initial" animate="animate" whileHover="hover">
<Card
role="button"
className={cn(
'group flex flex-1 cursor-pointer flex-col items-center justify-center ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
gradient={!disabled}
degrees={120}
aria-disabled={disabled}
{...getRootProps()}
{...props}
>
<CardContent className="flex flex-col items-center justify-center p-6 text-muted-foreground/40">
{disabled ? (
// Disabled State
<div className="flex">
<motion.div
className="a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-background/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 group-hover:bg-destructive/2"
variants={DocumentDropzoneDisabledCardLeftVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
<div className="h-2 w-5/6 rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
</motion.div>
<motion.div
className="z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border border-muted-foreground/20 bg-background/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/50 group-hover:bg-destructive/5"
variants={DocumentDropzoneDisabledCardCenterVariants}
>
<AlertTriangle
strokeWidth="2px"
className="h-12 w-12 text-muted-foreground/20 group-hover:text-destructive"
/>
</motion.div>
<motion.div
className="z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-background/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 group-hover:bg-destructive/2"
variants={DocumentDropzoneDisabledCardRightVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
<div className="h-2 w-5/6 rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
</motion.div>
</div>
) : (
// Non Disabled State
<div className="flex">
<motion.div
className="a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-background/80 px-2 py-4 backdrop-blur-sm group-hover:border-documenso/80"
variants={DocumentDropzoneCardLeftVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/20 group-hover:bg-documenso" />
<div className="h-2 w-5/6 rounded-[2px] bg-muted-foreground/20 group-hover:bg-documenso" />
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/20 group-hover:bg-documenso" />
</motion.div>
<motion.div
className="z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border border-muted-foreground/20 bg-background/80 px-2 py-4 backdrop-blur-sm group-hover:border-documenso/80"
variants={DocumentDropzoneCardCenterVariants}
>
<Plus strokeWidth="2px" className="h-12 w-12 text-muted-foreground/20 group-hover:text-documenso" />
</motion.div>
<motion.div
className="z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-background/80 px-2 py-4 backdrop-blur-sm group-hover:border-documenso/80"
variants={DocumentDropzoneCardRightVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/20 group-hover:bg-documenso" />
<div className="h-2 w-5/6 rounded-[2px] bg-muted-foreground/20 group-hover:bg-documenso" />
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/20 group-hover:bg-documenso" />
</motion.div>
</div>
)}
<input {...getInputProps()} />
<p className="mt-6 font-medium text-foreground">{_(heading[type])}</p>
<p className="mt-1 text-center text-muted-foreground/80 text-sm">
{_(disabled ? disabledMessage : msg`Drag & drop your document here.`)}
</p>
{disabled && IS_BILLING_ENABLED() && (
<Button className="mt-4 w-32 bg-warning hover:bg-warning/80" asChild>
<Link to={`/o/${organisation.url}/settings/billing`}>
<Trans>Upgrade</Trans>
</Link>
</Button>
)}
</CardContent>
</Card>
</motion.div>
);
};