mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 01:32:06 +10:00
Merge branch 'main' into test/sign-redirect-url
This commit is contained in:
@ -27,7 +27,7 @@
|
|||||||
<a href="https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso">
|
<a href="https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso">
|
||||||
<img alt="open in devcontainer" src="https://img.shields.io/static/v1?label=Dev%20Containers&message=Enabled&color=blue&logo=visualstudiocode" />
|
<img alt="open in devcontainer" src="https://img.shields.io/static/v1?label=Dev%20Containers&message=Enabled&color=blue&logo=visualstudiocode" />
|
||||||
</a>
|
</a>
|
||||||
<a href="code_of_conduct.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"></a>
|
<a href="CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ description: 'This article talks about the need for the public API and the proce
|
|||||||
authorName: 'Lucas Smith'
|
authorName: 'Lucas Smith'
|
||||||
authorImage: '/blog/blog-author-lucas.png'
|
authorImage: '/blog/blog-author-lucas.png'
|
||||||
authorRole: 'Co-Founder'
|
authorRole: 'Co-Founder'
|
||||||
date: 2024-07-23
|
date: 2024-03-07
|
||||||
tags:
|
tags:
|
||||||
- Development
|
- Development
|
||||||
---
|
---
|
||||||
|
|||||||
@ -76,9 +76,7 @@ export const DocumentsDataTable = ({
|
|||||||
{
|
{
|
||||||
header: 'Recipient',
|
header: 'Recipient',
|
||||||
accessorKey: 'recipient',
|
accessorKey: 'recipient',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => <StackAvatarsWithTooltip recipients={row.original.Recipient} />,
|
||||||
return <StackAvatarsWithTooltip recipients={row.original.Recipient} />;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -112,7 +112,7 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
|
|||||||
variant="destructive"
|
variant="destructive"
|
||||||
disabled={hasTwoFactorAuthentication}
|
disabled={hasTwoFactorAuthentication}
|
||||||
>
|
>
|
||||||
{isDeletingAccount ? 'Deleting account...' : 'Delete Account'}
|
{isDeletingAccount ? 'Deleting account...' : 'Confirm Deletion'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import type { Recipient } from '@documenso/prisma/client';
|
import type { Recipient } from '@documenso/prisma/client';
|
||||||
import {
|
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@documenso/ui/primitives/tooltip';
|
|
||||||
|
|
||||||
import { AvatarWithRecipient } from './avatar-with-recipient';
|
import { AvatarWithRecipient } from './avatar-with-recipient';
|
||||||
import { StackAvatar } from './stack-avatar';
|
import { StackAvatar } from './stack-avatar';
|
||||||
@ -24,6 +23,11 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
position,
|
position,
|
||||||
children,
|
children,
|
||||||
}: StackAvatarsWithTooltipProps) => {
|
}: StackAvatarsWithTooltipProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const isControlled = useRef(false);
|
||||||
|
const isMouseOverTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const waitingRecipients = recipients.filter(
|
const waitingRecipients = recipients.filter(
|
||||||
(recipient) => getRecipientType(recipient) === 'waiting',
|
(recipient) => getRecipientType(recipient) === 'waiting',
|
||||||
);
|
);
|
||||||
@ -40,15 +44,56 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
(recipient) => getRecipientType(recipient) === 'unsigned',
|
(recipient) => getRecipientType(recipient) === 'unsigned',
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const onMouseEnter = () => {
|
||||||
<TooltipProvider>
|
if (isMouseOverTimeout.current) {
|
||||||
<Tooltip delayDuration={200}>
|
clearTimeout(isMouseOverTimeout.current);
|
||||||
<TooltipTrigger className="flex cursor-pointer">
|
}
|
||||||
{children || <StackAvatars recipients={recipients} />}
|
|
||||||
</TooltipTrigger>
|
|
||||||
|
|
||||||
<TooltipContent side={position}>
|
if (isControlled.current) {
|
||||||
<div className="flex flex-col gap-y-5 p-1">
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMouseOverTimeout.current = setTimeout(() => {
|
||||||
|
setOpen((o) => (!o ? true : o));
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseLeave = () => {
|
||||||
|
if (isMouseOverTimeout.current) {
|
||||||
|
clearTimeout(isMouseOverTimeout.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isControlled.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpen((o) => (o ? false : o));
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenChange = (newOpen: boolean) => {
|
||||||
|
isControlled.current = newOpen;
|
||||||
|
|
||||||
|
setOpen(newOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={onOpenChange}>
|
||||||
|
<PopoverTrigger
|
||||||
|
className="flex cursor-pointer"
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
{children || <StackAvatars recipients={recipients} />}
|
||||||
|
</PopoverTrigger>
|
||||||
|
|
||||||
|
<PopoverContent
|
||||||
|
side={position}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
className="flex flex-col gap-y-5 py-2"
|
||||||
|
>
|
||||||
{completedRecipients.length > 0 && (
|
{completedRecipients.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-base font-medium">Completed</h1>
|
<h1 className="text-base font-medium">Completed</h1>
|
||||||
@ -97,9 +142,7 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</PopoverContent>
|
||||||
</TooltipContent>
|
</Popover>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -227,9 +227,9 @@ export const SignUpFormV2 = ({
|
|||||||
<div className="border-border dark:bg-background relative z-10 flex min-h-[min(800px,80vh)] w-full max-w-lg flex-col rounded-xl border bg-neutral-100 p-6">
|
<div className="border-border dark:bg-background relative z-10 flex min-h-[min(800px,80vh)] w-full max-w-lg flex-col rounded-xl border bg-neutral-100 p-6">
|
||||||
{step === 'BASIC_DETAILS' && (
|
{step === 'BASIC_DETAILS' && (
|
||||||
<div className="h-20">
|
<div className="h-20">
|
||||||
<h1 className="text-2xl font-semibold">Create a new account</h1>
|
<h1 className="text-xl font-semibold md:text-2xl">Create a new account</h1>
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm">
|
<p className="text-muted-foreground mt-2 text-xs md:text-sm">
|
||||||
Create your account and start using state-of-the-art document signing. Open and
|
Create your account and start using state-of-the-art document signing. Open and
|
||||||
beautiful signing is within your grasp.
|
beautiful signing is within your grasp.
|
||||||
</p>
|
</p>
|
||||||
@ -238,9 +238,9 @@ export const SignUpFormV2 = ({
|
|||||||
|
|
||||||
{step === 'CLAIM_USERNAME' && (
|
{step === 'CLAIM_USERNAME' && (
|
||||||
<div className="h-20">
|
<div className="h-20">
|
||||||
<h1 className="text-2xl font-semibold">Claim your username now</h1>
|
<h1 className="text-xl font-semibold md:text-2xl">Claim your username now</h1>
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm">
|
<p className="text-muted-foreground mt-2 text-xs md:text-sm">
|
||||||
You will get notified & be able to set up your documenso public profile when we launch
|
You will get notified & be able to set up your documenso public profile when we launch
|
||||||
the feature.
|
the feature.
|
||||||
</p>
|
</p>
|
||||||
@ -378,7 +378,7 @@ export const SignUpFormV2 = ({
|
|||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="bg-muted/50 border-border text-muted-foreground mt-2 inline-block truncate rounded-md border px-2 py-1 text-sm lowercase">
|
<div className="bg-muted/50 border-border text-muted-foreground mt-2 inline-block max-w-[16rem] truncate rounded-md border px-2 py-1 text-sm lowercase">
|
||||||
{baseUrl.host}/u/{field.value || '<username>'}
|
{baseUrl.host}/u/{field.value || '<username>'}
|
||||||
</div>
|
</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -24,9 +24,8 @@ export const UserProfileSkeleton = ({ className, user, rows = 2 }: UserProfileSk
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="border-border bg-background text-muted-foreground inline-flex items-center rounded-md border px-2.5 py-1.5 text-sm">
|
<div className="border-border bg-background text-muted-foreground inline-block max-w-full truncate rounded-md border px-2.5 py-1.5 text-sm">
|
||||||
<span>{baseUrl.host}/u/</span>
|
{baseUrl.host}/u/{user.url}
|
||||||
<span className="inline-block max-w-[8rem] truncate lowercase">{user.url}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export const UserProfileTimur = ({ className, rows = 2 }: UserProfileTimurProps)
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="border-border bg-background text-muted-foreground inline-block rounded-md border px-2.5 py-1.5 text-sm">
|
<div className="border-border bg-background text-muted-foreground inline-block max-w-full truncate rounded-md border px-2.5 py-1.5 text-sm">
|
||||||
{baseUrl.host}/u/timur
|
{baseUrl.host}/u/timur
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -60,15 +60,13 @@ docker pull ghcr.io/documenso/documenso
|
|||||||
```
|
```
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-p 3000:3000 \
|
-p 3000:3000 \
|
||||||
-e POSTGRES_USER="<your-postgres-user>"
|
|
||||||
-e POSTGRES_PASSWORD="<your-postgres-password>"
|
|
||||||
-e POSTGRES_DB="<your-postgres-db>"
|
|
||||||
-e NEXTAUTH_URL="<your-nextauth-url>"
|
-e NEXTAUTH_URL="<your-nextauth-url>"
|
||||||
-e NEXTAUTH_SECRET="<your-nextauth-secret>"
|
-e NEXTAUTH_SECRET="<your-nextauth-secret>"
|
||||||
-e NEXT_PRIVATE_ENCRYPTION_KEY="<your-next-private-encryption-key>"
|
-e NEXT_PRIVATE_ENCRYPTION_KEY="<your-next-private-encryption-key>"
|
||||||
-e NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="<your-next-private-encryption-secondary-key>"
|
-e NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="<your-next-private-encryption-secondary-key>"
|
||||||
-e NEXT_PUBLIC_WEBAPP_URL="<your-next-public-webapp-url>"
|
-e NEXT_PUBLIC_WEBAPP_URL="<your-next-public-webapp-url>"
|
||||||
-e NEXT_PRIVATE_DATABASE_URL="<your-next-private-database-url>"
|
-e NEXT_PRIVATE_DATABASE_URL="<your-next-private-database-url>"
|
||||||
|
-e NEXT_PRIVATE_DIRECT_DATABASE_URL="<your-next-private-database-url>"
|
||||||
-e NEXT_PRIVATE_SMTP_TRANSPORT="<your-next-private-smtp-transport>"
|
-e NEXT_PRIVATE_SMTP_TRANSPORT="<your-next-private-smtp-transport>"
|
||||||
-e NEXT_PRIVATE_SMTP_FROM_NAME="<your-next-private-smtp-from-name>"
|
-e NEXT_PRIVATE_SMTP_FROM_NAME="<your-next-private-smtp-from-name>"
|
||||||
-e NEXT_PRIVATE_SMTP_FROM_ADDRESS="<your-next-private-smtp-from-address>"
|
-e NEXT_PRIVATE_SMTP_FROM_ADDRESS="<your-next-private-smtp-from-address>"
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
"commitlint": "commitlint --edit",
|
"commitlint": "commitlint --edit",
|
||||||
"clean": "turbo run clean && rimraf node_modules",
|
"clean": "turbo run clean && rimraf node_modules",
|
||||||
"d": "npm run dx && npm run dev",
|
"d": "npm run dx && npm run dev",
|
||||||
"dx": "npm i && npm run dx:up && npm run prisma:migrate-dev",
|
"dx": "npm i && npm run dx:up && npm run prisma:migrate-dev && npm run prisma:seed",
|
||||||
"dx:up": "docker compose -f docker/development/compose.yml up -d",
|
"dx:up": "docker compose -f docker/development/compose.yml up -d",
|
||||||
"dx:down": "docker compose -f docker/development/compose.yml down",
|
"dx:down": "docker compose -f docker/development/compose.yml down",
|
||||||
"ci": "turbo run test:e2e",
|
"ci": "turbo run test:e2e",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"prisma:migrate-dev": "npm run with:env -- npm run prisma:migrate-dev -w @documenso/prisma",
|
"prisma:migrate-dev": "npm run with:env -- npm run prisma:migrate-dev -w @documenso/prisma",
|
||||||
"prisma:migrate-deploy": "npm run with:env -- npm run prisma:migrate-deploy -w @documenso/prisma",
|
"prisma:migrate-deploy": "npm run with:env -- npm run prisma:migrate-deploy -w @documenso/prisma",
|
||||||
"prisma:seed": "npm run with:env -- npm run prisma:seed -w @documenso/prisma",
|
"prisma:seed": "npm run with:env -- npm run prisma:seed -w @documenso/prisma",
|
||||||
"prisma:studio": "npm run with:env -- npx prisma studio --schema packages/prisma/schema.prisma",
|
"prisma:studio": "npm run with:env -- npm run prisma:studio -w @documenso/prisma",
|
||||||
"with:env": "dotenv -e .env -e .env.local --",
|
"with:env": "dotenv -e .env -e .env.local --",
|
||||||
"reset:hard": "npm run clean && npm i && npm run prisma:generate",
|
"reset:hard": "npm run clean && npm i && npm run prisma:generate",
|
||||||
"precommit": "npm install && git add package.json package-lock.json"
|
"precommit": "npm install && git add package.json package-lock.json"
|
||||||
|
|||||||
25
packages/app-tests/e2e/test-delete-user.spec.ts
Normal file
25
packages/app-tests/e2e/test-delete-user.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||||
|
import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-email';
|
||||||
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
|
import { manualLogin } from './fixtures/authentication';
|
||||||
|
|
||||||
|
test('delete user', async ({ page }) => {
|
||||||
|
const user = await seedUser();
|
||||||
|
|
||||||
|
await manualLogin({
|
||||||
|
page,
|
||||||
|
email: user.email,
|
||||||
|
redirectPath: '/settings',
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Delete Account' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Confirm Deletion' }).click();
|
||||||
|
|
||||||
|
await page.waitForURL(`${WEBAPP_BASE_URL}/signin`);
|
||||||
|
|
||||||
|
// Verify that the user no longer exists in the database
|
||||||
|
await expect(getUserByEmail({ email: user.email })).rejects.toThrow();
|
||||||
|
});
|
||||||
@ -10,9 +10,10 @@
|
|||||||
"clean": "rimraf node_modules",
|
"clean": "rimraf node_modules",
|
||||||
"post-install": "prisma generate",
|
"post-install": "prisma generate",
|
||||||
"prisma:generate": "prisma generate",
|
"prisma:generate": "prisma generate",
|
||||||
"prisma:migrate-dev": "prisma migrate dev",
|
"prisma:migrate-dev": "prisma migrate dev --skip-seed",
|
||||||
"prisma:migrate-deploy": "prisma migrate deploy",
|
"prisma:migrate-deploy": "prisma migrate deploy",
|
||||||
"prisma:seed": "prisma db seed"
|
"prisma:seed": "prisma db seed",
|
||||||
|
"prisma:studio": "prisma studio"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "ts-node --transpileOnly --project ./tsconfig.seed.json ./seed-database.ts"
|
"seed": "ts-node --transpileOnly --project ./tsconfig.seed.json ./seed-database.ts"
|
||||||
|
|||||||
@ -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))',
|
||||||
|
|||||||
120
packages/ui/lib/document-dropzone-constants.ts
Normal file
120
packages/ui/lib/document-dropzone-constants.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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: 50,
|
||||||
|
y: 0,
|
||||||
|
rotate: -14,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: 50,
|
||||||
|
y: 0,
|
||||||
|
rotate: -14,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: 30,
|
||||||
|
y: 0,
|
||||||
|
rotate: -17,
|
||||||
|
transition: { type: 'spring', duration: 0.3, stiffness: 500 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneDisabledCardRightVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: -50,
|
||||||
|
y: 0,
|
||||||
|
rotate: 14,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: -50,
|
||||||
|
y: 0,
|
||||||
|
rotate: 14,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: -30,
|
||||||
|
y: 0,
|
||||||
|
rotate: 17,
|
||||||
|
transition: { type: 'spring', duration: 0.3, stiffness: 500 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentDropzoneDisabledCardCenterVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
x: -10,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
x: -10,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
x: [-15, -10, -5, -10],
|
||||||
|
y: 0,
|
||||||
|
transition: { type: 'spring', duration: 0.3, stiffness: 1000 },
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -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, IS_BILLING_ENABLED } 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;
|
||||||
@ -123,33 +60,68 @@ export const DocumentDropzone = ({
|
|||||||
maxSize: megabytesToBytes(APP_DOCUMENT_UPLOAD_SIZE_LIMIT),
|
maxSize: megabytesToBytes(APP_DOCUMENT_UPLOAD_SIZE_LIMIT),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const heading = {
|
||||||
|
document: disabled ? 'You have reached your document limit.' : 'Add a document',
|
||||||
|
template: 'Upload Template Document',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className={cn('flex aria-disabled:cursor-not-allowed', 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 group 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 ? (
|
||||||
|
// Disabled State
|
||||||
|
<div className="flex">
|
||||||
|
<motion.div
|
||||||
|
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={DocumentDropzoneDisabledCardLeftVariants}
|
||||||
|
>
|
||||||
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-full 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/10 group-hover:bg-destructive/10 h-2 w-full rounded-[2px]" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
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={DocumentDropzoneDisabledCardCenterVariants}
|
||||||
|
>
|
||||||
|
<AlertTriangle
|
||||||
|
strokeWidth="2px"
|
||||||
|
className="text-muted-foreground/20 group-hover:text-destructive h-12 w-12"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
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={DocumentDropzoneDisabledCardRightVariants}
|
||||||
|
>
|
||||||
|
<div className="bg-muted-foreground/10 group-hover:bg-destructive/10 h-2 w-full 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/10 group-hover:bg-destructive/10 h-2 w-full rounded-[2px]" />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Non Disabled State
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<motion.div
|
<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"
|
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}
|
variants={DocumentDropzoneCardLeftVariants}
|
||||||
>
|
>
|
||||||
<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-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-5/6 rounded-[2px]" />
|
||||||
@ -158,7 +130,7 @@ export const DocumentDropzone = ({
|
|||||||
|
|
||||||
<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="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}
|
variants={DocumentDropzoneCardCenterVariants}
|
||||||
>
|
>
|
||||||
<Plus
|
<Plus
|
||||||
strokeWidth="2px"
|
strokeWidth="2px"
|
||||||
@ -168,23 +140,28 @@ export const DocumentDropzone = ({
|
|||||||
|
|
||||||
<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="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}
|
variants={DocumentDropzoneCardRightVariants}
|
||||||
>
|
>
|
||||||
<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-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-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/20 group-hover:bg-documenso h-2 w-full rounded-[2px]" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</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">{heading[type]}</p>
|
||||||
{DocumentDescription[type].headline}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="text-muted-foreground/80 mt-1 text-sm">
|
<p className="text-muted-foreground/80 mt-1 text-center text-sm">
|
||||||
{disabled ? disabledMessage : 'Drag & drop your PDF here.'}
|
{disabled ? disabledMessage : 'Drag & drop your PDF here.'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{disabled && IS_BILLING_ENABLED() && (
|
||||||
|
<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