diff --git a/README.md b/README.md index f70e7425a..93c6d9f95 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ open in devcontainer - Contributor Covenant + Contributor Covenant

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