Compare commits

..

2 Commits

Author SHA1 Message Date
Mythie
ea00fec5ac chore: next 13.5.4 2023-10-10 14:13:26 +11:00
Mythie
b5e06c9dcc chore: next 13.5.3 2023-09-26 16:29:15 +10:00
39 changed files with 394 additions and 1159 deletions

View File

@@ -15,20 +15,6 @@ 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

@@ -1,53 +0,0 @@
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,8 +47,3 @@ 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/ea8de2d0-8e33-4527-8a5c-40780142a2ed" alt="Documenso Logo">
<img width="250px" src="https://github.com/documenso/documenso/assets/1309312/cd7823ec-4baa-40b9-be78-4acb3b1c73cb" alt="Documenso Logo">
</a>
<p align="center">
@@ -26,30 +26,37 @@
<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>
> 🦺 Documenso 1.0 is deployed to our <a href="https://documen.so/staging" target="_blank">Staging Environment</a>.
> **🚧 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.**
>
> 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.
> **[Read more on why 👀](https://documenso.com/blog/why-were-doing-a-rewrite)**
# 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://github.com/documenso/documenso/assets/1309312/67e08c98-c153-4115-aa2d-77979bb12c94)">
src="https://user-images.githubusercontent.com/1309312/224570645-167128ee-3e39-4578-85d2-5394d9a0379c.png">
<img style="display: block; height: 120px; width: 24%"
src="https://github.com/documenso/documenso/assets/1309312/040cfbae-3438-4ca3-87f2-ce52c793dcaf">
src="https://user-images.githubusercontent.com/1309312/224570651-0afd12f8-cfe3-49d1-805e-e495af963d91.png">
<img style="display: block; height: 120px; width: 24%"
src="https://github.com/documenso/documenso/assets/1309312/72d445be-41e5-4936-bdba-87ef8e70fa09">
src="https://user-images.githubusercontent.com/1309312/224570655-328d2279-058d-4a3e-b5c3-5cbd8a1f4e05.png">
<img style="display: block; height: 120px; width: 24%"
src="https://github.com/documenso/documenso/assets/1309312/d7b86c0f-a755-4476-a022-a608db2c4633">
src="https://user-images.githubusercontent.com/1309312/224571617-1f3c2811-c1ac-4d7d-b9b0-4ab183731405.png">
<img style="display: block; height: 120px; width: 24%"
src=https://github.com/documenso/documenso/assets/1309312/c0f55116-ab82-433f-a266-f3fc8571d69f">
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">
</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 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.
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.
## Recognition
@@ -58,13 +65,13 @@ Signing documents digitally is fast and easy and should be the best practice for
## 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

@@ -1,54 +0,0 @@
---
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

@@ -1,62 +0,0 @@
---
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.**

View File

@@ -21,7 +21,7 @@
"framer-motion": "^10.12.8",
"lucide-react": "^0.279.0",
"micro": "^10.0.1",
"next": "13.4.19",
"next": "13.5.4",
"next-auth": "4.22.3",
"next-contentlayer": "^0.3.4",
"next-plausible": "^3.10.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

Before

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="relative z-10 text-center text-2xl font-semibold leading-normal md:text-3xl lg:mb-2 lg:text-4xl">
<h2 className="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="relative mt-8 w-full">
<div className="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 bg-transparent backdrop-blur-sm" disabled>
<Button variant="outline" className="flex-1" disabled>
<Share className="mr-2 h-5 w-5" />
Share
</Button>
<DocumentDownloadButton
className="flex-1 bg-transparent backdrop-blur-sm"
className="flex-1"
fileName={document.title}
documentData={document.documentData}
disabled={document.status !== DocumentStatus.COMPLETED}

3
apps/web/.gitignore vendored
View File

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

View File

@@ -1,16 +0,0 @@
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,8 +8,6 @@
"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"
},
@@ -27,7 +25,7 @@
"lucide-react": "^0.279.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"next": "13.4.19",
"next": "13.5.4",
"next-auth": "4.22.3",
"next-plausible": "^3.10.1",
"next-themes": "^0.2.1",
@@ -43,11 +41,9 @@
"sharp": "0.32.5",
"ts-pattern": "^5.0.5",
"typescript": "5.1.6",
"zod": "^3.21.4",
"start-server-and-test": "^2.0.1"
"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",

View File

@@ -1,66 +0,0 @@
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, NextResponse } from 'next/server';
import { ImageResponse } from 'next/server';
import { P, match } from 'ts-pattern';
@@ -21,7 +21,7 @@ type SharePageOpenGraphImageProps = {
params: { slug: string };
};
export async function GET(_request: Request, { params: { slug } }: SharePageOpenGraphImageProps) {
export default async function Image({ 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 async function GET(_request: Request, { params: { slug } }: SharePageOpen
const recipientOrSender = await getRecipientOrSenderByShareLinkSlug({ slug }).catch(() => null);
if (!recipientOrSender) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
return null;
}
const isRecipient = 'Signature' in recipientOrSender;

View File

@@ -1,39 +1,11 @@
import { Metadata } from 'next';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { APP_BASE_URL } from '@documenso/lib/constants/app';
import { Redirect } from './redirect';
type SharePageProps = {
params: { slug: string };
export const metadata: Metadata = {
title: 'Documenso - Share',
};
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');
return <Redirect />;
}

View File

@@ -0,0 +1,11 @@
'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 { SigningCard3D } from '@documenso/ui/components/signing-card';
import { SigningCard } 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 lg:pt-36 xl:pt-44">
<div className="flex flex-col items-center pt-24">
{/* Card with recipient */}
<SigningCard3D name={recipientName} signingCelebrationImage={signingCelebration} />
<SigningCard name={recipientName} signingCelebrationImage={signingCelebration} />
<div className="relative mt-6 flex w-full flex-col items-center">
<div className="mt-6">
{match(document.status)
.with(DocumentStatus.COMPLETED, () => (
<div className="text-documenso-700 flex items-center text-center">
@@ -71,44 +71,41 @@ 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, redirect } from 'next/navigation';
import { notFound } 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 { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client';
import { FieldType } 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,13 +53,6 @@ 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 delayDuration={200}>
<Tooltip>
<TooltipTrigger className="flex cursor-pointer">
{children || <StackAvatars recipients={recipients} />}
</TooltipTrigger>

View File

@@ -1,35 +0,0 @@
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

@@ -1,57 +0,0 @@
%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

@@ -1,45 +0,0 @@
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

@@ -1,38 +0,0 @@
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

@@ -1,49 +0,0 @@
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

@@ -1,13 +0,0 @@
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';

727
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,10 @@
"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

@@ -10,7 +10,7 @@
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"eslint": "^8.40.0",
"eslint-config-next": "13.4.19",
"eslint-config-next": "13.5.4",
"eslint-config-prettier": "^8.8.0",
"eslint-config-turbo": "^1.9.3",
"eslint-plugin-package-json": "^0.1.4",
@@ -18,4 +18,4 @@
"eslint-plugin-react": "^7.32.2",
"typescript": "^5.1.6"
}
}
}

View File

@@ -28,7 +28,7 @@
"bcrypt": "^5.1.0",
"luxon": "^3.4.0",
"nanoid": "^4.0.2",
"next": "13.4.19",
"next": "13.5.4",
"next-auth": "4.22.3",
"pdf-lib": "^1.17.1",
"react": "18.2.0",

View File

@@ -1,41 +0,0 @@
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

@@ -1,15 +0,0 @@
-- 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

@@ -1,8 +0,0 @@
/*
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,5 +1,3 @@
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 z-10 mx-auto flex aspect-[21/9] w-full items-center justify-center',
'group 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-50 flex items-center justify-center md:-inset-44 xl:-inset-60 2xl:-inset-80"
className="pointer-events-none absolute -inset-32 -z-10 flex items-center justify-center md:-inset-44 xl:-inset-60 2xl:-inset-80"
initial={{
opacity: 0,
scale: 0.6,
scale: 0.8,
}}
animate={{
scale: 1,
opacity: 0.6,
opacity: 0.5,
}}
transition={{
delay: 0.5,
@@ -197,7 +197,7 @@ const SigningCardImage = ({ signingCelebrationImage }: SigningCardImageProps) =>
<Image
src={signingCelebrationImage}
alt="background pattern"
className="w-full dark:brightness-150 dark:contrast-[70%] dark:invert dark:sepia"
className="w-full 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

@@ -60,7 +60,7 @@
"framer-motion": "^10.12.8",
"lucide-react": "^0.279.0",
"luxon": "^3.4.2",
"next": "13.4.19",
"next": "13.5.4",
"pdfjs-dist": "3.6.172",
"react-day-picker": "^8.7.1",
"react-hook-form": "^7.45.4",

View File

@@ -2,8 +2,13 @@
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
"dependsOn": [
"^build"
],
"outputs": [
".next/**",
"!.next/cache/**"
]
},
"lint": {},
"clean": {
@@ -12,26 +17,13 @@
"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",