diff --git a/README.md b/README.md
index f70e7425a..93c6d9f95 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@
-
+
diff --git a/apps/marketing/content/blog/removing-server-actions.mdx b/apps/marketing/content/blog/removing-server-actions.mdx
index 7e53c5b58..36dabdf9d 100644
--- a/apps/marketing/content/blog/removing-server-actions.mdx
+++ b/apps/marketing/content/blog/removing-server-actions.mdx
@@ -4,7 +4,7 @@ description: 'This article talks about the need for the public API and the proce
authorName: 'Lucas Smith'
authorImage: '/blog/blog-author-lucas.png'
authorRole: 'Co-Founder'
-date: 2024-07-23
+date: 2024-03-07
tags:
- Development
---
diff --git a/apps/web/src/app/(dashboard)/documents/data-table.tsx b/apps/web/src/app/(dashboard)/documents/data-table.tsx
index 1adaace7b..d36860032 100644
--- a/apps/web/src/app/(dashboard)/documents/data-table.tsx
+++ b/apps/web/src/app/(dashboard)/documents/data-table.tsx
@@ -76,9 +76,7 @@ export const DocumentsDataTable = ({
{
header: 'Recipient',
accessorKey: 'recipient',
- cell: ({ row }) => {
- return ;
- },
+ cell: ({ row }) => ,
},
{
header: 'Status',
diff --git a/apps/web/src/app/(dashboard)/documents/upload-document.tsx b/apps/web/src/app/(dashboard)/documents/upload-document.tsx
index 28ddd4318..26f1e795c 100644
--- a/apps/web/src/app/(dashboard)/documents/upload-document.tsx
+++ b/apps/web/src/app/(dashboard)/documents/upload-document.tsx
@@ -2,7 +2,6 @@
import { useMemo, useState } from 'react';
-import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
@@ -139,27 +138,6 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
)}
-
- {team?.id === undefined && remaining.documents === 0 && (
-
-
-
- You have reached your document limit.
-
-
-
- You can upload up to {quota.documents} documents per month on your current plan.
-
-
-
- Upgrade your account to upload more documents.
-
-
-
- )}
);
};
diff --git a/apps/web/src/app/(dashboard)/settings/profile/delete-account-dialog.tsx b/apps/web/src/app/(dashboard)/settings/profile/delete-account-dialog.tsx
index 933b37f31..e9cc885e9 100644
--- a/apps/web/src/app/(dashboard)/settings/profile/delete-account-dialog.tsx
+++ b/apps/web/src/app/(dashboard)/settings/profile/delete-account-dialog.tsx
@@ -112,7 +112,7 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
variant="destructive"
disabled={hasTwoFactorAuthentication}
>
- {isDeletingAccount ? 'Deleting account...' : 'Delete Account'}
+ {isDeletingAccount ? 'Deleting account...' : 'Confirm Deletion'}
diff --git a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx
index bd7bea2b0..10f7d1e6a 100644
--- a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx
+++ b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx
@@ -1,13 +1,12 @@
+'use client';
+
+import { useRef, useState } from 'react';
+
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import type { Recipient } from '@documenso/prisma/client';
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from '@documenso/ui/primitives/tooltip';
+import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { AvatarWithRecipient } from './avatar-with-recipient';
import { StackAvatar } from './stack-avatar';
@@ -24,6 +23,11 @@ export const StackAvatarsWithTooltip = ({
position,
children,
}: StackAvatarsWithTooltipProps) => {
+ const [open, setOpen] = useState(false);
+
+ const isControlled = useRef(false);
+ const isMouseOverTimeout = useRef(null);
+
const waitingRecipients = recipients.filter(
(recipient) => getRecipientType(recipient) === 'waiting',
);
@@ -40,66 +44,105 @@ export const StackAvatarsWithTooltip = ({
(recipient) => getRecipientType(recipient) === 'unsigned',
);
+ const onMouseEnter = () => {
+ if (isMouseOverTimeout.current) {
+ clearTimeout(isMouseOverTimeout.current);
+ }
+
+ if (isControlled.current) {
+ 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 (
-
-
-
- {children || }
-
+
+
+ {children || }
+
-
-
- {completedRecipients.length > 0 && (
-
-
Completed
- {completedRecipients.map((recipient: Recipient) => (
-
-
-
-
{recipient.email}
-
- {RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
-
-
-
- ))}
+
+ {completedRecipients.length > 0 && (
+
+
Completed
+ {completedRecipients.map((recipient: Recipient) => (
+
+
+
+
{recipient.email}
+
+ {RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
+
+
- )}
-
- {waitingRecipients.length > 0 && (
-
-
Waiting
- {waitingRecipients.map((recipient: Recipient) => (
-
- ))}
-
- )}
-
- {openedRecipients.length > 0 && (
-
-
Opened
- {openedRecipients.map((recipient: Recipient) => (
-
- ))}
-
- )}
-
- {uncompletedRecipients.length > 0 && (
-
-
Uncompleted
- {uncompletedRecipients.map((recipient: Recipient) => (
-
- ))}
-
- )}
+ ))}
-
-
-
+ )}
+
+ {waitingRecipients.length > 0 && (
+
+
Waiting
+ {waitingRecipients.map((recipient: Recipient) => (
+
+ ))}
+
+ )}
+
+ {openedRecipients.length > 0 && (
+
+
Opened
+ {openedRecipients.map((recipient: Recipient) => (
+
+ ))}
+
+ )}
+
+ {uncompletedRecipients.length > 0 && (
+
+
Uncompleted
+ {uncompletedRecipients.map((recipient: Recipient) => (
+
+ ))}
+
+ )}
+
+
);
};
diff --git a/apps/web/src/components/forms/v2/signup.tsx b/apps/web/src/components/forms/v2/signup.tsx
index 189116e01..a7e33a759 100644
--- a/apps/web/src/components/forms/v2/signup.tsx
+++ b/apps/web/src/components/forms/v2/signup.tsx
@@ -227,9 +227,9 @@ export const SignUpFormV2 = ({
{step === 'BASIC_DETAILS' && (
-
Create a new account
+
Create a new account
-
+
Create your account and start using state-of-the-art document signing. Open and
beautiful signing is within your grasp.
@@ -238,9 +238,9 @@ export const SignUpFormV2 = ({
{step === 'CLAIM_USERNAME' && (
-
Claim your username now
+
Claim your username now
-
+
You will get notified & be able to set up your documenso public profile when we launch
the feature.
@@ -378,7 +378,7 @@ export const SignUpFormV2 = ({
-
+
{baseUrl.host}/u/{field.value || ''}
diff --git a/apps/web/src/components/ui/user-profile-skeleton.tsx b/apps/web/src/components/ui/user-profile-skeleton.tsx
index 1c8f35b64..c8b8b808a 100644
--- a/apps/web/src/components/ui/user-profile-skeleton.tsx
+++ b/apps/web/src/components/ui/user-profile-skeleton.tsx
@@ -24,9 +24,8 @@ export const UserProfileSkeleton = ({ className, user, rows = 2 }: UserProfileSk
className,
)}
>
-
-
{baseUrl.host}/u/
-
{user.url}
+
+ {baseUrl.host}/u/{user.url}
diff --git a/apps/web/src/components/ui/user-profile-timur.tsx b/apps/web/src/components/ui/user-profile-timur.tsx
index e99a314b4..8d1e517ad 100644
--- a/apps/web/src/components/ui/user-profile-timur.tsx
+++ b/apps/web/src/components/ui/user-profile-timur.tsx
@@ -25,7 +25,7 @@ export const UserProfileTimur = ({ className, rows = 2 }: UserProfileTimurProps)
className,
)}
>
-
+
{baseUrl.host}/u/timur
diff --git a/docker/README.md b/docker/README.md
index addb278c4..ba942ac1c 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -60,15 +60,13 @@ docker pull ghcr.io/documenso/documenso
```
docker run -d \
-p 3000:3000 \
- -e POSTGRES_USER="
"
- -e POSTGRES_PASSWORD=""
- -e POSTGRES_DB=""
-e NEXTAUTH_URL=""
-e NEXTAUTH_SECRET=""
-e NEXT_PRIVATE_ENCRYPTION_KEY=""
-e NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=""
-e NEXT_PUBLIC_WEBAPP_URL=""
-e NEXT_PRIVATE_DATABASE_URL=""
+ -e NEXT_PRIVATE_DIRECT_DATABASE_URL=""
-e NEXT_PRIVATE_SMTP_TRANSPORT=""
-e NEXT_PRIVATE_SMTP_FROM_NAME=""
-e NEXT_PRIVATE_SMTP_FROM_ADDRESS=""
diff --git a/package.json b/package.json
index c25aed514..cbaa2a1eb 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"commitlint": "commitlint --edit",
"clean": "turbo run clean && rimraf node_modules",
"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:down": "docker compose -f docker/development/compose.yml down",
"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-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: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 --",
"reset:hard": "npm run clean && npm i && npm run prisma:generate",
"precommit": "npm install && git add package.json package-lock.json"
diff --git a/packages/app-tests/e2e/test-delete-user.spec.ts b/packages/app-tests/e2e/test-delete-user.spec.ts
new file mode 100644
index 000000000..beae6eb09
--- /dev/null
+++ b/packages/app-tests/e2e/test-delete-user.spec.ts
@@ -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();
+});
diff --git a/packages/prisma/package.json b/packages/prisma/package.json
index 0cd3ed282..199ce197a 100644
--- a/packages/prisma/package.json
+++ b/packages/prisma/package.json
@@ -10,9 +10,10 @@
"clean": "rimraf node_modules",
"post-install": "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:seed": "prisma db seed"
+ "prisma:seed": "prisma db seed",
+ "prisma:studio": "prisma studio"
},
"prisma": {
"seed": "ts-node --transpileOnly --project ./tsconfig.seed.json ./seed-database.ts"
diff --git a/packages/tailwind-config/index.cjs b/packages/tailwind-config/index.cjs
index 81706fd37..92222462f 100644
--- a/packages/tailwind-config/index.cjs
+++ b/packages/tailwind-config/index.cjs
@@ -28,6 +28,9 @@ module.exports = {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
+ warning: {
+ DEFAULT: 'hsl(var(--warning))',
+ },
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
diff --git a/packages/ui/lib/document-dropzone-constants.ts b/packages/ui/lib/document-dropzone-constants.ts
new file mode 100644
index 000000000..dd12acf97
--- /dev/null
+++ b/packages/ui/lib/document-dropzone-constants.ts
@@ -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 },
+ },
+};
diff --git a/packages/ui/primitives/document-dropzone.tsx b/packages/ui/primitives/document-dropzone.tsx
index 6caf6d040..c0006a5f6 100644
--- a/packages/ui/primitives/document-dropzone.tsx
+++ b/packages/ui/primitives/document-dropzone.tsx
@@ -1,90 +1,27 @@
'use client';
-import type { Variants } from 'framer-motion';
+import Link from 'next/link';
+
import { motion } from 'framer-motion';
-import { Plus } from 'lucide-react';
+import { AlertTriangle, Plus } from 'lucide-react';
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 {
+ 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';
-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 = {
className?: string;
disabled?: boolean;
@@ -123,68 +60,108 @@ export const DocumentDropzone = ({
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 (
- {/* */}
-
-
-
-
-
-
+ {disabled ? (
+ // Disabled State
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+ ) : (
+ // Non Disabled State
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
-
- {DocumentDescription[type].headline}
-
+ {heading[type]}
-
+
{disabled ? disabledMessage : 'Drag & drop your PDF here.'}
+
+ {disabled && IS_BILLING_ENABLED() && (
+
+ )}
diff --git a/packages/ui/styles/theme.css b/packages/ui/styles/theme.css
index 58dbc892d..8e488ad95 100644
--- a/packages/ui/styles/theme.css
+++ b/packages/ui/styles/theme.css
@@ -42,6 +42,8 @@
--ring: 95.08 71.08% 67.45%;
--radius: 0.5rem;
+
+ --warning: 54 96% 45%;
}
.dark {
@@ -79,6 +81,8 @@
--ring: 95.08 71.08% 67.45%;
--radius: 0.5rem;
+
+ --warning: 54 96% 45%;
}
}