mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
feat: improved ui of document dropzone for max quota state
Signed-off-by: Adithya Krishna <adithya@documenso.com>
This commit is contained in:
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
@ -139,27 +138,6 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
|
|||||||
<Loader className="text-muted-foreground h-12 w-12 animate-spin" />
|
<Loader className="text-muted-foreground h-12 w-12 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{team?.id === undefined && remaining.documents === 0 && (
|
|
||||||
<div className="bg-background/60 absolute inset-0 flex items-center justify-center rounded-lg backdrop-blur-sm">
|
|
||||||
<div className="text-center">
|
|
||||||
<h2 className="text-muted-foreground/80 text-xl font-semibold">
|
|
||||||
You have reached your document limit.
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground/60 mt-2 text-sm">
|
|
||||||
You can upload up to {quota.documents} documents per month on your current plan.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
className="text-primary hover:text-primary/80 mt-6 block font-medium"
|
|
||||||
href="/settings/billing"
|
|
||||||
>
|
|
||||||
Upgrade your account to upload more documents.
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -28,6 +28,9 @@ module.exports = {
|
|||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: 'hsl(var(--secondary-foreground))',
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
},
|
},
|
||||||
|
warning: {
|
||||||
|
DEFAULT: 'hsl(var(--warning))',
|
||||||
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: 'hsl(var(--destructive-foreground))',
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
|
|||||||
118
packages/ui/lib/document-dropzone-constants.ts
Normal file
118
packages/ui/lib/document-dropzone-constants.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import type { Variants } from 'framer-motion';
|
||||||
|
|
||||||
|
export const DocumentDropzoneContainerVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
scale: 1,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
scale: 1,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.05,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneCardLeftVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: 40,
|
||||||
|
y: -10,
|
||||||
|
rotate: -14,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: 40,
|
||||||
|
y: -10,
|
||||||
|
rotate: -14,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: -25,
|
||||||
|
y: -25,
|
||||||
|
rotate: -22,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneCardRightVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: -40,
|
||||||
|
y: -10,
|
||||||
|
rotate: 14,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: -40,
|
||||||
|
y: -10,
|
||||||
|
rotate: 14,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: 25,
|
||||||
|
y: -25,
|
||||||
|
rotate: 22,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneCardCenterVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: 0,
|
||||||
|
y: -25,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneDisabledCardLeftVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: 40,
|
||||||
|
y: 30,
|
||||||
|
rotate: -14,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: 40,
|
||||||
|
y: 0,
|
||||||
|
rotate: -14,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: 30,
|
||||||
|
y: 0,
|
||||||
|
transition: { type: 'spring', duration: 0.25, bounce: 0.5 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneDisabledCardRightVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: -40,
|
||||||
|
y: 30,
|
||||||
|
rotate: 14,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: -50,
|
||||||
|
y: 5,
|
||||||
|
rotate: 14,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: -40,
|
||||||
|
y: 5,
|
||||||
|
transition: { type: 'spring', duration: 0.25, bounce: 0.5 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneDisabledCardCenterVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: 20,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: -10,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: -20,
|
||||||
|
y: 0,
|
||||||
|
rotate: -5,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,90 +1,27 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import type { Variants } from 'framer-motion';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Plus } from 'lucide-react';
|
import { AlertTriangle, Plus } from 'lucide-react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
|
||||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||||
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
|
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DocumentDropzoneCardCenterVariants,
|
||||||
|
DocumentDropzoneCardLeftVariants,
|
||||||
|
DocumentDropzoneCardRightVariants,
|
||||||
|
DocumentDropzoneContainerVariants,
|
||||||
|
DocumentDropzoneDisabledCardCenterVariants,
|
||||||
|
DocumentDropzoneDisabledCardLeftVariants,
|
||||||
|
DocumentDropzoneDisabledCardRightVariants,
|
||||||
|
} from '../lib/document-dropzone-constants';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
import { Button } from './button';
|
||||||
import { Card, CardContent } from './card';
|
import { Card, CardContent } from './card';
|
||||||
|
|
||||||
const DocumentDropzoneContainerVariants: Variants = {
|
|
||||||
initial: {
|
|
||||||
scale: 1,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
scale: 1,
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
transition: {
|
|
||||||
staggerChildren: 0.05,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DocumentDropzoneCardLeftVariants: Variants = {
|
|
||||||
initial: {
|
|
||||||
x: 40,
|
|
||||||
y: -10,
|
|
||||||
rotate: -14,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
x: 40,
|
|
||||||
y: -10,
|
|
||||||
rotate: -14,
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
x: -25,
|
|
||||||
y: -25,
|
|
||||||
rotate: -22,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DocumentDropzoneCardRightVariants: Variants = {
|
|
||||||
initial: {
|
|
||||||
x: -40,
|
|
||||||
y: -10,
|
|
||||||
rotate: 14,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
x: -40,
|
|
||||||
y: -10,
|
|
||||||
rotate: 14,
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
x: 25,
|
|
||||||
y: -25,
|
|
||||||
rotate: 22,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DocumentDropzoneCardCenterVariants: Variants = {
|
|
||||||
initial: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
x: 0,
|
|
||||||
y: -25,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DocumentDescription = {
|
|
||||||
document: {
|
|
||||||
headline: 'Add a document',
|
|
||||||
},
|
|
||||||
template: {
|
|
||||||
headline: 'Upload Template Document',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DocumentDropzoneProps = {
|
export type DocumentDropzoneProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@ -100,10 +37,18 @@ export const DocumentDropzone = ({
|
|||||||
onDrop,
|
onDrop,
|
||||||
onDropRejected,
|
onDropRejected,
|
||||||
disabled,
|
disabled,
|
||||||
disabledMessage = 'You cannot upload documents at this time.',
|
disabledMessage = 'You can upload up to 5 documents per month on your current plan.',
|
||||||
type = 'document',
|
type = 'document',
|
||||||
...props
|
...props
|
||||||
}: DocumentDropzoneProps) => {
|
}: DocumentDropzoneProps) => {
|
||||||
|
const DocumentDescription = {
|
||||||
|
document: {
|
||||||
|
headline: disabled ? 'You have reached your document limit.' : 'Add a document',
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
headline: 'Upload Template Document',
|
||||||
|
},
|
||||||
|
};
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
'application/pdf': ['.pdf'],
|
'application/pdf': ['.pdf'],
|
||||||
@ -125,66 +70,101 @@ export const DocumentDropzone = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className={cn('flex aria-disabled:cursor-not-allowed', className)}
|
className={cn('flex', className)}
|
||||||
variants={DocumentDropzoneContainerVariants}
|
variants={DocumentDropzoneContainerVariants}
|
||||||
initial="initial"
|
initial="initial"
|
||||||
animate="animate"
|
animate="animate"
|
||||||
whileHover="hover"
|
whileHover="hover"
|
||||||
aria-disabled={disabled}
|
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
role="button"
|
role="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
'focus-visible:ring-ring ring-offset-background flex flex-1 cursor-pointer flex-col items-center justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 aria-disabled:pointer-events-none aria-disabled:opacity-60',
|
'focus-visible:ring-ring ring-offset-background flex flex-1 cursor-pointer flex-col items-center justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
gradient={true}
|
gradient={!disabled}
|
||||||
degrees={120}
|
degrees={120}
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<CardContent className="text-muted-foreground/40 flex flex-col items-center justify-center p-6">
|
<CardContent className="text-muted-foreground/40 flex flex-col items-center justify-center p-6">
|
||||||
{/* <FilePlus strokeWidth="1px" className="h-16 w-16"/> */}
|
{disabled ? (
|
||||||
<div className="flex">
|
// Disabled State
|
||||||
<motion.div
|
<div className="flex">
|
||||||
className="border-muted-foreground/20 group-hover:border-documenso/80 dark:bg-muted/80 a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
<motion.div
|
||||||
variants={!disabled ? DocumentDropzoneCardLeftVariants : undefined}
|
className="group-hover:bg-destructive/2 border-muted-foreground/20 group-hover:border-destructive/10 dark:bg-muted/80 a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||||
>
|
variants={disabled ? DocumentDropzoneDisabledCardLeftVariants : undefined}
|
||||||
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
>
|
||||||
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-5/6 rounded-[2px]" />
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-full rounded-[2px]" />
|
||||||
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-5/6 rounded-[2px]" />
|
||||||
</motion.div>
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-full rounded-[2px]" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="border-muted-foreground/20 group-hover:border-documenso/80 dark:bg-muted/80 z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
className="group-hover:bg-destructive/5 border-muted-foreground/20 group-hover:border-destructive/50 dark:bg-muted/80 z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||||
variants={!disabled ? DocumentDropzoneCardCenterVariants : undefined}
|
variants={disabled ? DocumentDropzoneDisabledCardCenterVariants : undefined}
|
||||||
>
|
>
|
||||||
<Plus
|
<AlertTriangle
|
||||||
strokeWidth="2px"
|
strokeWidth="2px"
|
||||||
className="text-muted-foreground/20 group-hover:text-documenso h-12 w-12"
|
className="text-muted-foreground/20 group-hover:text-destructive h-12 w-12"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="border-muted-foreground/20 group-hover:border-documenso/80 dark:bg-muted/80 z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
className="group-hover:bg-destructive/2 border-muted-foreground/20 group-hover:border-destructive/10 dark:bg-muted/80 z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||||
variants={!disabled ? DocumentDropzoneCardRightVariants : undefined}
|
variants={disabled ? DocumentDropzoneDisabledCardRightVariants : undefined}
|
||||||
>
|
>
|
||||||
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-full rounded-[2px]" />
|
||||||
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-5/6 rounded-[2px]" />
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-5/6 rounded-[2px]" />
|
||||||
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-full rounded-[2px]" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
// Non Disabled State
|
||||||
|
<div className="flex">
|
||||||
|
<motion.div
|
||||||
|
className="border-muted-foreground/20 group-hover:border-documenso/80 dark:bg-muted/80 a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||||
|
variants={!disabled ? DocumentDropzoneCardLeftVariants : undefined}
|
||||||
|
>
|
||||||
|
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
||||||
|
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-5/6 rounded-[2px]" />
|
||||||
|
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="border-muted-foreground/20 group-hover:border-documenso/80 dark:bg-muted/80 z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||||
|
variants={!disabled ? DocumentDropzoneCardCenterVariants : undefined}
|
||||||
|
>
|
||||||
|
<Plus
|
||||||
|
strokeWidth="2px"
|
||||||
|
className="text-muted-foreground/20 group-hover:text-documenso h-12 w-12"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="border-muted-foreground/20 group-hover:border-documenso/80 dark:bg-muted/80 z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||||
|
variants={!disabled ? DocumentDropzoneCardRightVariants : undefined}
|
||||||
|
>
|
||||||
|
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
||||||
|
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-5/6 rounded-[2px]" />
|
||||||
|
<div className="bg-muted-foreground/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
|
|
||||||
<p className="group-hover:text-foreground text-muted-foreground mt-8 font-medium">
|
<p className="text-foreground mt-8 font-medium">{DocumentDescription[type].headline}</p>
|
||||||
{DocumentDescription[type].headline}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground/80 mt-1 text-sm">
|
<p className="text-muted-foreground/80 mt-1 text-sm">
|
||||||
{disabled ? disabledMessage : 'Drag & drop your PDF here.'}
|
{disabled ? disabledMessage : 'Drag & drop your PDF here.'}
|
||||||
</p>
|
</p>
|
||||||
|
{disabled && (
|
||||||
|
<Button className="hover:bg-warning/80 bg-warning mt-4 w-32" asChild>
|
||||||
|
<Link href="/settings/billing">Upgrade</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -42,6 +42,8 @@
|
|||||||
--ring: 95.08 71.08% 67.45%;
|
--ring: 95.08 71.08% 67.45%;
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
--warning: 54 96% 45%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@ -79,6 +81,8 @@
|
|||||||
--ring: 95.08 71.08% 67.45%;
|
--ring: 95.08 71.08% 67.45%;
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
--warning: 54 96% 45%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user