Compare commits

..

75 Commits

Author SHA1 Message Date
Catalin Pit
fe493adbb5 ci: update gh action 2023-09-30 19:48:26 +01:00
Catalin Pit
9437639c53 ci: update gh action 2023-09-30 19:37:54 +01:00
Catalin Pit
2afe8ecef0 ci: update ci command 2023-09-30 19:27:42 +01:00
Catalin Pit
fe939d8aa4 ci: fix error no BUILD_ID 2023-09-30 19:18:40 +01:00
Catalin Pit
b918e6ba77 ci: update e2e-tests github action 2023-09-30 19:05:59 +01:00
Catalin Pit
ae7a372ca0 ci: trigger github action 2023-09-30 18:54:21 +01:00
Catalin Pit
a18932378f ci: update GitHub Actions 2023-09-30 10:52:58 +01:00
pit
5f27eb20fd chore: trigger ci 2023-09-27 12:08:22 +01:00
pit
0d91e61a50 chore: trigger ci 2023-09-27 11:26:17 +01:00
pit
a2322e68d7 chore: trigger ci 2023-09-27 11:19:21 +01:00
pit
acb1fde5e7 feat: add docker to gh action 2023-09-27 11:17:17 +01:00
pit
f057666776 chore: e2e-tests GitHub Action update 2023-09-27 10:11:32 +01:00
pit
e82c12154b chore: e2e-tests GitHub Action update 2023-09-27 10:02:02 +01:00
pit
7efa8f0fc8 chore: e2e-tests GitHub action change 2023-09-27 10:01:02 +01:00
pit
63c2c4acf4 chore: implemented feedback 2023-09-27 09:51:30 +01:00
pit
5c81105e71 chore: merged feat/refresh 2023-09-27 09:16:20 +01:00
Mythie
7bd847c0d3 chore: add missing migrations 2023-09-27 16:53:48 +10:00
Lucas Smith
a20b1e2f6a Merge pull request #350 from documenso/feat/redirect-signed-document
feat: redirect signed document to completed page
2023-09-27 15:09:07 +10:00
Mythie
572f9d5ad6 chore: styling updates 2023-09-27 14:58:10 +10:00
Mythie
e49c102e8c fix: lint errors 2023-09-27 09:11:56 +10:00
Mythie
2348221b03 fix: faster tooltips 2023-09-27 09:09:53 +10:00
Mythie
a0c327cfcf fix: share og updates 2023-09-27 09:03:53 +10:00
Mythie
ddbeb9e3db fix: twitter images 2023-09-27 08:02:51 +10:00
Mythie
e8205c1390 fix: better share links 2023-09-27 07:52:24 +10:00
Timur Ercan
fbb53fc8c3 chore: update mania shirt res 2023-09-26 22:06:41 +02:00
Timur Ercan
d7fed5a5dc feat: shop Article 2023-09-26 21:49:04 +02:00
pit
11fce3ffba chore: merged feat/refresh 2023-09-26 16:27:11 +01:00
pit
86dddb8a4d feat: improve tests 2023-09-26 16:19:26 +01:00
pit
fba38f7b02 chore: cleanup 2023-09-26 12:19:59 +01:00
pit
2e2ef4c20b chore: updated GitHub Action 2023-09-26 11:16:33 +01:00
Timur Ercan
3285881586 chore: typos 2023-09-26 11:39:30 +02:00
Timur Ercan
3ff61607a2 Merge pull request #370 from documenso/feat/mania
feat: malfunction mania blog post
2023-09-26 11:24:24 +02:00
Timur Ercan
2a124b03e9 Merge branch 'feat/refresh' into feat/mania 2023-09-26 11:24:13 +02:00
Timur Ercan
e1aa23bc55 chore: fix typo 2023-09-26 11:23:39 +02:00
Timur Ercan
818c5c7ba4 chore: fix lightmode logo 2023-09-26 11:19:32 +02:00
Timur Ercan
668011d1c7 Update README.md with M̸͍͚̽͒A̴̯͊͌L̴͖͖͘F̵̗̻́̅U̶̲̅͠N̸͙̰̓C̵̙̮̾T̸̜̙͌Í̷͎̯Ö̵̘̜́N̴̳͊̈ͅ ̶͔́M̸̡͐̾A̵̞̚N̴̤̏́Ǐ̸̩͂Ă̸͓͝ 2023-09-26 11:12:59 +02:00
Timur Ercan
562fd043a9 chore: typo 2023-09-26 10:31:41 +02:00
Timur Ercan
b4781c011c chore: alt text 2023-09-26 10:27:19 +02:00
Timur Ercan
5c7d3d5503 chore: update date 2023-09-26 10:26:16 +02:00
Timur Ercan
0f2b6c0ebf chore: remove unused image 2023-09-26 10:23:28 +02:00
Timur Ercan
ab5bdfeae4 chore: typo 2023-09-26 10:18:56 +02:00
Mythie
2d2615447e fix: redirectless authentication 2023-09-26 16:17:01 +10:00
Mythie
46465acd73 chore: quieten dependabot 2023-09-26 16:11:06 +10:00
Lucas Smith
2dea9ec3e7 Merge pull request #423 from documenso/feat/copy-or-tweet
feat: add dropdown to tweet or copy signing link
2023-09-26 16:08:43 +10:00
Timur Ercan
109ad190cf fix: text 2023-09-25 19:13:03 +02:00
Timur Ercan
fde53e355f chore: staging 2023-09-22 18:44:34 +02:00
Timur Ercan
8e0a10298e Merge branch 'feat/refresh' into feat/mania 2023-09-22 18:33:16 +02:00
pit
1b31d590a5 chore: use fixtures 2023-09-22 13:45:59 +01:00
pit
a882b72492 chore: removed auth 2023-09-21 15:57:11 +01:00
pit
03fa6a3fa9 chore: add auth to gitignore 2023-09-21 15:56:23 +01:00
pit
bcf1ffabd4 chore: removed test details from env file 2023-09-21 15:55:05 +01:00
pit
8eff8db990 chore: changed files 2023-09-21 15:54:02 +01:00
pit
cbb56891c4 feat: moved playwright to the web app 2023-09-21 15:52:53 +01:00
pit
5d5c5b2748 chore: merged feat/refresh 2023-09-19 11:47:25 +01:00
pit
97b4655bc8 feat: improve tests 2023-09-19 11:45:12 +01:00
pit
3c43d7af05 chore: improved structure'
'
2023-09-18 21:13:55 +01:00
Timur Ercan
eaa3e0a303 Merge branch 'feat/refresh' into feat/mania 2023-09-18 20:15:50 +02:00
Timur Ercan
a283c88d7f chore: update malfunction mania image 2023-09-18 20:11:27 +02:00
pit
5beb1c6192 feat: fix auth issue 2023-09-18 11:32:21 +01:00
Ephraim Atta-Duncan
c2a9647c90 Merge branch 'feat/refresh' into feat/redirect-signed-document 2023-09-17 14:52:44 +00:00
Ephraim Atta-Duncan
02424596db fix: update for code review 2023-09-17 14:45:22 +00:00
Catalin Pit
ba31138217 feat: add more tests 2023-09-15 15:57:18 +03:00
Catalin Pit
7a9d987258 chore: removed auth 2023-09-14 18:08:54 +03:00
Catalin Pit
b0592c5d00 chore: remove auth from pr 2023-09-14 18:05:59 +03:00
Catalin Pit
aee103d4e3 chore: deleted file 2023-09-14 18:04:50 +03:00
Catalin Pit
0148c0f0cf feat: add-playwright 2023-09-14 18:03:35 +03:00
Timur Ercan
5809109c05 chore: grammerly, finetuned bounties 2023-09-14 15:43:17 +02:00
Timur Ercan
a6400eb6c9 Merge branch 'feat/refresh' into feat/mania 2023-09-14 15:29:55 +02:00
flō
39958ed22c Fix typo 2023-09-14 14:57:11 +02:00
flō
7c4ba1b1ea Fix typo 2023-09-14 11:38:42 +02:00
flō
f588897531 Fix punctuation for consistency 2023-09-14 11:33:11 +02:00
flō
5629e08f83 Add keywords
added keywords in description for SEO optimizations
2023-09-14 11:06:48 +02:00
flō
37394c054c Fix typo
Fix typo and minor edits for consistency
2023-09-14 11:04:10 +02:00
Timur Ercan
b7c0df67b1 feat: malfunction mania, first draft 2023-09-11 18:20:17 +02:00
Ephraim Atta-Duncan
e47ab838c5 chore: remove undefined check 2023-09-06 07:37:03 +00:00
37 changed files with 1052 additions and 137 deletions

View File

@@ -15,6 +15,20 @@ NEXT_PRIVATE_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documen
# Defines the URL to use for the database when running migrations and other commands that won't work with a connection pool.
NEXT_PRIVATE_DIRECT_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
# [[E2E Tests]]
E2E_TEST_SIGNER_NAME="Test Signer"
E2E_TEST_SIGNER_EMAIL="testsigner@mail.com"
E2E_TEST_SIGNING_SUBJECT="Test subject"
E2E_TEST_SIGNING_MESSAGE="Test message"
# User for the "auth.setup.ts" file and the authenticated tests
E2E_TEST_AUTHENTICATE_USERNAME="New user"
E2E_TEST_AUTHENTICATE_USER_EMAIL="mytestnewuser@mail.com"
E2E_TEST_AUTHENTICATE_USER_PASSWORD="new_test_password"
# User for the *.unauthenticated.ts files
E2E_TEST_USERNAME="Test"
E2E_TEST_USER_EMAIL="mytestuser@mail.com"
E2E_TEST_USER_PASSWORD="test_password"
# [[STORAGE]]
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
NEXT_PUBLIC_UPLOAD_TRANSPORT="database"

View File

@@ -9,7 +9,7 @@ updates:
labels:
- "ci dependencies"
- "ci"
open-pull-requests-limit: 10
open-pull-requests-limit: 2
- package-ecosystem: "npm"
directory: "/apps/marketing"
@@ -19,7 +19,7 @@ updates:
labels:
- "npm dependencies"
- "frontend"
open-pull-requests-limit: 10
open-pull-requests-limit: 2
- package-ecosystem: "npm"
directory: "/apps/web"
@@ -29,4 +29,4 @@ updates:
labels:
- "npm dependencies"
- "frontend"
open-pull-requests-limit: 10
open-pull-requests-limit: 2

53
.github/workflows/e2e-tests.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Playwright Tests
on:
push:
branches: [feat/refresh, feat/add-e2e-testing]
pull_request:
branches: [feat/refresh, feat/add-e2e-testing]
jobs:
e2e_tests:
timeout-minutes: 60
runs-on: ubuntu-latest
services:
postgres:
image: postgres
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
env:
NEXT_PRIVATE_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/documenso
NEXT_PRIVATE_DIRECT_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/documenso
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Generate package-lock.json
run: npm cache clean --force && npm install
- name: Install dependencies
run: npm ci
- name: Copy env
run: cp .env.example .env
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Install Prisma Client
run: npm install @prisma/client
- name: Generate Prisma Client
run: npm run prisma:generate -w @documenso/prisma
- name: Create the database
run: npm run prisma:migrate-dev -w @documenso/prisma
- name: Run Playwright tests
run: npm run ci
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

5
.gitignore vendored
View File

@@ -47,3 +47,8 @@ yarn-error.log*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
**/test-results/
/playwright-report/
/playwright/.cache/
playwright/.auth

View File

@@ -1,6 +1,6 @@
<p align="center" style="margin-top: 120px">
<a href="https://github.com/documenso/documenso">
<img width="250px" src="https://github.com/documenso/documenso/assets/1309312/cd7823ec-4baa-40b9-be78-4acb3b1c73cb" alt="Documenso Logo">
<img width="250px" src="https://github.com/documenso/documenso/assets/1309312/ea8de2d0-8e33-4527-8a5c-40780142a2ed" alt="Documenso Logo">
</a>
<p align="center">
@@ -26,37 +26,30 @@
<a href="https://github.com/documenso/documenso/pulse"><img src="https://img.shields.io/github/commit-activity/m/documenso/documenso" alt="Commits-per-month"></a>
</p>
> **🚧 We're currently working on a large scale refactor which can be found on the [feat/refresh](https://github.com/documenso/documenso/tree/feat/refresh) branch.**
> 🦺 Documenso 1.0 is deployed to our <a href="https://documen.so/staging" target="_blank">Staging Environment</a>.
>
> **[Read more on why 👀](https://documenso.com/blog/why-were-doing-a-rewrite)**
> The code can be found on the [feat/refresh](https://github.com/documenso/documenso/tree/feat/refresh) branch.
>
> The new Version will be released after the current testing phase.
# Documenso 0.9 - Developer Preview
# Join us in testing Documenso 1.0 during [MALFUNCTION MANIA](https://documenso.com/blog/malfunction-mania)
<div>
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224570645-167128ee-3e39-4578-85d2-5394d9a0379c.png">
src="https://github.com/documenso/documenso/assets/1309312/67e08c98-c153-4115-aa2d-77979bb12c94)">
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224570651-0afd12f8-cfe3-49d1-805e-e495af963d91.png">
src="https://github.com/documenso/documenso/assets/1309312/040cfbae-3438-4ca3-87f2-ce52c793dcaf">
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224570655-328d2279-058d-4a3e-b5c3-5cbd8a1f4e05.png">
src="https://github.com/documenso/documenso/assets/1309312/72d445be-41e5-4936-bdba-87ef8e70fa09">
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224571617-1f3c2811-c1ac-4d7d-b9b0-4ab183731405.png">
src="https://github.com/documenso/documenso/assets/1309312/d7b86c0f-a755-4476-a022-a608db2c4633">
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224570322-b2c76ea8-7482-4043-ad97-f1221220c591.png">
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224570325-a8055f24-9826-4a23-b116-4fbb0577581a.png">
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224570318-f724bbd9-c394-4bdc-bace-2d78af92de44.png">
<img style="display: block; height: 120px; width: 24%"
src="https://user-images.githubusercontent.com/1309312/224571539-f019b860-f613-4b20-86e8-4437c5784265.png">
src=https://github.com/documenso/documenso/assets/1309312/c0f55116-ab82-433f-a266-f3fc8571d69f">
</div>
> **Note**
> This project is currently under community review and will publish it's first production release soon™.
## About this project
Signing documents digitally is fast, easy and should be best practice for every document signed worldwide. This is technically quite easy today, but it also introduces a new party to every signature: The signing tool providers. While this is not a problem in itself, it should make us think about how we want these providers of trust to work. Documenso aims to be the world's most trusted document signing tool. This trust is built by empowering you to self-host Documenso and review how it works under the hood. Join us in creating the next generation of open trust infrastructure.
Signing documents digitally is fast and easy and should be the best practice for every document signed worldwide. This is technically quite easy today, but it also introduces a new party to every signature: The signing tool providers. While this is not a problem in itself, it should make us think about how we want these providers of trust to work. Documenso aims to be the world's most trusted document-signing tool. This trust is built by empowering you to self-host Documenso and review how it works under the hood. Join us in creating the next generation of open trust infrastructure.
## Recognition
@@ -65,13 +58,13 @@ Signing documents digitally is fast, easy and should be best practice for every
## Community and Next Steps 🎯
We're currently working on a redesign of the application including a revamp of the codebase so Documenso can be more intuitive to use and robust to develop upon.
We're currently working on a redesign of the application, including a revamp of the codebase so Documenso can be more intuitive to use and robust to develop upon.
- Check out the first source code release in this repository and test it
- Tell us what you think in the current [Discussions](https://github.com/documenso/documenso/discussions)
- Join the [Discord server](https://documen.so/discord) for any questions and getting to know to other community members
- ⭐ the repository to help us raise awareness
- Spread the word on Twitter, that Documenso is working towards a more open signing tool
- Spread the word on Twitter that Documenso is working towards a more open signing tool
- Fix or create [issues](https://github.com/documenso/documenso/issues), that are needed for the first production release
## Contributing

View File

@@ -0,0 +1,54 @@
---
title: Announcing Malfunction Mania
description: Launch Week Day 2 Y'all! We're getting ready to release Documenso 1.0! Join in on the fun of making sure the open-source alternative to DocuSign is on point. We're calling a 'MALFUNCTION MANIA.'
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2023-09-26
tags:
- Testing
- Rewrite
- Bounties
---
<figure>
<MdxNextImage
src="/blog/mm.png"
width="1260"
height="630"
alt="Malfunctioning Documenso Logo inbroken colors"
/>
<figcaption className="text-center">
We're calling a MALFUNCTION MANIA! 🚨
</figcaption>
</figure>
> TLDR; Documenso 1.0 is in the [staging environment](https://documen.so/staging). Go check it out.
It's been a minute since Lucas proclaimed, ["We're doing a rewrite"](https://documenso.com/blog/why-were-doing-a-rewrite), and many of you have been asking when the new version will be available. I'm happy to say that the wait has come to an end. The work on Documenso 1.0 has reached a level we feel comfortable going into the next phase with. We had a lot of community feedback, contributions, and moral support to get us this far, which is why we're excited to announce the most extensive community project yet:
As Documenso 1.0 just hit the staging environment, we're calling a MALFUNCTION MANIA. An enormous, public testing phase, where we invite everyone to try out the new version, hunt down, report, and fix any malfunctions (aka bugs), and give feedback before release. Malfunction Mania will happen alongside our internal testing, and by combining the two, we want to ensure the best possible release we can have for Version 1.0. We know many of you have been eager to contribute; this is your chance (the first of many to come).
## As part of Malfunction Mania, we're offering special bug bounties
- We award $25 - $100 per report/ issue/ fix, depending on the severity and if the problem is already known
- Bounties will be awarded for fixing reported or other critical issues via accepted Pull Requests (PR)
- Just reporting issues in a reproducible way can also be awarded
- Smaller but notable contributions like minor issues and documentation will be awarded with exclusive merch as we see fit.
## What you can do
- Head over to the [staging environment](https://documen.so/staging), check out the new version and give it a spin;
- Check out the [source code](https://github.com/documenso/documenso) on GitHub and look it over;
- Spin up the new version locally and try it out.
## How to get the bounties
- Report bugs by creating an issue here: [documen.so/issues](https://documen.so/issues);
- Fix bugs by creating a Pull Request (PR);
- Look over and add missing documentation/ Quickstarts and other useful resources.
We don't have a specific end date for Malfunction Mania. We plan to move the staging version into the production environment by the end of the month once we're happy with the results. Bug reports and fixes are, of course, always welcome going forward.
**[Follow Documenso on Twitter / X](https://documen.so/tw) and [join the Discord server](https://documen.so/discord) to get the latest about Malfunction Mania.**

View File

@@ -20,11 +20,11 @@ Today, I'm pleased to share with you a preview of the next Documenso.
We redesigned the whole signing flow to make it more appealing and more convenient.
We improved the overall look and feel by making it more elegant and appropriately playful. Focused on the task at hand, but explicitly enjoying doing it.
We improved the overall look and feel by making it more elegant and appropriately playful. Focused on the task at hand, but explicitly enjoying doing it.
**We call it happy minimalism.**
We paid particular attention to the moment of signing, which should be celebrated.
We paid particular attention to the moment of signing, which should be celebrated.
The image below is the final bloom of the completion celebration we added:

View File

@@ -0,0 +1,62 @@
---
title: Shop and Limited Edition "Mania" Shirt
description: Happy Launch Week Day 3. The limited edition "Malfunction Mania" shirt is here. Grab it, while you can.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2023-09-27
tags:
- Testing
- Rewrite
- Bounties
---
<figure>
<MdxNextImage
src="/blog/mania-shirt.png"
width="1260"
height="630"
alt="Malfunctioning Mania Themed Documenso Shirt"
/>
<figcaption className="text-center">
The Limited Edition "Malfunction Mania" Shirt - Only during Malfunction Mania
</figcaption>
</figure>
> TLDR; We have a fancy limited edition shirt. Contribute to Malfunction Mania to get one.
We kicked off [Malfunction Mania](https://documenso.com/blog/malfunction-mania) yesterday, and the first [Issues](https://github.com/documenso/documenso/issues) are coming in. As mentioned, there will be dollar bounties, but we also wanted to celebrate entering the final stage of version 1.0 with something special. This is why we created the limited edition shirt for Malfunction Mania. It will only be available during the runtime of Malfunction Mania. We have yet to set an exact end date, the next event in October, however, is looming, ready to end MM.
## Documenso Merch Shop
The shirt will be available in our [merch shop](https://documen.so/shop) via a unique discount code. While the shirt will be gone after Malfunction Mania, the shop is here to stay and provide a well-deserved reward for great community members and contributors.
<figure>
<MdxNextImage
src="/blog/shop.png"
width="1260"
height="630"
alt="Malfunctioning Mania Themed Documenso Shirt"
/>
<figcaption className="text-center">
Merch at Documenso is always given to those who deserve it.
</figcaption>
</figure>
## How to get the shirt
If you have been following us, you know we are not big on formalities but highly value rewarding merit. That being said, any worthwhile contribution has a chance to get one. To inspire, here are a few ideas on how to contribute to securing one:
- Report a bug with detailed reproduction details
- Fix a bug (you or somebody else reported)
- Analyze and describe a usability or user experience shortcoming
- Test the product in a systematic and least somewhat documented way
- Engage in discussion about the current version and its choices
- Raise awareness for Malfunction Mania and try out the [version currently in staging](https://documen.so/staging)
- Review the version with a video, stream, or screenshots and post about it
- Review existing or create missing documenso
- ...
**[Follow Documenso on Twitter / X](https://documen.so/tw) and [join the Discord server](https://documen.so/discord) to get the latest about Malfunction Mania.**

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 KiB

View File

@@ -73,7 +73,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod
<ConfettiScreen duration={3000} gravity={0.075} initialVelocityY={50} wind={0.005} />
)}
<h2 className="text-center text-2xl font-semibold leading-normal md:text-3xl lg:mb-2 lg:text-4xl">
<h2 className="relative z-10 text-center text-2xl font-semibold leading-normal md:text-3xl lg:mb-2 lg:text-4xl">
You have signed
<span className="mt-2 block">{document.title}</span>
</h2>
@@ -84,17 +84,17 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod
signingCelebrationImage={signingCelebration}
/>
<div className="mt-8 w-full">
<div className="relative mt-8 w-full">
<div className={cn('flex flex-col items-center', className)}>
<div className="grid w-full max-w-sm grid-cols-2 gap-4">
{/* TODO: Hook this up */}
<Button variant="outline" className="flex-1" disabled>
<Button variant="outline" className="flex-1 bg-transparent backdrop-blur-sm" disabled>
<Share className="mr-2 h-5 w-5" />
Share
</Button>
<DocumentDownloadButton
className="flex-1"
className="flex-1 bg-transparent backdrop-blur-sm"
fileName={document.title}
documentData={document.documentData}
disabled={document.status !== DocumentStatus.COMPLETED}

3
apps/web/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
playwright-report/
playwright/.auth

View File

@@ -0,0 +1,16 @@
import { deleteUserAndItsData } from '@documenso/lib/server-only/user/delete-user-and-data';
async function teardown() {
if (!process.env.E2E_TEST_USERNAME || !process.env.E2E_TEST_AUTHENTICATE_USERNAME) {
return;
}
try {
await deleteUserAndItsData(process.env.E2E_TEST_USERNAME);
await deleteUserAndItsData(process.env.E2E_TEST_AUTHENTICATE_USERNAME);
} catch (e) {
throw new Error(`Error deleting user: ${e}`);
}
}
export default teardown;

View File

@@ -8,6 +8,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"test:dev": "playwright test",
"test:e2e": "start-server-and-test \"npm run start\" http://localhost:3000 \"playwright test\"",
"clean": "rimraf .next && rimraf node_modules",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
},
@@ -41,9 +43,11 @@
"sharp": "0.32.5",
"ts-pattern": "^5.0.5",
"typescript": "5.1.6",
"zod": "^3.21.4"
"zod": "^3.21.4",
"start-server-and-test": "^2.0.1"
},
"devDependencies": {
"@playwright/test": "^1.38.0",
"@types/formidable": "^2.0.6",
"@types/luxon": "^3.3.1",
"@types/node": "20.1.0",

View File

@@ -0,0 +1,66 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import { config as dotenvConfig } from 'dotenv';
dotenvConfig();
export const STORAGE_STATE = 'playwright/.auth/user.json';
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './src/tests/e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
navigationTimeout: 60 * 1000,
},
/* Configure projects for major browsers */
projects: [
{ name: 'setup', testMatch: '**/*.setup.ts' },
{
name: 'Authenticated User Tests',
testMatch: '*.authenticated.spec.ts',
dependencies: ['setup', 'cleanup db'],
use: {
...devices['Desktop Chrome'],
storageState: STORAGE_STATE,
},
},
{
name: 'Unauthenticated User Tests',
dependencies: ['cleanup db'],
use: {
...devices['Desktop Chrome'],
},
testMatch: '*.unauthenticated.spec.ts',
testIgnore: ['*.setup.ts', '*.authenticated.spec.ts'],
},
{
name: 'cleanup db',
testMatch: /global.teardown\.ts/,
},
],
globalTeardown: './global.teardown.ts',
});

View File

@@ -1,4 +1,4 @@
import { ImageResponse } from 'next/server';
import { ImageResponse, NextResponse } from 'next/server';
import { P, match } from 'ts-pattern';
@@ -21,7 +21,7 @@ type SharePageOpenGraphImageProps = {
params: { slug: string };
};
export default async function Image({ params: { slug } }: SharePageOpenGraphImageProps) {
export async function GET(_request: Request, { params: { slug } }: SharePageOpenGraphImageProps) {
const [interSemiBold, interRegular, caveatRegular, shareFrameImage] = await Promise.all([
getAssetBuffer('/fonts/inter-semibold.ttf'),
getAssetBuffer('/fonts/inter-regular.ttf'),
@@ -32,7 +32,7 @@ export default async function Image({ params: { slug } }: SharePageOpenGraphImag
const recipientOrSender = await getRecipientOrSenderByShareLinkSlug({ slug }).catch(() => null);
if (!recipientOrSender) {
return null;
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
const isRecipient = 'Signature' in recipientOrSender;

View File

@@ -1,11 +1,39 @@
import { Metadata } from 'next';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { Redirect } from './redirect';
import { APP_BASE_URL } from '@documenso/lib/constants/app';
export const metadata: Metadata = {
title: 'Documenso - Share',
type SharePageProps = {
params: { slug: string };
};
export default function SharePage() {
return <Redirect />;
export function generateMetadata({ params: { slug } }: SharePageProps) {
return {
title: 'Documenso - Share',
description: 'I just signed a document with Documenso!',
openGraph: {
title: 'Documenso - Join the open source signing revolution',
description: 'I just signed with Documenso!',
type: 'website',
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
},
twitter: {
site: '@documenso',
card: 'summary_large_image',
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
description: 'I just signed with Documenso!',
},
} satisfies Metadata;
}
export default function SharePage() {
const userAgent = headers().get('User-Agent') ?? '';
// https://stackoverflow.com/questions/47026171/how-to-detect-bots-for-open-graph-with-user-agent
if (/bot|facebookexternalhit|WhatsApp|google|bing|duckduckbot|MetaInspector/i.test(userAgent)) {
return null;
}
redirect(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
}

View File

@@ -1,11 +0,0 @@
'use client';
import { useEffect } from 'react';
export const Redirect = () => {
useEffect(() => {
window.location.href = process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001';
}, []);
return null;
};

View File

@@ -9,7 +9,7 @@ import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-f
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { DocumentStatus, FieldType } from '@documenso/prisma/client';
import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button';
import { SigningCard } from '@documenso/ui/components/signing-card';
import { SigningCard3D } from '@documenso/ui/components/signing-card';
import signingCelebration from '~/assets/signing-celebration.png';
@@ -53,11 +53,11 @@ export default async function CompletedSigningPage({
recipient.email;
return (
<div className="flex flex-col items-center pt-24">
<div className="flex flex-col items-center pt-24 lg:pt-36 xl:pt-44">
{/* Card with recipient */}
<SigningCard name={recipientName} signingCelebrationImage={signingCelebration} />
<SigningCard3D name={recipientName} signingCelebrationImage={signingCelebration} />
<div className="mt-6">
<div className="relative mt-6 flex w-full flex-col items-center">
{match(document.status)
.with(DocumentStatus.COMPLETED, () => (
<div className="text-documenso-700 flex items-center text-center">
@@ -71,41 +71,44 @@ export default async function CompletedSigningPage({
<span className="text-sm">Waiting for others to sign</span>
</div>
))}
<h2 className="mt-6 max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
You have signed "{document.title}"
</h2>
{match(document.status)
.with(DocumentStatus.COMPLETED, () => (
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
Everyone has signed! You will receive an Email copy of the signed document.
</p>
))
.otherwise(() => (
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
You will receive an Email copy of the signed document once everyone has signed.
</p>
))}
<div className="mt-8 flex w-full max-w-sm items-center justify-center gap-4">
<ShareButton documentId={document.id} token={recipient.token} />
<DocumentDownloadButton
className="flex-1"
fileName={document.title}
documentData={documentData}
disabled={document.status !== DocumentStatus.COMPLETED}
/>
</div>
<p className="text-muted-foreground/60 mt-36 text-sm">
Want to send slick signing links like this one?{' '}
<Link
href="https://documenso.com"
className="text-documenso-700 hover:text-documenso-600"
>
Check out Documenso.
</Link>
</p>
</div>
<h2 className="mt-6 max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
You have signed "{document.title}"
</h2>
{match(document.status)
.with(DocumentStatus.COMPLETED, () => (
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
Everyone has signed! You will receive an Email copy of the signed document.
</p>
))
.otherwise(() => (
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
You will receive an Email copy of the signed document once everyone has signed.
</p>
))}
<div className="mt-8 flex w-full max-w-sm items-center justify-center gap-4">
<ShareButton documentId={document.id} token={recipient.token} />
<DocumentDownloadButton
className="flex-1"
fileName={document.title}
documentData={documentData}
disabled={document.status !== DocumentStatus.COMPLETED}
/>
</div>
<p className="text-muted-foreground/60 mt-36 text-sm">
Want to send slick signing links like this one?{' '}
<Link href="https://documenso.com" className="text-documenso-700 hover:text-documenso-600">
Check out Documenso.
</Link>
</p>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { notFound } from 'next/navigation';
import { notFound, redirect } from 'next/navigation';
import { match } from 'ts-pattern';
@@ -9,7 +9,7 @@ import { viewedDocument } from '@documenso/lib/server-only/document/viewed-docum
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import { FieldType } from '@documenso/prisma/client';
import { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
@@ -53,6 +53,13 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
const user = await getServerComponentSession();
if (
document.status === DocumentStatus.COMPLETED ||
recipient.signingStatus === SigningStatus.SIGNED
) {
redirect(`/sign/${token}/complete`);
}
return (
<SigningProvider email={recipient.email} fullName={recipient.name} signature={user?.signature}>
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">

View File

@@ -40,7 +40,7 @@ export const StackAvatarsWithTooltip = ({
return (
<TooltipProvider>
<Tooltip>
<Tooltip delayDuration={200}>
<TooltipTrigger className="flex cursor-pointer">
{children || <StackAvatars recipients={recipients} />}
</TooltipTrigger>

View File

@@ -1,8 +1,6 @@
'use client';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Eye, EyeOff, Loader } from 'lucide-react';
@@ -40,8 +38,6 @@ export type SignInFormProps = {
};
export const SignInForm = ({ className }: SignInFormProps) => {
const searchParams = useSearchParams();
const { toast } = useToast();
const [showPassword, setShowPassword] = useState(false);
@@ -57,36 +53,29 @@ export const SignInForm = ({ className }: SignInFormProps) => {
resolver: zodResolver(ZSignInFormSchema),
});
const errorCode = searchParams?.get('error');
useEffect(() => {
let timeout: NodeJS.Timeout | null = null;
if (isErrorCode(errorCode)) {
timeout = setTimeout(() => {
toast({
variant: 'destructive',
description: ERROR_MESSAGES[errorCode] ?? 'An unknown error occurred',
});
}, 0);
}
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [errorCode, toast]);
const onFormSubmit = async ({ email, password }: TSignInFormSchema) => {
try {
await signIn('credentials', {
const result = await signIn('credentials', {
email,
password,
callbackUrl: LOGIN_REDIRECT_PATH,
}).catch((err) => {
console.error(err);
redirect: false,
});
if (result?.error && isErrorCode(result.error)) {
toast({
variant: 'destructive',
description: ERROR_MESSAGES[result.error],
});
return;
}
if (!result?.url) {
throw new Error('An unknown error occurred');
}
window.location.href = result.url;
} catch (err) {
toast({
title: 'An unknown error occurred',

View File

@@ -0,0 +1,35 @@
import { type Page, expect, test as setup } from '@playwright/test';
import { STORAGE_STATE } from '../../../playwright.config';
const username = process.env.E2E_TEST_AUTHENTICATE_USERNAME || '';
const email = process.env.E2E_TEST_AUTHENTICATE_USER_EMAIL || '';
const password = process.env.E2E_TEST_AUTHENTICATE_USER_PASSWORD || '';
setup('authenticate', async ({ page }: { page: Page }) => {
await page.goto('/signup');
await page.getByLabel('Name').fill(username);
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
const canvas = page.locator('canvas');
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 8, box.y + box.height / 6);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 8, box.y + box.height / 6);
await page.mouse.up();
}
await page.getByRole('button', { name: 'Sign Up' }).click();
await page.goto('/');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('/documents');
await expect(page).toHaveURL('/documents');
await page.context().storageState({ path: STORAGE_STATE });
});

View File

@@ -0,0 +1,57 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD>
1 0 obj
<</Title (documenso)
/Producer (Skia/PDF m118 Google Docs Renderer)>>
endobj
3 0 obj
<</ca 1
/BM /Normal>>
endobj
4 0 obj
<</Length 84>> stream
1 0 0 -1 0 842 cm
q
.75 0 0 .75 0 0 cm
1 1 1 RG 1 1 1 rg
/G3 gs
0 0 794 1123 re
f
Q
endstream
endobj
2 0 obj
<</Type /Page
/Resources <</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
/ExtGState <</G3 3 0 R>>>>
/MediaBox [0 0 596 842]
/Contents 4 0 R
/StructParents 0
/Parent 5 0 R>>
endobj
5 0 obj
<</Type /Pages
/Count 1
/Kids [2 0 R]>>
endobj
6 0 obj
<</Type /Catalog
/Pages 5 0 R>>
endobj
xref
0 7
0000000000 65535 f
0000000015 00000 n
0000000269 00000 n
0000000100 00000 n
0000000137 00000 n
0000000457 00000 n
0000000512 00000 n
trailer
<</Size 7
/Root 6 0 R
/Info 1 0 R>>
startxref
559
%%EOF

View File

@@ -0,0 +1,45 @@
import { type Page, expect, test } from '@playwright/test';
test.use({ storageState: { cookies: [], origins: [] } });
/*
Using them sequentially so the 2nd test
uses the details from the 1st (registration) test
*/
test.describe.configure({ mode: 'serial' });
const username = process.env.E2E_TEST_USERNAME || '';
const email = process.env.E2E_TEST_USER_EMAIL || '';
const password = process.env.E2E_TEST_USER_PASSWORD || '';
test('user can sign up with email and password', async ({ page }: { page: Page }) => {
await page.goto('/signup');
await page.getByLabel('Name').fill(username);
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
const canvas = page.locator('canvas');
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
await page.mouse.up();
}
await page.getByRole('button', { name: 'Sign Up' }).click();
await page.waitForURL('/documents');
await expect(page).toHaveURL('/documents');
});
test('user can login with user and password', async ({ page }: { page: Page }) => {
await page.goto('/signin');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('/documents');
await expect(page).toHaveURL('/documents');
});

View File

@@ -0,0 +1,38 @@
import path from 'path';
import { expect, test } from '../test-fixtures/documents-page/documents-page';
const signer_name = process.env.E2E_TEST_SIGNER_NAME;
const signer_email = process.env.E2E_TEST_SIGNER_EMAIL;
const signing_subject = process.env.E2E_TEST_SIGNING_SUBJECT;
const signing_message = process.env.E2E_TEST_SIGNING_MESSAGE;
if (!signer_name || !signer_email || !signing_subject || !signing_message) {
throw new Error('Required environment variables for tests are not defined');
}
test.describe('Document upload test', () => {
test.beforeEach(async ({ documentsPage }) => {
await documentsPage.uploadDocument(path.join(__dirname, './documenso.pdf'));
});
test('user can see /documents page', async ({ page, documentsPage }) => {
await documentsPage.goToDocumentsPage();
await expect(page).toHaveTitle('Documenso - The Open Source DocuSign Alternative');
});
test('user can upload a document succesfully', async ({ page }) => {
await expect(page.getByText('Drag & drop your document here.')).toBeVisible();
});
test('user can send the document for signing succesfully', async ({ documentsPage, page }) => {
await documentsPage.addSigner(signer_email, signer_name);
await documentsPage.addSignatureField(signer_name);
await documentsPage.addSubjectAndMessage(signing_subject, signing_message);
await expect(page).toHaveURL('/documents');
await expect(page.getByRole('status').locator('div').nth(2)).toHaveText(
'Your document has been sent successfully.',
);
});
});

View File

@@ -0,0 +1,49 @@
import type { Locator, Page } from '@playwright/test';
export class DocumentsPage {
private readonly fileInput: Locator;
private readonly subject: Locator;
private readonly message: Locator;
// eslint-disable-next-line no-unused-vars
constructor(public readonly page: Page) {
this.fileInput = this.page.locator('input[type=file]');
this.subject = this.page
.locator('div')
.filter({ hasText: /^Subject \(Optional\)$/ })
.locator('input');
this.message = this.page
.locator('div')
.filter({ hasText: /^Message \(Optional\)$/ })
.locator('textarea');
}
async goToDocumentsPage() {
await this.page.goto('/documents');
}
async uploadDocument(filePath: string) {
await this.goToDocumentsPage();
await this.fileInput.setInputFiles(filePath);
}
async addSigner(email: string, name: string) {
await this.page.getByLabel('Email*').fill(email);
await this.page.getByLabel('Name').fill(name);
await this.page.getByRole('button', { name: 'Continue' }).click();
}
async addSignatureField(name: string) {
await this.page
.getByRole('button', { name: `${name} Signature` })
.dragTo(this.page.locator('canvas'));
await this.page.getByRole('button', { name: 'Continue' }).click();
}
async addSubjectAndMessage(subject: string, message: string) {
await this.subject.fill(subject);
await this.message.fill(message);
await this.page.getByRole('button', { name: 'Send' }).click();
}
}

View File

@@ -0,0 +1,13 @@
import { test as base } from '@playwright/test';
import { DocumentsPage } from './DocumentsPageObject';
export const test = base.extend<{ documentsPage: DocumentsPage }>({
documentsPage: async ({ page }, use) => {
const documentsPage = new DocumentsPage(page);
await use(documentsPage);
},
});
export { expect } from '@playwright/test';

320
package-lock.json generated
View File

@@ -98,11 +98,13 @@
"react-icons": "^4.8.0",
"react-rnd": "^10.4.1",
"sharp": "0.32.5",
"start-server-and-test": "^2.0.1",
"ts-pattern": "^5.0.5",
"typescript": "5.1.6",
"zod": "^3.21.4"
},
"devDependencies": {
"@playwright/test": "^1.38.0",
"@types/formidable": "^2.0.6",
"@types/luxon": "^3.3.1",
"@types/node": "20.1.0",
@@ -2450,6 +2452,19 @@
"node": ">=6"
}
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@hookform/resolvers": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.0.tgz",
@@ -3786,6 +3801,21 @@
"url": "https://opencollective.com/unts"
}
},
"node_modules/@playwright/test": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
"integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
"dev": true,
"dependencies": {
"playwright": "1.38.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@prisma/client": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.3.1.tgz",
@@ -5435,6 +5465,24 @@
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"node_modules/@sindresorhus/slugify": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz",
@@ -7619,6 +7667,14 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/check-more-types": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
"integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -9124,6 +9180,11 @@
"node": ">=12"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="
},
"node_modules/duplexer2": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
@@ -10328,6 +10389,20 @@
"node": ">=0.10.0"
}
},
"node_modules/event-stream": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
"integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==",
"dependencies": {
"duplexer": "~0.1.1",
"from": "~0",
"map-stream": "~0.1.0",
"pause-stream": "0.0.11",
"split": "0.3",
"stream-combiner": "~0.0.4",
"through": "~2.3.1"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -10727,6 +10802,11 @@
}
}
},
"node_modules/from": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
"integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g=="
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -12165,6 +12245,18 @@
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
"integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="
},
"node_modules/joi": {
"version": "17.10.2",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.10.2.tgz",
"integrity": "sha512-hcVhjBxRNW/is3nNLdGLIjkgXetkeGc2wyhydhz8KumG23Aerk4HPjU5zaPAMRqXQFc0xNqXTC7+zQjxr0GlKA==",
"dependencies": {
"@hapi/hoek": "^9.0.0",
"@hapi/topo": "^5.0.0",
"@sideway/address": "^4.1.3",
"@sideway/formula": "^3.0.1",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jose": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
@@ -12399,6 +12491,14 @@
"language-subtag-registry": "~0.3.2"
}
},
"node_modules/lazy-ass": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
"integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==",
"engines": {
"node": "> 0.8"
}
},
"node_modules/leac": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
@@ -12946,6 +13046,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/map-stream": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
"integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g=="
},
"node_modules/markdown-extensions": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
@@ -14999,6 +15104,14 @@
"node": ">=8"
}
},
"node_modules/pause-stream": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
"integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==",
"dependencies": {
"through": "~2.3"
}
},
"node_modules/pdf-lib": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
@@ -15097,6 +15210,36 @@
"node": ">= 6"
}
},
"node_modules/playwright": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
"integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
"dev": true,
"dependencies": {
"playwright-core": "1.38.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.38.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
"integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/postcss": {
"version": "8.4.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
@@ -15647,6 +15790,20 @@
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
},
"node_modules/ps-tree": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz",
"integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==",
"dependencies": {
"event-stream": "=3.3.4"
},
"bin": {
"ps-tree": "bin/ps-tree.js"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -17267,6 +17424,14 @@
"resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz",
"integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA=="
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -17642,6 +17807,17 @@
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
"integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w=="
},
"node_modules/split": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
"integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==",
"dependencies": {
"through": "2"
},
"engines": {
"node": "*"
}
},
"node_modules/split2": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
@@ -17673,6 +17849,121 @@
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/start-server-and-test": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.0.1.tgz",
"integrity": "sha512-8PFo4DLLLCDMuS51/BEEtE1m9CAXw1LNVtZSS1PzkYQh6Qf9JUwM4huYeSoUumaaoAyuwYBwCa9OsrcpMqcOdQ==",
"dependencies": {
"arg": "^5.0.2",
"bluebird": "3.7.2",
"check-more-types": "2.24.0",
"debug": "4.3.4",
"execa": "5.1.1",
"lazy-ass": "1.6.0",
"ps-tree": "1.2.0",
"wait-on": "7.0.1"
},
"bin": {
"server-test": "src/bin/start.js",
"start-server-and-test": "src/bin/start.js",
"start-test": "src/bin/start.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/start-server-and-test/node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
},
"node_modules/start-server-and-test/node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"node_modules/start-server-and-test/node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
"human-signals": "^2.1.0",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.1",
"onetime": "^5.1.2",
"signal-exit": "^3.0.3",
"strip-final-newline": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/start-server-and-test/node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/start-server-and-test/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/start-server-and-test/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"engines": {
"node": ">=6"
}
},
"node_modules/start-server-and-test/node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/start-server-and-test/node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dependencies": {
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/start-server-and-test/node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"engines": {
"node": ">=6"
}
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -17681,6 +17972,14 @@
"node": ">= 0.6"
}
},
"node_modules/stream-combiner": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
"integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==",
"dependencies": {
"duplexer": "~0.1.1"
}
},
"node_modules/stream-shift": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
@@ -18226,8 +18525,7 @@
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
},
"node_modules/through2": {
"version": "4.0.2",
@@ -19488,6 +19786,24 @@
"d3-timer": "^3.0.1"
}
},
"node_modules/wait-on": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz",
"integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==",
"dependencies": {
"axios": "^0.27.2",
"joi": "^17.7.0",
"lodash": "^4.17.21",
"minimist": "^1.2.7",
"rxjs": "^7.8.0"
},
"bin": {
"wait-on": "bin/wait-on"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

View File

@@ -4,10 +4,12 @@
"build": "turbo run build",
"dev": "turbo run dev --filter=@documenso/web --filter=@documenso/marketing",
"start": "cd apps && cd web && next start",
"test": "cd apps && cd web && npm run test:e2e",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
"prepare": "husky install",
"commitlint": "commitlint --edit",
"ci": "turbo run build test:e2e lint",
"clean": "turbo run clean && rimraf node_modules"
},
"engines": {

View File

@@ -0,0 +1,41 @@
import { prisma } from '@documenso/prisma';
export const deleteUserAndItsData = async (name: string) => {
const user = await prisma.user.findFirst({
where: {
name: {
contains: name,
},
},
});
if (!user) {
throw new Error(`User with name ${name} not found`);
}
const document = await prisma.document.findMany({
where: {
userId: user.id,
},
select: {
documentData: {
select: {
data: true,
},
},
},
});
return Promise.all([
await prisma.user.delete({
where: {
id: user.id,
},
}),
await prisma.documentData.deleteMany({
where: {
data: document[0]?.documentData.data,
},
}),
]);
};

View File

@@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "PasswordResetToken" (
"id" SERIAL NOT NULL,
"token" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" INTEGER NOT NULL,
CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "PasswordResetToken_token_key" ON "PasswordResetToken"("token");
-- AddForeignKey
ALTER TABLE "PasswordResetToken" ADD CONSTRAINT "PasswordResetToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,8 @@
/*
Warnings:
- Added the required column `expiry` to the `PasswordResetToken` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "PasswordResetToken" ADD COLUMN "expiry" TIMESTAMP(3) NOT NULL;

View File

@@ -1,3 +1,5 @@
DROP TABLE IF EXISTS "PasswordResetToken" CASCADE;
-- CreateTable
CREATE TABLE "PasswordResetToken" (
"id" SERIAL NOT NULL,

View File

@@ -148,7 +148,7 @@ const SigningCardContent = ({ className, name }: SigningCardContentProps) => {
return (
<Card
className={cn(
'group mx-auto flex aspect-[21/9] w-full items-center justify-center',
'group z-10 mx-auto flex aspect-[21/9] w-full items-center justify-center',
className,
)}
degrees={-145}
@@ -180,14 +180,14 @@ type SigningCardImageProps = {
const SigningCardImage = ({ signingCelebrationImage }: SigningCardImageProps) => {
return (
<motion.div
className="pointer-events-none absolute -inset-32 -z-10 flex items-center justify-center md:-inset-44 xl:-inset-60 2xl:-inset-80"
className="pointer-events-none absolute -inset-32 -z-50 flex items-center justify-center md:-inset-44 xl:-inset-60 2xl:-inset-80"
initial={{
opacity: 0,
scale: 0.8,
scale: 0.6,
}}
animate={{
scale: 1,
opacity: 0.5,
opacity: 0.6,
}}
transition={{
delay: 0.5,
@@ -197,7 +197,7 @@ const SigningCardImage = ({ signingCelebrationImage }: SigningCardImageProps) =>
<Image
src={signingCelebrationImage}
alt="background pattern"
className="w-full dark:invert dark:sepia"
className="w-full dark:brightness-150 dark:contrast-[70%] dark:invert dark:sepia"
style={{
mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)',
WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)',

View File

@@ -2,13 +2,8 @@
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
".next/**",
"!.next/cache/**"
]
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"clean": {
@@ -17,13 +12,26 @@
"dev": {
"cache": false,
"persistent": true
},
"test:e2e": {
"dependsOn": ["^build"]
}
},
"globalDependencies": [
"**/.env.*local"
],
"globalDependencies": ["**/.env.*local"],
"globalEnv": [
"APP_VERSION",
"E2E_TEST_USERNAME",
"E2E_TEST_USER_EMAIL",
"E2E_TEST_USER_PASSWORD",
"E2E_TEST_SIGNER_NAME",
"E2E_TEST_SIGNER_EMAIL",
"E2E_TEST_SIGNING_SUBJECT",
"E2E_TEST_SIGNING_MESSAGE",
"E2E_TEST_AUTHENTICATE_USERNAME",
"E2E_TEST_AUTHENTICATE_USER_EMAIL",
"E2E_TEST_AUTHENTICATE_USER_PASSWORD",
"NEXTAUTH_URL",
"NEXTAUTH_SECRET",
"NEXT_PUBLIC_PROJECT",