feat: improved ui of document dropzone for max quota state

Signed-off-by: Adithya Krishna <adithya@documenso.com>
This commit is contained in:
Adithya Krishna
2024-03-07 19:04:58 +05:30
parent b9b57f16c0
commit 9ae51a0072
5 changed files with 217 additions and 134 deletions

View File

@ -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>
); );
}; };

View File

@ -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))',

View 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,
},
};

View File

@ -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>

View File

@ -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%;
} }
} }