Compare commits

..

61 Commits

Author SHA1 Message Date
Lucas Smith
c2cf25b138 fix: templates incorrectly linking when in a team 2024-03-11 01:46:19 +00:00
Lucas Smith
1a23744d2a fix: table layout shift while changing tabs (#921)
fixes: #875 



https://github.com/documenso/documenso/assets/28510494/083fd87a-ef62-40e6-9696-9c04b4411502
2024-03-11 12:16:19 +11:00
Lucas Smith
7f31ab1ce3 fix: add scrollbar gutter property 2024-03-11 00:52:56 +00:00
Lucas Smith
7ac34099ee fix: ensure password input is cleared when 2fa enable dialog is closed (#1014)
Issue found by guatam in Documenso Discord.

https://discord.com/channels/1132216843537485854/1133420505030987857/1215716399822147664

Bug Loom:

https://www.loom.com/share/b7defde91dfb437580c13c7f166f59ff?sid=cbca746f-174a-44ad-997c-cf3a2eef8380

Fix Loom:

https://www.loom.com/share/8748ac47459247a49ddf7d5379e2a0a0?sid=02977122-c150-44e8-9a19-4c8356c298d7
2024-03-11 11:26:35 +11:00
Lucas Smith
c744482b84 fix: add conditional to useEffects 2024-03-11 10:55:46 +11:00
Lucas Smith
b71cb66dd3 fix: show close icon on notification in mobile (#996)
As per the requirement on the mobile, the close icon will always be
visible

On the desktop, close icon will be visible on hover.

fixes #965
2024-03-10 22:12:29 +11:00
Brayden Brayden
afe99e5ec9 fix: revert reset changes, reset on open state change instead 2024-03-10 09:36:54 +00:00
Brayden Brayden
6f958b9320 fix: update the dialog cancel to reset 2024-03-10 09:28:08 +00:00
Brayden Brayden
f646fa29d7 fix: ensure password input is cleared when 2fa enable dialog is closed 2024-03-10 07:01:18 +00:00
Lucas Smith
373e806bd9 fix: eslint config file parseOptions.project path is updated (#1008)
I found out that the problem of slow down is the use of
parseOption.project with multiple tsconfig files which increases usage
of memory consumption quite a lot.

Here is the reference I found to resolve this issue:

[reference
link](https://github.com/typescript-eslint/typescript-eslint/issues/1192)
[Doc
link](https://typescript-eslint.io/getting-started/typed-linting/monorepos/)

Based on this reference I created the solution and tested out locally it
does reduce the eslint time.

#1002
2024-03-10 15:32:59 +11:00
Lucas Smith
c3c00dfe05 fix: update docker documentation and include cert env vars (#1013)
## Description

Updates the docker documentation and compose files to include key file
related configuration for self hosters. This was missed as the key file
environment variables weren't documented in the `.env.example` file
which was used for creating the docker documentation.

## Related Issue

#1012

## Changes Made

- Updated the docker configuration to include key file environment
variables
- Updated the .env.example to include key file env vars
- Updated the compose images to use volumes for key files

## Testing Performed

- I have used the containers with the updated values and confirmed that
they work as expected

## Checklist

- [x] I have tested these changes locally and they work as expected.
- [x] I have added/updated tests that prove the effectiveness of these
changes.
- [x] I have updated the documentation to reflect these changes, if
applicable.
- [x] I have followed the project's coding style guidelines.
- [x] I have addressed the code review feedback from the previous
submission, if applicable.

## Additional Notes

N/A
2024-03-10 14:11:47 +11:00
Mythie
9e1b2e5cc3 fix: update sharp dependency 2024-03-10 13:48:25 +11:00
Mythie
608e622f69 fix: update testing compose config 2024-03-10 13:48:09 +11:00
Mythie
415f79f821 fix: update docker docs and compose files 2024-03-10 11:13:05 +11:00
Rohit Saluja
3b5f8d149a fix: eslint config file parseOptions.project path is updated 2024-03-08 21:14:37 +05:30
Lucas Smith
4476cf8fd1 Merge branch 'main' into fix/layout-shift-on-table 2024-03-09 00:41:20 +11:00
Lucas Smith
05aa52b44a fix: fixed the recipients viewing issue on touch screens (#773)
## Description

This pull request addresses the issue where users on touchscreen devices
couldn't properly see the Recipients list. I have implemented a two-easy
solution to address this problem, offering a seamless user experience
across device types:

1. Popover for Touchscreen: For touchscreen devices, I have added a
popover that displays the Recipients list when users tap on the avatars.

2. Tooltip for Larger Screens: On larger screens (desktops and laptops),
I have added a tooltip that appears when users hover over the avatars.

## Changes
1. Added `Popover` for smaller devices and keep the `Tooltip` for larger
devices.
2. Renamed the component `stack-avatars-wtih-tooltip` to
`stack-avatars-wtih-ui` because now it contains both Tooltip and
Popover.
3. Used the `useWindowSize` hook to conditionally render the `Tooltip`
and `Popover`
4. To avoid repeating the same code, I've created a new component named
`stack-avatars-components.tsx` to show the recipient's details. This
component uses both Popover and Tooltip.


## PR Preview



https://github.com/documenso/documenso/assets/87828904/2dc9b056-b4bd-43dd-b427-a0e803dee55a


## Issue

Closes #756
2024-03-08 23:59:11 +11:00
Lucas Smith
40343d1c72 fix: add use client directive 2024-03-08 12:34:49 +00:00
Lucas Smith
08201240d2 Merge branch 'main' into update-documents-avatar 2024-03-08 23:26:28 +11:00
Lucas Smith
32b0b1bcda fix: revert api change and use mouseenter/mouseleave 2024-03-08 12:21:32 +00:00
Lucas Smith
fd4ea3aca5 fix: update docker docs 2024-03-08 20:00:24 +11:00
premiare
1b32c5a17f fix: fix blog post date (#1003)
Fixing the blog date for
https://documenso.com/blog/removing-server-actions
(I assume it was meant to be March 7th)


![image](https://github.com/documenso/documenso/assets/64188227/a7b96168-b094-46c0-877a-da26c9d140d4)
2024-03-08 09:27:30 +02:00
Lucas Smith
6376112f9d fix: overflow issue with user name input (#991)
Before:-

![Screenshot 2024-03-06
203158](https://github.com/documenso/documenso/assets/81948346/17050582-454b-49af-8124-294d0a0be5bc)

After:-

![Screenshot 2024-03-06
202332](https://github.com/documenso/documenso/assets/81948346/7c346699-3bff-4847-95ef-fd7fdc8a89af)
2024-03-08 15:24:17 +11:00
Lucas Smith
ddfd4b9e1b fix: update styling 2024-03-08 03:59:15 +00:00
Lucas Smith
5bec549868 feat: improved ui of document dropzone for max quota state (#997)
**Description:**


[Dropzone.webm](https://github.com/documenso/documenso/assets/23498248/df2d3a54-0e39-4d2d-b792-bf4cd4a1e19d)
2024-03-08 14:38:27 +11:00
Lucas Smith
2f728f401b chore: add e2e test for deleting a user (#1001) 2024-03-08 14:32:06 +11:00
Lucas Smith
bc60278bac fix: remove useless ternaries 2024-03-08 03:30:57 +00:00
Lucas Smith
e9664d6369 chore: tidy code 2024-03-08 03:23:27 +00:00
Lucas Smith
3b3346e6af fix: remove data-testid attributes 2024-03-08 13:47:59 +11:00
Lucas Smith
ee35b4a24b fix: update test to use getByRole 2024-03-08 02:46:25 +00:00
Lucas Smith
47b06fa290 Merge branch 'main' into test/delete-user 2024-03-08 13:30:24 +11:00
Lucas Smith
b9dccdb359 chore: updated code of conduct link (#999)
**Description:**

Updated broken link to code of conduct in the Readme
2024-03-08 12:54:23 +11:00
Lucas Smith
5e00280486 fix: add seed script to dx setup (#1000) 2024-03-08 12:53:08 +11:00
Lucas Smith
f0fd5506fc fix: skip seeding when running migrate dev
When prisma:migrate-dev needs to reset the database it will run the seed script to repopulate data. Now that we've added the seed script to our root setup command we will want to avoid this behaviour since we will end up double seeding the database which currently can cause issues.
2024-03-08 12:49:55 +11:00
Ephraim Atta-Duncan
ff3b49656c chore: remove unused function 2024-03-08 00:07:11 +00:00
Ephraim Atta-Duncan
e47ca1d6b6 chore: add e2e test for deleting a user 2024-03-08 00:04:27 +00:00
Ephraim Atta-Duncan
59cdf3203e fix: add seed script to dx setup 2024-03-07 20:09:29 +00:00
Adithya Krishna
92f44cd304 chore: remove trailing slash
Signed-off-by: Adithya Krishna <adithya@documenso.com>
2024-03-08 00:36:18 +05:30
Adithya Krishna
3ce5b9e0a0 chore: updated code_of_conduct link
Signed-off-by: Adithya Krishna <adithya@documenso.com>
2024-03-08 00:33:15 +05:30
Adithya Krishna
e7f8f4e188 Merge branch 'main' of https://github.com/documenso/documenso into feat/doc-limit-improvement 2024-03-07 21:08:04 +05:30
Adithya Krishna
6c9303012c chore: updated animation
Signed-off-by: Adithya Krishna <adithya@documenso.com>
2024-03-07 21:06:16 +05:30
Anik Dhabal Babu
f7e7c6dedf fix: overflow issue 2024-03-07 20:08:11 +05:30
Adithya Krishna
0c426983bb chore: updated initial animation state
Signed-off-by: Adithya Krishna <adithya@documenso.com>
2024-03-07 19:28:35 +05:30
Adithya Krishna
9ae51a0072 feat: improved ui of document dropzone for max quota state
Signed-off-by: Adithya Krishna <adithya@documenso.com>
2024-03-07 19:04:58 +05:30
Rohit Saluja
d694f4a17b fix: show close icon on notification inmobile 2024-03-07 18:48:23 +05:30
Lucas Smith
80f767b321 chore: update docker section in readme 2024-03-07 23:36:34 +11:00
Lucas Smith
4ec8eeeac1 docs: add article on removing server actions (#994) 2024-03-07 23:11:32 +11:00
Catalin Pit
7b77084dee fix: deleted files that shouldn't have been committed 2024-03-07 11:34:19 +02:00
Catalin Pit
a34334c4dc docs: add article on removing server actions 2024-03-07 11:32:01 +02:00
Anik Dhabal Babu
b5b74a788c fix: overflow issue with user name input 2024-03-06 14:52:59 +00:00
Apoorv Taneja
3106c325d7 Merge branch 'main' into fix/layout-shift-on-table 2024-02-20 21:52:35 +05:30
apoorv taneja
58f4b72939 added fixed width for status col 2024-02-08 13:31:38 +05:30
Ashraf Chowdury
7b6d6fb1b9 Merge branch 'main' into update-documents-avatar 2024-01-29 19:52:29 +08:00
Ashraf Chowdury
1d4e78e579 Merge branch 'main' into update-documents-avatar 2024-01-09 20:33:31 +08:00
Ashraf Chowdury
8e754405f8 Merge branch 'main' into update-documents-avatar 2024-01-04 20:45:39 +06:00
Ashraf
3fb711cb42 Merge branch 'update-documents-avatar' of https://github.com/ashrafchowdury/documenso into update-documents-avatar 2023-12-29 19:34:46 +08:00
Ashraf Chowdury
8a8a5510cb Merge branch 'main' into update-documents-avatar 2023-12-29 19:13:02 +06:00
Ashraf
ce67de9a1c refactor: changed component name for better readability 2023-12-29 19:29:13 +08:00
Ashraf
cf5841a895 Merge branch 'main' of https://github.com/ashrafchowdury/documenso into update-documents-avatar 2023-12-29 18:48:43 +08:00
Ashraf Chowdury
2bc5d15af2 Merge branch 'main' into update-documents-avatar 2023-12-28 10:43:51 +06:00
Ashraf
2a448fe06d fix: fixed the recipients viewing issue on touch screens 2023-12-20 17:08:09 +08:00
32 changed files with 565 additions and 312 deletions

View File

@@ -27,6 +27,16 @@ E2E_TEST_AUTHENTICATE_USERNAME="Test User"
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com" E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123" E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
# [[SIGNING]]
# OPTIONAL: Defines the signing transport to use. Available options: local (default)
NEXT_PRIVATE_SIGNING_TRANSPORT="local"
# OPTIONAL: Defines the passphrase for the signing certificate.
NEXT_PRIVATE_SIGNING_PASSPHRASE=
# OPTIONAL: Defines the file contents for the signing certificate as a base64 encoded string.
NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS=
# OPTIONAL: Defines the file path for the signing certificate. defaults to ./example/cert.p12
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=
# [[STORAGE]] # [[STORAGE]]
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3 # OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
NEXT_PUBLIC_UPLOAD_TRANSPORT="database" NEXT_PUBLIC_UPLOAD_TRANSPORT="database"

View File

@@ -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>
@@ -207,7 +207,16 @@ If you're a visual learner and prefer to watch a video walkthrough of setting up
## Docker ## Docker
🚧 Docker containers and images are current in progress. We are actively working on bringing a simple Docker build and publish pipeline for Documenso. We provide a Docker container for Documenso, which is published on both DockerHub and GitHub Container Registry.
- DockerHub: [https://hub.docker.com/r/documenso/documenso](https://hub.docker.com/r/documenso/documenso)
- GitHub Container Registry: [https://ghcr.io/documenso/documenso](https://ghcr.io/documenso/documenso)
You can pull the Docker image from either of these registries and run it with your preferred container hosting provider.
Please note that you will need to provide environment variables for connecting to the database, mailserver, and so forth.
For detailed instructions on how to configure and run the Docker container, please refer to the [Docker README](./docker/README.md) in the `docker` directory.
## Self Hosting ## Self Hosting

View File

@@ -0,0 +1,56 @@
---
title: 'Embracing the Future and Moving Back Again: From Server Actions to tRPC'
description: 'This article talks about the need for the public API and the process of building it. It also talks about the requirements, constraints, challenges and lessons learnt from building it.'
authorName: 'Lucas Smith'
authorImage: '/blog/blog-author-lucas.png'
authorRole: 'Co-Founder'
date: 2024-03-07
tags:
- Development
---
On October 26, 2022, during the Next.js Conf, Vercel unveiled Next.js 13, introducing a bunch of new features and a whole new paradigm shift for creating Next.js apps.
React Server Components (RSCs) have been known for some time but haven't yet been implemented in a React meta-framework. With Next.js now adding them, the world of server-side rendering React applications was about to change.
## The promise of React Server Components
Described by Vercel as a tool for writing UIs rendered or optionally cached on the server, RSC promised direct integration with databases and server-specific capabilities, a departure from the typical React and SSR models.
This feature initially appeared as a game-changer, promising simpler data fetching and routing, thanks to async server components and additional nested layout support.
After experimenting with it for about six months on other projects, I came to the conclusion that while it was a little rough at the edges, it was indeed a game changer and something that should be adopted in Next.js projects moving forward.
## A new new paradigm: Server Actions
Vercel didn't stop at adding Server Components. A short while after the initial Next.js 13 release, they introduced "Server Actions." Server Actions allow for the calling of server-side functions without API routes, reducing the amount of ceremony required for adding a new mutation or event to your application.
## Betting on new technology
Following the release of both Server Actions and Server Components, we at Documenso embarked on a rewrite of the application. We had accumulated a bit of technical debt from the initial MVP, which was best shed as we also improved the design with help from our new designer.
Having had much success with Server Components on other projects, I opted to go all in on both Server Components and Server Actions for the new version of the application. I was excited to see how much simpler and more efficient the application would be with these new technologies.
Following our relaunch, we felt quite happy with our choices of server actions, which even let us cocktail certain client—and server-side logic into a single component without needing a bunch of effort on our end.
## Navigating challenges with Server Actions
While initially successful, we soon encountered issues with Server Actions, particularly when monitoring the application. Unfortunately, server actions make it much harder to monitor network requests and identify failed server actions since they use the route that they are currently on.
Despite this issue, things were manageable; however, a critical issue arose when the usage of server actions caused bundle corruption, resulting in a top-level await insertion that would cause builds and tests to fail.
This last issue was a deal breaker since it highlighted how much magic we were relying on with server actions. I like to avoid adding more magic where possible since, with bundlers and meta-frameworks, we are already handing off a lot of heavy lifting and getting a lot of magic in return.
## Going all in on tRPC
My quick and final solution to the issues we were facing was to fully embrace [tRPC](https://trpc.io/), which we already used in other app areas. The migration took less than a day and resolved all our issues while adding a lot more visibility to failing actions since they now had a dedicated API route.
For those unfamiliar with tRPC, it is a framework for building end-to-end typesafe APIs with TypeScript and Next.js. It's an awesome library that lets you call your defined API safely within the client, causing build time errors when things inevitably drift.
I've been a big fan of tRPC for some time now, so the decision to drop server actions and instead use more of tRPC was easy for me.
## Lessons learned and the future
While I believe server actions are a great idea, and I'm sure they will be improved in the future, I've relearned the lesson that it's best to avoid magic where possible.
This doesn't mean we won't consider moving certain things back to server actions in the future. But for now, we will wait and watch how others use them and how they evolve.

View File

@@ -36,7 +36,7 @@
"react-hook-form": "^7.43.9", "react-hook-form": "^7.43.9",
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"recharts": "^2.7.2", "recharts": "^2.7.2",
"sharp": "0.33.1", "sharp": "^0.33.1",
"typescript": "5.2.2", "typescript": "5.2.2",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },

View File

@@ -43,7 +43,7 @@
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"react-rnd": "^10.4.1", "react-rnd": "^10.4.1",
"remeda": "^1.27.1", "remeda": "^1.27.1",
"sharp": "0.33.1", "sharp": "^0.33.1",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",
"uqr": "^0.1.2", "uqr": "^0.1.2",

View File

@@ -76,14 +76,13 @@ 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',
accessorKey: 'status', accessorKey: 'status',
cell: ({ row }) => <DocumentStatus status={row.getValue('status')} />, cell: ({ row }) => <DocumentStatus status={row.getValue('status')} />,
size: 140,
}, },
{ {
header: 'Actions', header: 'Actions',

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

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

View File

@@ -25,7 +25,11 @@ type TemplateWithRecipient = Template & {
}; };
type TemplatesDataTableProps = { type TemplatesDataTableProps = {
templates: TemplateWithRecipient[]; templates: Array<
TemplateWithRecipient & {
team: { id: number; url: string } | null;
}
>;
perPage: number; perPage: number;
page: number; page: number;
totalPages: number; totalPages: number;

View File

@@ -1,23 +1,35 @@
'use client';
import Link from 'next/link'; import Link from 'next/link';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { Template } from '@documenso/prisma/client'; import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import type { Template } from '@documenso/prisma/client';
import { useOptionalCurrentTeam } from '~/providers/team';
export type DataTableTitleProps = { export type DataTableTitleProps = {
row: Template; row: Template & {
team: { id: number; url: string } | null;
};
}; };
export const DataTableTitle = ({ row }: DataTableTitleProps) => { export const DataTableTitle = ({ row }: DataTableTitleProps) => {
const { data: session } = useSession(); const { data: session } = useSession();
const team = useOptionalCurrentTeam();
if (!session) { if (!session) {
return null; return null;
} }
const isCurrentTeamTemplate = team?.url && row.team?.url === team?.url;
const templatesPath = formatTemplatesPath(isCurrentTeamTemplate ? team?.url : undefined);
return ( return (
<Link <Link
href={`/templates/${row.id}`} href={`${templatesPath}/${row.id}`}
className="block max-w-[10rem] cursor-pointer truncate font-medium hover:underline md:max-w-[20rem]" className="block max-w-[10rem] cursor-pointer truncate font-medium hover:underline md:max-w-[20rem]"
> >
{row.title} {row.title}

View File

@@ -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,66 +44,105 @@ export const StackAvatarsWithTooltip = ({
(recipient) => getRecipientType(recipient) === 'unsigned', (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 ( return (
<TooltipProvider> <Popover open={open} onOpenChange={onOpenChange}>
<Tooltip delayDuration={200}> <PopoverTrigger
<TooltipTrigger className="flex cursor-pointer"> className="flex cursor-pointer"
{children || <StackAvatars recipients={recipients} />} onMouseEnter={onMouseEnter}
</TooltipTrigger> onMouseLeave={onMouseLeave}
>
{children || <StackAvatars recipients={recipients} />}
</PopoverTrigger>
<TooltipContent side={position}> <PopoverContent
<div className="flex flex-col gap-y-5 p-1"> side={position}
{completedRecipients.length > 0 && ( onMouseEnter={onMouseEnter}
<div> onMouseLeave={onMouseLeave}
<h1 className="text-base font-medium">Completed</h1> className="flex flex-col gap-y-5 py-2"
{completedRecipients.map((recipient: Recipient) => ( >
<div key={recipient.id} className="my-1 flex items-center gap-2"> {completedRecipients.length > 0 && (
<StackAvatar <div>
first={true} <h1 className="text-base font-medium">Completed</h1>
key={recipient.id} {completedRecipients.map((recipient: Recipient) => (
type={getRecipientType(recipient)} <div key={recipient.id} className="my-1 flex items-center gap-2">
fallbackText={recipientAbbreviation(recipient)} <StackAvatar
/> first={true}
<div className=""> key={recipient.id}
<p className="text-muted-foreground text-sm">{recipient.email}</p> type={getRecipientType(recipient)}
<p className="text-muted-foreground/70 text-xs"> fallbackText={recipientAbbreviation(recipient)}
{RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName} />
</p> <div className="">
</div> <p className="text-muted-foreground text-sm">{recipient.email}</p>
</div> <p className="text-muted-foreground/70 text-xs">
))} {RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
</p>
</div>
</div> </div>
)} ))}
{waitingRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Waiting</h1>
{waitingRecipients.map((recipient: Recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
))}
</div>
)}
{openedRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Opened</h1>
{openedRecipients.map((recipient: Recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
))}
</div>
)}
{uncompletedRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Uncompleted</h1>
{uncompletedRecipients.map((recipient: Recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
))}
</div>
)}
</div> </div>
</TooltipContent> )}
</Tooltip>
</TooltipProvider> {waitingRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Waiting</h1>
{waitingRecipients.map((recipient: Recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
))}
</div>
)}
{openedRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Opened</h1>
{openedRecipients.map((recipient: Recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
))}
</div>
)}
{uncompletedRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Uncompleted</h1>
{uncompletedRecipients.map((recipient: Recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} />
))}
</div>
)}
</PopoverContent>
</Popover>
); );
}; };

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -149,6 +149,13 @@ export const EnableAuthenticatorAppDialog = ({
} }
}; };
useEffect(() => {
// Reset the form when the Dialog closes
if (!open) {
setupTwoFactorAuthenticationForm.reset();
}
}, [open, setupTwoFactorAuthenticationForm]);
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl"> <DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl">

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -92,6 +92,13 @@ export const ViewRecoveryCodesDialog = ({ open, onOpenChange }: ViewRecoveryCode
} }
}; };
useEffect(() => {
// Reset the form when the Dialog closes
if (!open) {
viewRecoveryCodesForm.reset();
}
}, [open, viewRecoveryCodesForm]);
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl"> <DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl">

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,16 @@ NEXT_PRIVATE_SMTP_USERNAME="<your-username>"
NEXT_PRIVATE_SMTP_PASSWORD="<your-password>" NEXT_PRIVATE_SMTP_PASSWORD="<your-password>"
``` ```
4. Run the following command to start the containers: 4. Update the volume binding for the cert file in the `compose.yml` file to point to your own key file:
Since the `cert.p12` file is required for signing and encrypting documents, you will need to provide your own key file. Update the volume binding in the `compose.yml` file to point to your key file:
```yaml
volumes:
- /path/to/your/keyfile.p12:/opt/documenso/cert.p12
```
1. Run the following command to start the containers:
``` ```
docker-compose --env-file ./.env -d up docker-compose --env-file ./.env -d up
@@ -60,18 +69,17 @@ 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>"
-v /path/to/your/keyfile.p12:/opt/documenso/cert.p12
documenso/documenso documenso/documenso
``` ```
@@ -101,6 +109,10 @@ Here's a markdown table documenting all the provided environment variables:
| `NEXT_PUBLIC_WEBAPP_URL` | The URL for the web application. | | `NEXT_PUBLIC_WEBAPP_URL` | The URL for the web application. |
| `NEXT_PRIVATE_DATABASE_URL` | The URL for the primary database connection (with connection pooling). | | `NEXT_PRIVATE_DATABASE_URL` | The URL for the primary database connection (with connection pooling). |
| `NEXT_PRIVATE_DIRECT_DATABASE_URL` | The URL for the direct database connection (without connection pooling). | | `NEXT_PRIVATE_DIRECT_DATABASE_URL` | The URL for the direct database connection (without connection pooling). |
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | The signing transport to use. Available options: local (default) |
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | The passphrase for the key file. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | The base64-encoded contents of the key file, will be used instead of file path. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` | The path to the key file, default `/opt/documenso/cert.p12`. |
| `NEXT_PUBLIC_UPLOAD_TRANSPORT` | The transport to use for file uploads (database or s3). | | `NEXT_PUBLIC_UPLOAD_TRANSPORT` | The transport to use for file uploads (database or s3). |
| `NEXT_PRIVATE_UPLOAD_ENDPOINT` | The endpoint for the S3 storage transport (for third-party S3-compatible providers). | | `NEXT_PRIVATE_UPLOAD_ENDPOINT` | The endpoint for the S3 storage transport (for third-party S3-compatible providers). |
| `NEXT_PRIVATE_UPLOAD_REGION` | The region for the S3 storage transport (defaults to us-east-1). | | `NEXT_PRIVATE_UPLOAD_REGION` | The region for the S3 storage transport (defaults to us-east-1). |

View File

@@ -57,8 +57,11 @@ services:
- NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT=${NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT} - NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT=${NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT}
- NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY} - NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
- NEXT_PUBLIC_DISABLE_SIGNUP=${NEXT_PUBLIC_DISABLE_SIGNUP} - NEXT_PUBLIC_DISABLE_SIGNUP=${NEXT_PUBLIC_DISABLE_SIGNUP}
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH?:-/opt/documenso/cert.p12}
ports: ports:
- ${PORT:-3000}:${PORT:-3000} - ${PORT:-3000}:${PORT:-3000}
volumes:
- /opt/documenso/cert.p12:/opt/documenso/cert.p12
volumes: volumes:
database: database:

View File

@@ -47,5 +47,8 @@ services:
- NEXT_PRIVATE_SMTP_PASSWORD=password - NEXT_PRIVATE_SMTP_PASSWORD=password
- NEXT_PRIVATE_SMTP_FROM_NAME="No Reply @ Documenso" - NEXT_PRIVATE_SMTP_FROM_NAME="No Reply @ Documenso"
- NEXT_PRIVATE_SMTP_FROM_ADDRESS=noreply@documenso.com - NEXT_PRIVATE_SMTP_FROM_ADDRESS=noreply@documenso.com
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
ports: ports:
- 3000:3000 - 3000:3000
volumes:
- ../../apps/web/example/cert.p12:/opt/documenso/cert.p12

121
package-lock.json generated
View File

@@ -58,7 +58,7 @@
"react-hook-form": "^7.43.9", "react-hook-form": "^7.43.9",
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"recharts": "^2.7.2", "recharts": "^2.7.2",
"sharp": "0.33.1", "sharp": "^0.33.1",
"typescript": "5.2.2", "typescript": "5.2.2",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
@@ -74,45 +74,6 @@
"integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==", "integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==",
"dev": true "dev": true
}, },
"apps/marketing/node_modules/sharp": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz",
"integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"semver": "^7.5.4"
},
"engines": {
"libvips": ">=8.15.0",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.1",
"@img/sharp-darwin-x64": "0.33.1",
"@img/sharp-libvips-darwin-arm64": "1.0.0",
"@img/sharp-libvips-darwin-x64": "1.0.0",
"@img/sharp-libvips-linux-arm": "1.0.0",
"@img/sharp-libvips-linux-arm64": "1.0.0",
"@img/sharp-libvips-linux-s390x": "1.0.0",
"@img/sharp-libvips-linux-x64": "1.0.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
"@img/sharp-linux-arm": "0.33.1",
"@img/sharp-linux-arm64": "0.33.1",
"@img/sharp-linux-s390x": "0.33.1",
"@img/sharp-linux-x64": "0.33.1",
"@img/sharp-linuxmusl-arm64": "0.33.1",
"@img/sharp-linuxmusl-x64": "0.33.1",
"@img/sharp-wasm32": "0.33.1",
"@img/sharp-win32-ia32": "0.33.1",
"@img/sharp-win32-x64": "0.33.1"
}
},
"apps/marketing/node_modules/typescript": { "apps/marketing/node_modules/typescript": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
@@ -159,7 +120,7 @@
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"react-rnd": "^10.4.1", "react-rnd": "^10.4.1",
"remeda": "^1.27.1", "remeda": "^1.27.1",
"sharp": "0.33.1", "sharp": "^0.33.1",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",
"uqr": "^0.1.2", "uqr": "^0.1.2",
@@ -182,45 +143,6 @@
"integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==", "integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==",
"dev": true "dev": true
}, },
"apps/web/node_modules/sharp": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz",
"integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"semver": "^7.5.4"
},
"engines": {
"libvips": ">=8.15.0",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.1",
"@img/sharp-darwin-x64": "0.33.1",
"@img/sharp-libvips-darwin-arm64": "1.0.0",
"@img/sharp-libvips-darwin-x64": "1.0.0",
"@img/sharp-libvips-linux-arm": "1.0.0",
"@img/sharp-libvips-linux-arm64": "1.0.0",
"@img/sharp-libvips-linux-s390x": "1.0.0",
"@img/sharp-libvips-linux-x64": "1.0.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
"@img/sharp-linux-arm": "0.33.1",
"@img/sharp-linux-arm64": "0.33.1",
"@img/sharp-linux-s390x": "0.33.1",
"@img/sharp-linux-x64": "0.33.1",
"@img/sharp-linuxmusl-arm64": "0.33.1",
"@img/sharp-linuxmusl-x64": "0.33.1",
"@img/sharp-wasm32": "0.33.1",
"@img/sharp-win32-ia32": "0.33.1",
"@img/sharp-win32-x64": "0.33.1"
}
},
"apps/web/node_modules/typescript": { "apps/web/node_modules/typescript": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
@@ -18560,6 +18482,45 @@
"sha.js": "bin.js" "sha.js": "bin.js"
} }
}, },
"node_modules/sharp": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz",
"integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"semver": "^7.5.4"
},
"engines": {
"libvips": ">=8.15.0",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.1",
"@img/sharp-darwin-x64": "0.33.1",
"@img/sharp-libvips-darwin-arm64": "1.0.0",
"@img/sharp-libvips-darwin-x64": "1.0.0",
"@img/sharp-libvips-linux-arm": "1.0.0",
"@img/sharp-libvips-linux-arm64": "1.0.0",
"@img/sharp-libvips-linux-s390x": "1.0.0",
"@img/sharp-libvips-linux-x64": "1.0.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
"@img/sharp-linux-arm": "0.33.1",
"@img/sharp-linux-arm64": "0.33.1",
"@img/sharp-linux-s390x": "0.33.1",
"@img/sharp-linux-x64": "0.33.1",
"@img/sharp-linuxmusl-arm64": "0.33.1",
"@img/sharp-linuxmusl-x64": "0.33.1",
"@img/sharp-wasm32": "0.33.1",
"@img/sharp-win32-ia32": "0.33.1",
"@img/sharp-win32-x64": "0.33.1"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

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

View 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();
});

View File

@@ -20,7 +20,7 @@ module.exports = {
parserOptions: { parserOptions: {
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,
project: ['../../apps/*/tsconfig.json', '../../packages/*/tsconfig.json'], project: ['../../tsconfig.eslint.json'],
ecmaVersion: 2022, ecmaVersion: 2022,
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,

View File

@@ -37,6 +37,12 @@ export const findTemplates = async ({
where: whereFilter, where: whereFilter,
include: { include: {
templateDocumentData: true, templateDocumentData: true,
team: {
select: {
id: true,
url: true,
},
},
Field: true, Field: true,
Recipient: true, Recipient: true,
}, },

View File

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

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

View File

@@ -115,7 +115,12 @@ export function DataTable<TData, TValue>({
table.getRowModel().rows.map((row) => ( table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}> <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}> <TableCell
key={cell.id}
style={{
width: `${cell.column.getSize()}px`,
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell> </TableCell>
))} ))}

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, 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,68 +60,108 @@ 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 ? (
<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={DocumentDropzoneDisabledCardLeftVariants}
<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={DocumentDropzoneDisabledCardCenterVariants}
> >
<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={DocumentDropzoneDisabledCardRightVariants}
> >
<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={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-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={DocumentDropzoneCardCenterVariants}
>
<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={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-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">{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>

View File

@@ -79,7 +79,7 @@ const ToastClose = React.forwardRef<
<ToastPrimitives.Close <ToastPrimitives.Close
ref={ref} ref={ref}
className={cn( className={cn(
'text-foreground/50 hover:text-foreground absolute right-2 top-2 rounded-md p-1 opacity-0 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', 'text-foreground/50 hover:text-foreground absolute right-2 top-2 rounded-md p-1 opacity-100 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 md:opacity-0 group-hover:md:opacity-100',
className, className,
)} )}
toast-close="" toast-close=""

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%;
} }
} }
@@ -87,6 +91,11 @@
@apply border-border; @apply border-border;
} }
html,
body {
scrollbar-gutter: stable;
}
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-feature-settings: 'rlig' 1, 'calt' 1; font-feature-settings: 'rlig' 1, 'calt' 1;

4
tsconfig.eslint.json Normal file
View File

@@ -0,0 +1,4 @@
{
"extends": "./packages/tsconfig/base.json",
"include": ["packages/**/*", "apps/**/*"]
}