Compare commits

..

21 Commits

Author SHA1 Message Date
Catalin Pit c63047a049 fix: custom email message for self-signers 2024-04-26 17:15:19 +03:00
Catalin Pit 882d569992 chore: fix contributing guide 2024-04-26 16:35:56 +03:00
Catalin Pit 4de2cb10e0 chore: remove list from gitpod page 2024-04-26 15:41:05 +03:00
Catalin Pit d7f2767377 Merge branch 'main' into feat/create-documentation-site 2024-04-26 15:39:14 +03:00
Catalin Pit 1387ce11f9 chore: lots of content updates 2024-04-26 15:35:53 +03:00
Catalin Pit 885809603a chore: added versioning information 2024-04-26 10:47:13 +03:00
Catalin Pit 5a9ac21443 chore: content updates 2024-04-26 10:25:16 +03:00
Catalin Pit c7beda4dd2 chore: finish api auth section 2024-04-25 14:39:04 +03:00
Catalin Pit 39d2630714 chore: started api documentation 2024-04-25 12:07:50 +03:00
Catalin Pit 47d1febad7 chore: added webhook fields documentation 2024-04-25 09:46:54 +03:00
Catalin Pit 442e88cff8 chore: added webhooks content and updated home page 2024-04-22 16:58:00 +03:00
Catalin Pit 2ed4cb636b chore: improved contributing copy 2024-04-22 12:03:45 +03:00
Catalin Pit bd82d46ecd chore: add contributing guide 2024-04-15 11:12:24 +03:00
Catalin Pit 86df82dc9f chore: hide home from nav 2024-04-12 15:16:49 +03:00
Catalin Pit c29e6ea861 chore: added frontmatter plus some changes 2024-04-12 14:40:39 +03:00
Catalin Pit 1f71baaffb chore: some changes 2024-04-12 13:41:52 +03:00
Catalin Pit df699e890c chore: added content and updated settings 2024-04-12 12:05:31 +03:00
Catalin Pit 24ed73ef3e chore: add package.json commands 2024-04-11 20:22:27 +03:00
Catalin Pit 23c4054d23 fix: app name 2024-04-11 20:15:50 +03:00
Catalin Pit b105be2305 fix: invalid key 2024-04-11 17:01:58 +03:00
Catalin Pit 5799e366d1 feat: documentation site 2024-04-11 16:44:22 +03:00
365 changed files with 63668 additions and 21575 deletions
+1 -13
View File
@@ -13,10 +13,6 @@ NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="DEADBEEF"
NEXT_PRIVATE_GOOGLE_CLIENT_ID=""
NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=""
NEXT_PRIVATE_OIDC_WELL_KNOWN=""
NEXT_PRIVATE_OIDC_CLIENT_ID=""
NEXT_PRIVATE_OIDC_CLIENT_SECRET=""
# [[URLS]]
NEXT_PUBLIC_WEBAPP_URL="http://localhost:3000"
NEXT_PUBLIC_MARKETING_URL="http://localhost:3001"
@@ -78,10 +74,8 @@ NEXT_PRIVATE_SMTP_APIKEY_USER=
NEXT_PRIVATE_SMTP_APIKEY=
# OPTIONAL: Defines whether to force the use of TLS.
NEXT_PRIVATE_SMTP_SECURE=
# OPTIONAL: if this is true and NEXT_PRIVATE_SMTP_SECURE is false then TLS is not used even if the server supports STARTTLS extension
NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS=
# REQUIRED: Defines the sender name to use for the from address.
NEXT_PRIVATE_SMTP_FROM_NAME="Documenso"
NEXT_PRIVATE_SMTP_FROM_NAME="No Reply @ Documenso"
# REQUIRED: Defines the email address to use as the from address.
NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@documenso.com"
# OPTIONAL: The API key to use for Resend.com
@@ -105,12 +99,6 @@ NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID=
NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID=
# [[BACKGROUND JOBS]]
NEXT_PRIVATE_JOBS_PROVIDER="local"
NEXT_PRIVATE_TRIGGER_API_KEY=
NEXT_PRIVATE_TRIGGER_API_URL=
NEXT_PRIVATE_INNGEST_EVENT_KEY=
# [[FEATURES]]
# OPTIONAL: Leave blank to disable PostHog and feature flags.
NEXT_PUBLIC_POSTHOG_KEY=""
-1
View File
@@ -4,7 +4,6 @@ module.exports = {
extends: ['@documenso/eslint-config'],
rules: {
'@next/next/no-img-element': 'off',
'no-unreachable': 'error',
},
settings: {
next: {
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
+2 -2
View File
@@ -33,9 +33,9 @@ jobs:
- uses: ./.github/actions/cache-build
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Run Playwright tests
run: npm run ci
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
- name: Check Assigned User's Issue Count
id: parse-comment
uses: actions/github-script@v6
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
-25
View File
@@ -1,25 +0,0 @@
name: Auto Label Assigned Issues
on:
issues:
types: [assigned]
jobs:
label-when-assigned:
runs-on: ubuntu-latest
steps:
- name: Label issue
uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const issue = context.issue;
// To run only on issues and not on PR
if (github.context.payload.issue.pull_request === undefined) {
const labelResponse = await github.rest.issues.addLabels({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
labels: ['status: assigned']
});
}
+1 -1
View File
@@ -17,5 +17,5 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["status: triage"]
labels: ["needs triage"]
})
+2 -2
View File
@@ -2,14 +2,14 @@ name: 'PR Review Reminder'
on:
pull_request:
types: ['opened', 'ready_for_review']
types: ['opened', 'reopened', 'ready_for_review', 'review_requested']
permissions:
pull-requests: write
jobs:
checkPRs:
if: ${{ github.event.pull_request.user.login }} && github.event.action == ('opened' || 'ready_for_review')
if: ${{ github.event.pull_request.user.login }} && github.event.action == ('opened' || 'reopened' || 'ready_for_review' || 'review_requested')
runs-on: ubuntu-latest
steps:
- name: Checkout
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/stale@v5
- uses: actions/stale@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-pr-stale: 90
+17 -3
View File
@@ -6,7 +6,7 @@ tasks:
set -a; source .env &&
export NEXTAUTH_URL="$(gp url 3000)" &&
export NEXT_PUBLIC_WEBAPP_URL="$(gp url 3000)" &&
export NEXT_PUBLIC_MARKETING_URL="$(gp url 3001)"
export NEXT_PUBLIC_MARKETING_URL="$(gp url 3001)"
command: npm run d
ports:
@@ -25,10 +25,20 @@ ports:
- port: 2500
visibility: private
onOpen: ignore
- port: 54320
visibility: private
- port: 54320
visibility: private
onOpen: ignore
github:
prebuilds:
master: true
pullRequests: true
pullRequestsFromForks: true
addCheck: true
addComment: true
addBadge: true
vscode:
extensions:
- aaron-bond.better-comments
@@ -37,5 +47,9 @@ vscode:
- esbenp.prettier-vscode
- mikestead.dotenv
- unifiedjs.vscode-mdx
- GitHub.copilot-chat
- GitHub.copilot-labs
- GitHub.copilot
- GitHub.vscode-pull-request-github
- Prisma.prisma
- VisualStudioExptTeam.vscodeintellicode
+3 -11
View File
@@ -5,19 +5,11 @@
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"eslint.validate": [
"typescript",
"typescriptreact",
"javascript",
"javascriptreact"
],
"eslint.validate": ["typescript", "typescriptreact", "javascript", "javascriptreact"],
"javascript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.useAliasesForRenames": false,
"typescript.enablePromptUseWorkspaceTsdk": true,
"files.eol": "\n",
"editor.tabSize": 2,
"editor.insertSpaces": true,
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
},
}
"editor.insertSpaces": true
}
+1 -13
View File
@@ -73,22 +73,10 @@ Contact us if you are interested in our Enterprise plan for large organizations
<a href="https://cal.com/timurercan/enterprise-customers?utm_source=banner&utm_campaign=oss"><img alt="Book us with Cal.com" src="https://cal.com/book-with-cal-dark.svg" /></a>
## Tech Stack
<p align="left">
<a href="https://www.typescriptlang.org"><img src="https://shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=FFF&style=flat-square" alt="TypeScript"></a>
<a href="https://nextjs.org/"><img src="https://img.shields.io/badge/next.js-000000?style=flat-square&logo=nextdotjs&logoColor=white" alt="NextJS"></a>
<a href="https://prisma.io"><img width="122" height="20" src="http://made-with.prisma.io/indigo.svg" alt="Made with Prisma" /></a>
<a href="https://tailwindcss.com/"><img src="https://img.shields.io/badge/tailwindcss-0F172A?&logo=tailwindcss" alt="Tailwind CSS"></a>
<a href=""><img src="" alt=""></a>
<a href=""><img src="" alt=""></a>
<a href=""><img src="" alt=""></a>
<a href=""><img src="" alt=""></a>
<a href=""><img src="" alt=""></a>
</p>
- [Typescript](https://www.typescriptlang.org/) - Language
- [Next.js](https://nextjs.org/) - Framework
- [Prisma](https://www.prisma.io/) - ORM
- [Prisma](https://www.prisma.io/) - ORM
- [Tailwind](https://tailwindcss.com/) - CSS
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
- [NextAuth.js](https://next-auth.js.org/) - Authentication
+36
View File
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+36
View File
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+33
View File
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
+22
View File
@@ -0,0 +1,22 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
+9
View File
@@ -0,0 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx',
});
module.exports = withNextra(nextConfig);
+29
View File
@@ -0,0 +1,29 @@
{
"name": "@documenso/documentation",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 3002",
"build": "next build",
"start": "next start -p 3002",
"lint:fix": "next lint --fix",
"clean": "rimraf .next && rimraf node_modules",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
},
"dependencies": {
"next": "14.0.3",
"nextra": "^2.13.4",
"nextra-theme-docs": "^2.13.4",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
+7
View File
@@ -0,0 +1,7 @@
import '../styles.css'
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
+44
View File
@@ -0,0 +1,44 @@
{
"index": {
"title": "Home",
"type": "page",
"theme": {
"layout": "full",
"sidebar": false
},
"display": "hidden"
},
"app": {
"title": "Getting Started",
"type": "page"
},
"faq": {
"title": "FAQ",
"type": "page"
},
"local-development": {
"title": "Local Development",
"type": "page"
},
"public-api": {
"title": "Public API",
"type": "page",
"href": "/fcc/"
},
"webhooks": {
"title": "Webhooks",
"type": "page"
},
"contributing": {
"title": "Contributing Guide",
"type": "page"
},
"---": {
"type": "separator"
},
"GitHub": {
"title": "View on GitHub",
"href": "https://documen.so/github",
"newWindow": true
}
}
@@ -0,0 +1 @@
# Getting Started
@@ -0,0 +1,8 @@
{
"index": {
"title": "Get started",
"theme": {
"sidebar": false
}
}
}
@@ -0,0 +1,115 @@
---
title: Contributing Guide
description: Learn how to contribute to Documenso and become part of our community.
---
import { Callout, Steps } from 'nextra/components';
# Contributing to Documenso
If you plan to contribute to Documenso, please take a moment to feel awesome ✨. People like you are what open source is about ♥. Any contributions, no matter how big or small, are highly appreciated 🙏. This guide will help you get started with contributing to Documenso 💻.
## Before Getting Started
<Steps>
### Check the Existing Issues and Pull Requests
Search the existing [issues](https://github.com/documenso/documenso/issues) to see if someone else reported the same issue. Or, check the [existing PRs](https://github.com/documenso/documenso/pulls) to see if someone else is already working on the same thing.
### Creating a New Issue
If there is no issue or PR for the problem you are facing, feel free to create a new issue. Make sure to provide as much detail as possible, including the steps to reproduce the issue.
### Picking an Existing Issue
If you pick an existing issue, take into consideration the discussion on the issue.
### Contributor License Agreement
Accept the [Contributor License Agreement](https://documen.so/cla) to ensure we can accept your contributions.
</Steps>
## Taking Issues
Before taking an issue, ensure that:
- The issue has been assigned the public label.
- The issue is clearly defined and understood.
- No one has been assigned to the issue.
- No one has expressed the intention to work on it.
After that:
1. Comment on the issue with your intention to work on it.
2. Start working on the issue.
Feel free to ask for help, clarification or guidance if needed. We are here to help you.
## Developing
The development branch is `main`, and all pull requests should be made against this branch. Here's how you can get started with developing:
<Steps>
### Set Up Documenso Locally
Check out the [local development](/local-development) guide to set up your local environment.
### Pick a Task
Find an issue to work on or create a new one.
- Before starting to work on an issue, ensure that no one else is working on it. If no one is assigned to the issue, feel free to pick it up by leaving a comment on the issue and asking to get it assigned to you.
Before creating a new issue, check the existing issues to see if someone else has already reported it.
### Create a New Branch
After you're assigned an issue, you can start working on it. Create a new branch for your feature or bug fix.
When creating a branch, make sure that the branch name:
- starts with the correct prefix: `feat/` for new features, `fix/` for bug fixes, etc.
- includes the issue id you are working on (if applicable).
- is descriptive.
```sh
git checkout -b feat/issue-id-your-branch-name
## Example
git checkout -b feat/1234-add-share-button-to-articles
```
In the pull request description, include `references #yyyy` or `fixes #yyyy` to link it to the issue you are working on.
### Implement Your Changes
Start working on the issue you picked up and implement the changes. Make sure to test your changes locally and ensure that they work as expected.
### Open a Pull Request
After implementing your changes, open a pull request against the `main` branch.
</Steps>
<Callout type="info">
If you need help getting started, [join us on Discord](https://documen.so/discord).
</Callout>
## Building
Before pushing code or creating pull requests, please ensure you can successfully create a successful production build. You can build the project by running the following command in your terminal:
```bash
npm run build
```
Once the project builds successfully, you can push your code changes or create a pull request.
<Callout type="info">
Remember to run tests and perform any necessary checks before finalizing your changes. As a
result, we can collaborate more effectively and maintain a high standard of code quality in our
project.
</Callout>
+1
View File
@@ -0,0 +1 @@
# FAQ
+78
View File
@@ -0,0 +1,78 @@
export const Wrapper = ({ children }) => {
return <div className="p-24">{children}</div>;
};
<Wrapper>
# Documentation
Welcome to the official Documenso documentation! This documentation is designed to help you get started with Documenso.
For users, we'll guide you through setting up your Documenso account, creating and organizing your documents, collaborating with team members, and leveraging our advanced features. You'll learn how to get the most out of Documenso.
If you're a developer, you'll find detailed information on how to set up your local development environment, work with our API, and utilize webhooks for seamless integration. We'll provide code examples, best practices, and troubleshooting tips to help you effectively incorporate Documenso's features into your applications.
We aim to make our documentation clear, concise, and easy to navigate. If you have any questions, feedback, or suggestions for improving our documentation, please don't hesitate to reach out to us. We want to make your experience with Documenso as smooth and enjoyable as possible.
Let's get started!
## About Documenso
Signing documents digitally should be 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.
## Our tech stack:
- [Typescript](https://www.typescriptlang.org/) - Language
- [Next.js](https://nextjs.org/) - Framework
- [Prisma](https://www.prisma.io/) - ORM
- [Tailwind](https://tailwindcss.com/) - CSS
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
- [NextAuth.js](https://next-auth.js.org/) - Authentication
- [react-email](https://react.email/) - Email Templates
- [tRPC](https://trpc.io/) - API
- [@documenso/pdf-sign](https://www.npmjs.com/package/@documenso/pdf-sign) - PDF Signatures
- [React-PDF](https://github.com/wojtekmaj/react-pdf) - Viewing PDFs
- [PDF-Lib](https://github.com/Hopding/pdf-lib) - PDF manipulation
- [Stripe](https://stripe.com/) - Payments
- [Vercel](https://vercel.com) - Hosting
<div className="mt-16 flex items-center justify-center gap-4">
<a href="https://documen.so/discord">
<img
src="https://img.shields.io/badge/Discord-documen.so/discord-%235865F2"
alt="Join Documenso on Discord"
/>
</a>
<a href="https://github.com/documenso/documenso/stargazers">
<img src="https://img.shields.io/github/stars/documenso/documenso" alt="Github Stars" />
</a>
<a href="https://github.com/documenso/documenso/blob/main/LICENSE">
<img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License" />
</a>
<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>
<a href="https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso">
<img
alt="open in devcontainer"
src="https://img.shields.io/static/v1?label=Dev%20Containers&message=Enabled&color=blue&logo=visualstudiocode"
/>
</a>
<a href="https://github.com/documenso/documenso/blob/main/CODE_OF_CONDUCT.md">
<img
src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg"
alt="Contributor Covenant"
/>
</a>
</div>
</Wrapper>
@@ -0,0 +1,7 @@
{
"index": "Home",
"quickstart": "Developer Quickstart",
"manual": "Manual Setup",
"docker": "Docker",
"gitpod": "Gitpod"
}
@@ -0,0 +1,177 @@
---
title: Docker Development
description: Set up Documenso using Docker for local development.
---
import { Callout, Steps } from 'nextra/components';
# Docker
The following guide will walk you through setting up Documenso using Docker. You can choose between a Docker Compose production setup or a standalone container.
We provide a Docker container for Documenso, which is published on both DockerHub and GitHub Container Registry.
- DockerHub: [https://hub.docker.com/r/documenso/documenso](https://hub.docker.com/r/documenso/documenso)
- GitHub Container Registry: [https://ghcr.io/documenso/documenso](https://ghcr.io/documenso/documenso)
You can pull the Docker image from either of these registries and run it with your preferred container hosting provider.
Please note that you will need to provide environment variables for connecting to the database, mail server, and other services.
## Prerequisites
Before you begin, ensure that you have the following installed:
- Docker
- Docker Compose (if using the Docker Compose setup)
## Option 1: Production Docker Compose Setup
This setup includes a PostgreSQL database and the Documenso application. You will need to provide your own SMTP details using environment variables.
<Steps>
### Download the Docker Compose File
Download the Docker Compose file from the Documenso repository - [compose.yml](https://raw.githubusercontent.com/documenso/documenso/release/docker/production/compose.yml).
### Navigate to the `compose.yml` File
Once downloaded, navigate to the directory containing the `compose.yml` file.
### Set Up Environment Variables
Create a `.env` file in the same directory as the `compose.yml` file.
Then add your SMTP details as well as the following environment variables:
```bash
NEXTAUTH_SECRET="<your-secret>"
NEXT_PRIVATE_ENCRYPTION_KEY="<your-key>"
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="<your-secondary-key>"
NEXT_PUBLIC_WEBAPP_URL="<your-url>"
NEXT_PRIVATE_SMTP_TRANSPORT="smtp-auth"
NEXT_PRIVATE_SMTP_HOST="<your-host>"
NEXT_PRIVATE_SMTP_PORT=<your-port>
NEXT_PRIVATE_SMTP_USERNAME="<your-username>"
NEXT_PRIVATE_SMTP_PASSWORD="<your-password>"
```
### Update the Volume Binding
The `cert.p12` file is required to sign and encrypt documents, so you must provide your key file. Update the volume binding in the `compose.yml` file to point to your key file:
```yaml
volumes:
- /path/to/your/keyfile.p12:/opt/documenso/cert.p12
```
After updating the volume binding, save the `compose.yml` file and run the following command to start the containers:
```bash
docker-compose --env-file ./.env -d up
```
The command will start the PostgreSQL database and the Documenso application containers.
### Access the Application
Access the Documenso application by visiting `http://localhost:3000` in your web browser.
</Steps>
## Option 2: Standalone Docker Container
If you prefer to host the Documenso application on a specific container provider, you can use the pre-built Docker image from DockerHub or GitHub's Package Registry. Note that you will need to provide your own database and SMTP host.
<Steps>
### Pull the Docker Image
Pull the Documenso Docker image from DockerHub:
```bash
docker pull documenso/documenso
```
Or, pull the image from GitHub Container Registry:
```bash
docker pull ghcr.io/documenso/documenso
```
### Run the Docker Container
Run the Docker container with the required environment variables:
```bash
docker run -d \
-p 3000:3000 \
-e NEXTAUTH_URL="<your-nextauth-url>"
-e NEXTAUTH_SECRET="<your-nextauth-secret>"
-e NEXT_PRIVATE_ENCRYPTION_KEY="<your-next-private-encryption-key>"
-e NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="<your-next-private-encryption-secondary-key>"
-e NEXT_PUBLIC_WEBAPP_URL="<your-next-public-webapp-url>"
-e NEXT_PRIVATE_DATABASE_URL="<your-next-private-database-url>"
-e NEXT_PRIVATE_DIRECT_DATABASE_URL="<your-next-private-database-url>"
-e NEXT_PRIVATE_SMTP_TRANSPORT="<your-next-private-smtp-transport>"
-e NEXT_PRIVATE_SMTP_FROM_NAME="<your-next-private-smtp-from-name>"
-e NEXT_PRIVATE_SMTP_FROM_ADDRESS="<your-next-private-smtp-from-address>"
-v /path/to/your/keyfile.p12:/opt/documenso/cert.p12
documenso/documenso
```
Replace the placeholders with the actual values.
### Access the Application
Access the Documenso application by visiting the URL you provided for the `NEXT_PUBLIC_WEBAPP_URL` environment variable in your web browser.
</Steps>
## Advanced Configuration
The environment variables listed above are a subset of those available for configuring Documenso. The table below provides a complete list of environment variables and their descriptions.
| Variable | Description |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `PORT` | The port on which the Documenso application runs. It defaults to `3000`. |
| `NEXTAUTH_URL` | The URL for the NextAuth.js authentication service. |
| `NEXTAUTH_SECRET` | The secret key used by NextAuth.js for encryption and signing. |
| `NEXT_PRIVATE_ENCRYPTION_KEY` | The primary encryption key for symmetric encryption and decryption (at least 32 characters). |
| `NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY` | The secondary encryption key for symmetric encryption and decryption (at least 32 characters). |
| `NEXT_PRIVATE_GOOGLE_CLIENT_ID` | The Google client ID for Google authentication (optional). |
| `NEXT_PRIVATE_GOOGLE_CLIENT_SECRET` | The Google client secret for Google authentication (optional). |
| `NEXT_PUBLIC_WEBAPP_URL` | The URL for the web application. |
| `NEXT_PRIVATE_DATABASE_URL` | The URL for the primary database connection (with connection pooling). |
| `NEXT_PRIVATE_DIRECT_DATABASE_URL` | The URL for the direct database connection (without connection pooling). |
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | The signing transport to use. Available options: local (default) |
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | The passphrase for the key file. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | The base64-encoded contents of the key file will be used instead of the file path. |
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` | The path to the key file, default `/opt/documenso/cert.p12`. |
| `NEXT_PUBLIC_UPLOAD_TRANSPORT` | The transport for file uploads (database or s3). |
| `NEXT_PRIVATE_UPLOAD_ENDPOINT` | The endpoint for the S3 storage transport (for third-party S3-compatible providers). |
| `NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE` | Whether to force path-style URLs for the S3 storage transport. |
| `NEXT_PRIVATE_UPLOAD_REGION` | The region for the S3 storage transport (defaults to us-east-1). |
| `NEXT_PRIVATE_UPLOAD_BUCKET` | The bucket to use for the S3 storage transport. |
| `NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID` | The access key ID for the S3 storage transport. |
| `NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY` | The secret access key for the S3 storage transport. |
| `NEXT_PRIVATE_SMTP_TRANSPORT` | The transport to send emails (smtp-auth, smtp-api, resend, or mailchannels). |
| `NEXT_PRIVATE_SMTP_HOST` | The host for the SMTP server for SMTP transports. |
| `NEXT_PRIVATE_SMTP_PORT` | The port for the SMTP server for SMTP transports. |
| `NEXT_PRIVATE_SMTP_USERNAME` | The username for the SMTP server for the `smtp-auth` transport. |
| `NEXT_PRIVATE_SMTP_PASSWORD` | The password for the SMTP server for the `smtp-auth` transport. |
| `NEXT_PRIVATE_SMTP_APIKEY_USER` | The API key user for the SMTP server for the `smtp-api` transport. |
| `NEXT_PRIVATE_SMTP_APIKEY` | The API key for the SMTP server for the `smtp-api` transport. |
| `NEXT_PRIVATE_SMTP_SECURE` | Whether to force the use of TLS for the SMTP server for SMTP transports. |
| `NEXT_PRIVATE_SMTP_FROM_ADDRESS` | The email address for the "from" address. |
| `NEXT_PRIVATE_SMTP_FROM_NAME` | The sender name for the "from" address. |
| `NEXT_PRIVATE_RESEND_API_KEY` | The API key for Resend.com for the `resend` transport. |
| `NEXT_PRIVATE_MAILCHANNELS_API_KEY` | The optional API key for MailChannels (if using a proxy) for the `mailchannels` transport. |
| `NEXT_PRIVATE_MAILCHANNELS_ENDPOINT` | The optional endpoint for the MailChannels API (if using a proxy) for the `mailchannels` transport. |
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN` | The domain for DKIM signing with MailChannels for the `mailchannels` transport. |
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR` | The selector for DKIM signing with MailChannels for the `mailchannels` transport. |
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY` | The private key for DKIM signing with MailChannels for the `mailchannels` transport. |
| `NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT` | The maximum document upload limit displayed to the user (in MB). |
| `NEXT_PUBLIC_POSTHOG_KEY` | The optional PostHog key for analytics and feature flags. |
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Whether to disable user signups through the /signup page. |
@@ -0,0 +1,10 @@
---
title: Run in Gitpod
description: Get started with Documenso in a ready-to-use Gitpod workspace in your browser.
---
# Run in Gitpod
Click below to launch a ready-to-use Gitpod workspace in your browser.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/documenso/documenso)
@@ -0,0 +1,15 @@
---
title: Local development
description: Learn how to set up Documenso for local development.
---
# Local development
There are multiple ways of setting up Documenso for local development. At the moment of writing this documentation, there are 4 ways of running Documenso locally:
- [Using the developer quickstart](/local-development/quickstart)
- [Manually setting up the development environment](/local-development/manual)
- [Using the Docker image](/local-development/docker)
- [Using Gitpod](/local-development/gitpod)
Pick the one that fits your needs the best.
@@ -0,0 +1,77 @@
---
title: Manual Setup
description: Manually set up Documenso on your machine for local development.
---
import { Callout, Steps } from 'nextra/components';
# Manual Setup
Follow these steps to set up Documenso on your local machine:
<Steps>
### Fork Documenso
Fork the [Documenso repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
### Clone Repository
After forking the repository, clone it to your local device by using the following command:
```bash
git clone https://github.com/<your-username>/documenso
```
### Install Dependencies
Run `npm i` in the root directory to install the dependencies required for the project.
### Set Up Environment Variables
Set up the following environment variables in the `.env` file:
```bash
NEXTAUTH_URL
NEXTAUTH_SECRET
NEXT_PUBLIC_WEBAPP_URL
NEXT_PUBLIC_MARKETING_URL
NEXT_PRIVATE_DATABASE_URL
NEXT_PRIVATE_DIRECT_DATABASE_URL
NEXT_PRIVATE_SMTP_FROM_NAME
NEXT_PRIVATE_SMTP_FROM_ADDRESS
```
Alternatively, you can run `cp .env.example .env` to get started with our handpicked defaults.
### Create Database Schema
Create the database schema by running the following command:
```bash
npm run prisma:migrate-dev
```
### Optional: Seed the Database
Seed the database with test data by running the following command:
```bash
npm run prisma:seed -w @documenso/prisma
```
### Start the Application
Run `npm run dev` in the root directory to start the application.
### Access the Application
Access the Documenso application by visiting `http://localhost:3000` in your web browser.
</Steps>
<Callout type="info">
Optional: Create your signing certificate. To generate your own using these steps and a Linux
Terminal or Windows Subsystem for Linux (WSL), see **[Create your signing
certificate](./SIGNING.md)**.
</Callout>
@@ -0,0 +1,66 @@
---
title: Developer Quickstart
description: Quickly set up Documenso on your machine for local development with Docker and Docker Compose.
---
import { Callout, Steps } from 'nextra/components';
# Developer Quickstart
<Callout type="info">
**Note**: This guide assumes that you have both [docker](https://docs.docker.com/get-docker/) and
[docker-compose](https://docs.docker.com/compose/) installed on your machine.
</Callout>
Want to get up and running quickly? Follow these steps:
<Steps>
### Fork Documenso
Fork the [Documenso repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
### Clone Repository
After forking the repository, clone it to your local device by using the following command:
```bash
git clone https://github.com/<your-username>/documenso
```
### Set Up Environment Variables
Set up your environment variables in the `.env` file using the `.env.example` file as a reference.
Alternatively, you can run `cp .env.example .env` to get started with our handpicked defaults.
### Start Database and Mail Server
Run `npm run dx` in the root directory.
This will spin up a Postgres database and inbucket mailserver in a docker container.
### Start the Application
Run `npm run dev` in the root directory to start the application.
### Optional: Fasten the Process
Want it even faster? Just use:
```sh
npm run d
```
</Steps>
### Access Points for the Project
You can access the following services:
- Main application - http://localhost:3000
- Incoming Mail Access - http://localhost:9000
- Database Connection Details:
- Port: 54320
- Connection: Use your favourite database client to connect to the database.
- S3 Storage Dashboard - http://localhost:9001
@@ -0,0 +1,6 @@
{
"intro": "Intro",
"authentication": "Authentication",
"versioning": "Versioning",
"api-reference": "API Reference"
}
@@ -0,0 +1,6 @@
---
title: 'API Reference'
description: 'Reference documentation for the Documenso public API.'
---
# API Reference
@@ -0,0 +1,67 @@
---
title: 'API Authentication'
description: 'Learn how to create a Documenso API key and authenticate your API requests.'
---
# API Authentication
Documenso uses API keys for authentication. An API key is a unique token that is generated for each client. The client must provide the key whenever it makes an API call. This way, Documenso can identify the clients making the requests and authorize their access to the API.
## Creating an API Key
To create an API key, navigate to the user settings page. Click on your avatar in the top right corner of the dashboard and select "**[User settings](https://app.documenso.com/settings)**" from the dropdown menu.
![A screenshot of the Documenso's dashboard that shows the dropdown menu when you click on your user avatar](/public-api-images/documenso-user-dropdown-menu.webp)
Once you're on the user settings page, navigate to the "**[API Tokens](https://app.documenso.com/settings/tokens)**" tab. The "API Token" page lists your existing keys and enables you to create new ones.
![A screenshot of the Documenso's user settings page that shows the API Tokens page](/public-api-images/api-tokens-page-documenso.webp)
To create a new API key, you must:
- Choose a name (e.g. "zapier-key")
- we recommend using a descriptive name that helps you quickly identify the key and its purpose
- Choose an expiration date
- you can set the key to never expire or choose when to become invalid: 7 days, 1 month, 3 months, 6 months, or 1 year
After providing the required information, click the "Create token" button to generate the API key.
![A screenshot of the newly created API token in the Documenso dashboard](/public-api-images/documenso-api-key-blurred.webp)
Once you've created the token, Documenso will display the key on the screen. Make sure to copy the key and store it securely. You won't be able to see the key again once you refresh/leave the page.
## Using the API Key
To authenticate your API requests, you must include the API key in the `Authorization` request header. The format is `Authorization: api_xxxxxxxxxxxxxxxx`.
Here's a sample API request using cURL:
```bash
curl --location 'https://app.documenso.com/api/v1/documents?page=1&perPage=1' \
--header 'Authorization: api_xxxxxxxxxxxxxxxx'
```
Here's a sample response from the API based on the above cURL request:
```json
{
"documents": [
{
"id": 11,
"userId": 2,
"teamId": null,
"title": "documenso",
"status": "PENDING",
"documentDataId": "ab2ecm1npk11rt5sp398waf7h",
"createdAt": "2024-04-25T11:05:18.420Z",
"updatedAt": "2024-04-25T11:05:36.328Z",
"completedAt": null
}
],
"totalPages": 1
}
```
![A screenshot of a cURL request to the Documenso public API with the API key in the Authorization header](/public-api-images/documenso-api-authorization.webp)
The API key has access to your account and all its resources. Please keep it secure and do not share it with others. If you suspect that your key has been compromised, you can revoke it from the API Tokens page in your user settings.
@@ -0,0 +1,22 @@
---
title: 'Public API'
description: 'Learn how to interact with your documents programmatically using the Documenso public API.'
---
# Public API
Documenso provides a public REST API that enables you to interact with your documents programmatically. The API exposes various HTTP endpoints that allow you to perform operations such as:
- retrieving, uploading, deleting, and sending documents for signing
- creating, updating, and deleting recipients
- creating, updating, and deleting document fields
The documentation walks you through creating API keys and using them to authenticate your API requests. You'll also learn about the available endpoints, request and response formats, and how to use the API.
## Swagger Documentation
There is also the [Swagger documentation](https://app.documenso.com/api/v1/openapi) available, which provides information about the API endpoints, request parameters, response formats, and authentication methods.
## Availability
The API is available to individual users and teams.
@@ -0,0 +1,18 @@
---
title: 'API Versioning'
description: 'Versioning information for the Documenso public API.'
---
import { Callout } from 'nextra/components';
# API Versioning
Documenso uses API versioning to manage changes to the public API. This allows us to introduce new features, fix bugs, and make other changes without breaking existing integrations.
<Callout type="info">The current version of the API is `v1`.</Callout>
The API version is specified in the URL. For example, the base URL for the `v1` API is `https://app.documenso.com/api/v1`.
We may make changes to the API without incrementing the version number. We will always try to avoid breaking changes, but in some cases, it may be necessary to make changes that are not backward compatible. In these cases, we will increment the version number and provide information about the changes in the release notes.
Also, we may deprecate certain features or endpoints in the API. When we deprecate a feature or endpoint, we will provide information about the deprecation in the release notes and give a timeline for when the feature or endpoint will be removed.
@@ -0,0 +1,8 @@
{
"intro": {
"title": "Get started",
"theme": {
"sidebar": false
}
}
}
+350
View File
@@ -0,0 +1,350 @@
---
title: 'Webhooks'
description: 'Learn how to use webhooks to receive real-time notifications about your documents.'
---
# Webhooks
Webhooks are HTTP callbacks that are triggered by specific events. When the user subscribes to a specific event and that event occurs, the webhook makes an HTTP request to the URL provided by the user. The request can be a simple notification or can carry a payload with more information about the event.
Some of the common use cases for webhooks include:
1. **Real-time Data Syncing**: Webhooks provide a way to keep data in sync across different platforms. For example, you can keep your system up-to-date with your Documenso documents by subscribing to events like document creation or signing.
2. **Automating Workflows**: They can trigger automated workflows that start when an event occurs. For example, the webhook could trigger an email when a document is signed.
3. **Integrating Third-Party Services**: Webhooks can be used to integrate Documenso with third-party services. For example, you could use a webhook to send data to a CRM system when a document is signed.
Documenso supports Webhooks and allows you to subscribe to the following events:
- `document.created`
- `document.sent`
- `document.opened`
- `document.signed`
- `document.completed`
## Create a webhook subscription
You can create a webhook subscription from the user settings page. Click on your avatar in the top right corner of the dashboard and select "**[User settings](https://app.documenso.com/settings)**" from the dropdown menu.
![A screenshot of the Documenso's dashboard that shows the dropdown menu when you click on your user avatar](/webhook-images/dashboard-user-dropdown-menu.webp)
Then, navigate to the "**[Webhooks](https://app.documenso.com/settings/webhooks)**" tab, where you can see a list of your existing webhooks and create new ones.
![A screenshot of the Documenso's user settings page that shows the Webhooks tab and the Create Webhook button](/webhook-images/webhooks-settings-page.webp)
Clicking on the "**Create Webhook**" button opens a modal to create a new webhook subscription.
To create a new webhook subscription, you need to provide the following information:
- Enter the webhook URL that will receive the event payload.
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`.
- Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Signature` header of the request.
![A screenshot of the Create Webhook modal that shows the URL input field and the event checkboxes](/webhook-images/webhooks-page-create-webhook-modal.webp)
After you have filled in the required information, click on the "**Create Webhook**" button to save your subscription.
The screenshot below illustrates a newly created webhook subscription.
![A screenshot of the Documenso's user settings page that shows the newly created webhook subscription](/webhook-images/webhooks-page.webp)
You can edit or delete your webhook subscriptions by clicking the "**Edit**" or "**Delete**" buttons next to the webhook.
## Webhook fields
The payload sent to the webhook URL contains the following fields:
| Field | Type | Description |
| -------------------------------------------- | --------- | ---------------------------------------------------- |
| `event` | string | The type of event that triggered the webhook. |
| `payload.id` | number | The id of the document. |
| `payload.userId` | number | The id of the user who owns the document. |
| `payload.authOptions` | json? | Authentication options for the document. |
| `payload.formValues` | json? | Form values for the document. |
| `payload.title` | string | The name of the document. |
| `payload.status` | string | The current status of the document. |
| `payload.documentDataId` | string | The identifier for the document data. |
| `payload.createdAt` | datetime | The creation date and time of the document. |
| `payload.updatedAt` | datetime | The last update date and time of the document. |
| `payload.completedAt` | datetime? | The completion date and time of the document. |
| `payload.deletedAt` | datetime? | The deletion date and time of the document. |
| `payload.teamId` | number? | The id of the team. |
| `payload.documentData.id` | string | The id of the document data. |
| `payload.documentData.type` | string | The type of the document data. |
| `payload.documentData.data` | string | The data of the document. |
| `payload.documentData.initialData` | string | The initial data of the document. |
| `payload.Recipient[].id` | number | The id of the recipient. |
| `payload.Recipient[].documentId` | number? | The id the document associated with the recipient. |
| `payload.Recipient[].templateId` | number? | The template identifier for the recipient. |
| `payload.Recipient[].email` | string | The email address of the recipient. |
| `payload.Recipient[].name` | string | The name of the recipient. |
| `payload.Recipient[].token` | string | The token associated with the recipient. |
| `payload.Recipient[].expired` | datetime? | The expiration status of the recipient. |
| `payload.Recipient[].signedAt` | datetime? | The date and time the recipient signed the document. |
| `payload.Recipient[].authOptions.accessAuth` | json? | Access authentication options. |
| `payload.Recipient[].authOptions.actionAuth` | json? | Action authentication options. |
| `payload.Recipient[].role` | string | The role of the recipient. |
| `payload.Recipient[].readStatus` | string | The read status of the document by the recipient. |
| `payload.Recipient[].signingStatus` | string | The signing status of the recipient. |
| `payload.Recipient[].sendStatus` | string | The send status of the document to the recipient. |
| `createdAt` | datetime | The creation date and time of the webhook event. |
| `webhookEndpoint` | string | The endpoint URL where the webhook is sent. |
## Webhook event payload example
When an event that you have subscribed to occurs, Documenso will send a POST request to the specified webhook URL with a payload containing information about the event.
## Example payloads
Below are examples of the payloads that are sent for each of the supported events. The payloads are sent as JSON data in the body of the POST request.
Example payload for the `document.created` event:
```json
{
"event": "DOCUMENT_CREATED",
"payload": {
"id": 10,
"userId": 1,
"authOptions": null,
"formValues": null,
"title": "documenso.pdf",
"status": "DRAFT",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:44:43.341Z",
"completedAt": null,
"deletedAt": null,
"teamId": null
},
"createdAt": "2024-04-22T11:44:44.779Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
Example payload for the `document.sent` event:
```json
{
"event": "DOCUMENT_SENT",
"payload": {
"id": 10,
"userId": 1,
"authOptions": null,
"formValues": null,
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:48:07.569Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"expired": null,
"signedAt": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "VIEWER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
},
{
"id": 53,
"documentId": 10,
"templateId": null,
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"expired": null,
"signedAt": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:48:07.945Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
Example payload for the `document.opened` event:
```json
{
"event": "DOCUMENT_OPENED",
"payload": {
"id": 10,
"userId": 1,
"authOptions": null,
"formValues": null,
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:48:07.569Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"expired": null,
"signedAt": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "VIEWER",
"readStatus": "OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
],
"documentData": {
"id": "hs8qz1ktr9204jn7mg6c5dxy0",
"type": "S3_PATH",
"data": "9753/xzqrshtlpokm/documenso.pdf",
"initialData": "9753/xzqrshtlpokm/documenso.pdf"
}
},
"createdAt": "2024-04-22T11:50:26.174Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
Example payload for the `document.signed` event:
```json
{
"event": "DOCUMENT_SIGNED",
"payload": {
"id": 10,
"userId": 1,
"authOptions": null,
"formValues": null,
"title": "documenso.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:52:05.708Z",
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"Recipient": [
{
"id": 51,
"documentId": 10,
"templateId": null,
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:52:18.577Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
Example payload for the `document.completed` event:
```json
{
"event": "DOCUMENT_COMPLETED",
"payload": {
"id": 10,
"userId": 1,
"authOptions": null,
"formValues": null,
"title": "documenso.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:52:05.708Z",
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"documentData": {
"id": "hs8qz1ktr9204jn7mg6c5dxy0",
"type": "S3_PATH",
"data": "bk9p1h7x0s3m/documenso-signed.pdf",
"initialData": "9753/xzqrshtlpokm/documenso.pdf"
},
"Recipient": [
{
"id": 50,
"documentId": 10,
"templateId": null,
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"expired": null,
"signedAt": "2024-04-22T11:51:10.055Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "VIEWER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
"sendStatus": "SENT"
},
{
"id": 51,
"documentId": 10,
"templateId": null,
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:52:18.277Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
## Availability
Webhooks are available to individual users and teams.
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+20
View File
@@ -0,0 +1,20 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
};
export default config;
+49
View File
@@ -0,0 +1,49 @@
import { useConfig } from 'nextra-theme-docs';
import type { DocsThemeConfig } from 'nextra-theme-docs';
const themeConfig: DocsThemeConfig = {
logo: <span>Documenso Docs</span>,
head: function useHead() {
const config = useConfig<{ title?: string; description?: string }>();
const title = `${config.frontMatter.title} | Documenso Docs` || 'Documenso Docs';
const description = config.frontMatter.description || 'The official Documenso documentation';
return (
<>
<meta httpEquiv="Content-Language" content="en" />
<meta name="title" content={title} />
<meta name="og:title" content={title} />
<meta name="description" content={description} />
<meta name="og:description" content={description} />
</>
);
},
project: {
link: 'https://documen.so/github',
},
chat: {
link: 'https://documen.so/discord',
},
docsRepositoryBase: 'https://github.com/documenso/documenso/tree/main/apps/documentation',
footer: {
text: (
<span>
{new Date().getFullYear()} ©{' '}
<a href="https://documen.so" target="_blank">
Documenso
</a>
.
</span>
),
},
primaryHue: 100,
primarySaturation: 48.47,
useNextSeoProps() {
return {
titleTemplate: '%s | Documenso Docs',
};
},
};
export default themeConfig;
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
},
"forceConsistentCasingInFileNames": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
@@ -1,63 +0,0 @@
---
title: Launching Direct Links
description: Today, we are launching direct links to templates, a new and async way to get documents signed.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-06-17
tags:
- Announcement
- Direct Links
- Profiles
---
<figure>
<MdxNextImage
src="/blog/direct-links.png"
width="1400"
height="884"
alt="Direct Links in Templats List View"
/>
<figcaption className="text-center">Direct Template Links - Async signing, anytime.</figcaption>
</figure>
> TLDR; We are launching direct links to templates. With direct links, a document is created from a template every time anyone signs the link. Links can be public.
## Sync or Async?
> Quick refresher on Sync vs. Async: Sync means everyone has to wait for me until they can continue their work. Async means everyone can and does their work at the time that fits best.
Digital signing has become almost as normalized as email when doing business. While not 100% of companies are onboarded on digital signatures yet, hardly anyone is surprised when receiving a link to sign something digitally. As we got used to the user experience of sending emails, we also got used to the experience of sending document signature requests, with all the downsides:
- I have to become active each time before anything can happen: I need to send a signature request
- My counterpart has to wait for me to send: "Did you send the signing link yet?"
- I need to monitor the requests I started for completion: "I sent you a link yesterday; please check it out."
## Introducing Direct Links
Today, we are introducing a new paradigm to signing: Async Direct Signing Links. Direct links are attached to a template and can be used anytime by anyone using the link. You set up the signature experience and flow once using all existing template mechanisms and you are done. You can provide anyone with the link so they can sign whenever they need to. You can even post the link publicly if you want to maximize its reach, i.e. for sales contracts.
<video
id="vid"
width="100%"
src="https://github.com/documenso/design/assets/1309312/129f690b-29b4-4a11-b9a0-14fc6648e611"
autoPlay
loop
muted
></video>
## Embrace Async
So, how does this help anyone? You may still need to send a signature request to people, but in the cases you don't, you are not forced to anymore. Need an NDA? Check out our standing NDA link. A customer needs an updated Form W-9? Just use the company W-9 Link; it always has the most up-to-date form. You can even go as far as publicly posting a link to a software development or design contract any potential customer can sign anytime. Can they talk to you first? Sure, but if they don't need to or already have to, they go straight to the link. The process of actively sending has gotten us used to using a sync paradigm (I send, you receive and sign, and I get the result), whereas an async one (you sign whenever it suits you, and I become active only then, if at all) is way better suited. Adding more approval and signature steps makes sure you still control the outcome, but the process becomes a lot more efficient. For example, you can grab your own copy of the early adopter's pledge here if you missed it: [documen.so/pledge](https://documen.so/pledge).
> Take a minute to think about every signing request you send and whether they really require you to be part of the transaction. Could they be outsourced to the recipient and only reviewed once their part is done?
## Coming Soon: Profiles
The best place to put your public links will be your **Documenso profile**, which is also close to launching. We want to get a feel for how links are used and move on to profiles shortly after. Want to try out direct links? Grab a free account here to get started: [documen.so/free](https://documen.so/free).
As always, we want to hear from you on [Twitter / X](https://twitter.com/eltimuro) (DMs are open) or [Discord](https://documen.so/discord) if you have any questions, ideas, or comments.
Best from Hamburg\
Timur
@@ -1,68 +0,0 @@
---
title: Documenso Profiles Are Here
description: Today, we are launching Documenso Profiles, a new way to let your peers sign your documents.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-07-01
tags:
- Announcement
- Direct Links
- Profiles
---
<figure>
<MdxNextImage
src="/blog/profile1.png"
width="1260"
height="1260"
alt="Documenso Profile of Timur"
/>
<figcaption className="text-center">Let people sign anytime with Documenso Profiles. Try it [with my profile](https://app.documenso.com/p/timur).</figcaption>
</figure>
> TLDR; We are launching a Documenso Profile where you can display your templates for everyone to sign at any time.
## Introducing Documenso Profiles
Today, Im excited to announce that we are launching Documenso Profiles 🎉 While we have been focussing on the conventional signing experience so far, Direct Links and now profiles are our first steps to bring some long-awaited innovation back to digital signatures.
Documenso Profiles allows you to share any template with a public link in a very easy-to-understand way. Adding templates to your profiles allows everyone to sign your documents just when they need to. Forms, NDAs, Disclaimers, and even contracts are now available anytime they are needed. Profiles turn the classic signing flow on its head by letting the recipient sign first before the documents owner becomes active. Booking links (e.g., Cal.com) and customer self-service portals (e.g., Stripe Billing Portal) are becoming the norm, so its time we did the same for signing.
## The Best Way to Share Your Singing Links
With profiles, we want to offer you the best way to share your standing Documenso Template Links in a public place. You can add your Documenso Profile to your social profiles, Email footer, Video Description, or wherever your audience, customers, and partners interact with you. The profile will be a public, trusted place to ensure you know who you are dealing with.
Looking at the classical social media fake profile problem, we know this is tricky to achieve. We will pay close attention to how profiles are used and how we can help the community use them easily and securely. As a first step towards this direction, we are introducing a trust badge next to your name. There will be 3 levels to start:
- Free Users: No Badge
- Paid Users: Green Badge
- Early Adopters: Gold Badge
<figure>
<MdxNextImage
src="/blog/profile2.png"
width="1260"
height="1260"
alt="Documenso Profile Edit Screen"
/>
<figcaption className="text-center">Add and remove templates anytime.</figcaption>
</figure>
## An Open Economy built on Documenso
We see offering profiles as a first step towards creating a full economy on top of the open signing ecosystem we envision. While we want to keep building the product with the community, we also want an economy to grow on the open tech we create. This includes using the tech (profiles) or offering service on top of the tech (hosting and customization). Our ecosystem is still young, and the goal is not to have Documenso Inc. as the only commercial actor but to fill our own niche in a global and thriving open ecosystem. One of our guiding principles is solving things once and for all. While our focus will be on the core signing product, we want to enable others to offer templates for use on profiles or privately. A lot of contracts, forms, and other paperwork have been recreated countless times. Offering high-quality, peer-reviewed templates is a natural extension of the current platform. There will be free templates, offered as marketing for their creators, and paid templates, offered as actual products.
## What's next
While an open signing economy is really exciting, we know it will take time to mature. Building out a mature ecosystem for builders and entrepreneurs takes time. While this aspect of Documenso matures, we will be focussing on the core of the platform: Letting you integrate and embed Documenso wherever you want it.
If you already have a Documenso account, you can [activate your profile here](https://app.documenso.com/settings/public-profile).
Dont have an account and want to check out profiles? You can do so using our [free plan](https://documen.so/free).
As always, we want to hear from you on [Twitter / X](https://twitter.com/eltimuro) (DMs are open) or [Discord](https://documen.so/discord) if you have any questions, ideas, or comments.
Best from Hamburg\
Timur
@@ -1,103 +0,0 @@
---
title: How Documenso Enhances Contract Management for Freelancers, Helping Them Close More Clients Efficiently
description: Making it easy for the customer to sign the contract after they say yes is critical. Let take a look how Documenso can help.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-06-20
tags:
- Freelancer
- Proposal
- Productivity
---
## Yes to Yes
> [Check out Part 1](https://documen.so/freelance-proposal) to learn about signing freelance proposals with Documenso and getting your first yes
A basic rule of sales is going from "yes to yes”. Outlining the main points of working together in a proposal is a good way to get to your first yes since it reduces details and focuses on the main points of the work at hand. After being on the same page about the work and getting the first yes, it's time to draw up a formal contract. While agreeing to the proposal has some weight as well, the legal contract formalizes the commitments of both sides in an enforceable way. Having clear legal terms on payments, unexpected cases, and even dissolving the partnership helps both parties to feel assured about what to expect.
### **Digital Signatures for the Win**
Digitally signing documents accelerates contract closure, enhancing both speed and security. Parties can review and sign documents within minutes, eliminating the days required for manual signatures or even weeks with traditional mail. Beyond these efficiency gains, digital signatures boost trust by making the process secure and auditable. Once signed, digital documents are immutable, and every step is logged.
Documenso simplifies this process, allowing you to send contracts effortlessly. As an open-source solution, our product's integrity and security are verifiable by anyone, which is why thousands of users rely on Documenso for their signing needs. Discover more at [https://documen.so/open](https://documen.so/open).
## Preparing the Contract
As a freelancer, obtaining a contract template ensures you have a standardized and professional agreement ready for new clients, helping to protect your interests and clarify project terms. While there are many good templates out there, be sure to verify that they fit your case since contracts are often very specific to a certain case. Always consider having your contract checked by a legal professional if it's a high-value transaction.
Here is a quick checklist of what your contract should include:
### Checklist
- Names and Addresses of you and your client
- Scope of Work to be performed, deadlines and deliverables
- Payment Terms, Payment Schedules, and Pricing
- A clear timeline
- Provisions for unexpected extra work
- Intellectual Property Rights Provisions
- Confidentiality and Non-Disclosure Agreements, if needed
- Termination Clauses: Condition and terms when the contract can be terminated, including notice period and compensation
- Indemnity and Liability
- Dispute Resolution
- Provisions ensuring changes can only be made in writing
- Completeness Agreement: Both parties state this is the full extent of the agreement
- Severability Clause ensuring minor errors will not endanger the whole contract
- The signees with name, role, and date
## Getting the Signature
Once you have your contract ready, you can upload it and add recipients and signature fields. To add a more personal touch, consider adding a personal message to the signature request.
<figure>
<MdxNextImage
src="/blog/l1.png"
width="1260"
height="630"
alt="Manually Copy Signature Link by Hovering of the Recipient"
/>
<figcaption className="text-center">
Copy recipient links to send them for a personal touch manually.
</figcaption>
</figure>
You can also copy the link for each recipient after sending it and send it via another channel e.g. WhatsApp with a personal message. To further customize the experience, you can define a redirect when your customer signs the contract to redirect them to a Cal.com Link to get started, a Thank You Page, or a Form.
<figure>
<MdxNextImage
src="/blog/l2.png"
width="1260"
height="630"
alt="Redirect Link in Advanced Settings"
/>
<figcaption className="text-center">
Redirect after Signing for a more personal experience.
</figcaption>
</figure>
The more you add to the workflow, the more important it is to keep up to date with the process. Using Zapier, you can add a variety of notifications, from email to Discord messages, to keep a good overview and respond quickly. It's not just about getting the signatures; it's about creating the workflow that provides the best experience for you and your customers.
<figure>
<MdxNextImage
src="/blog/l3.png"
width="1260"
height="630"
alt="Zapier Documenso Discord Integration"
/>
<figcaption className="text-center">
Trigger any kind of notification with[Zapier](https://documen.so/zapier)
</figcaption>
</figure>
### Conclusion
Sending a contract to clients using Documenso makes the process fast and easy. Seeing if your contract was signed or even read helps you understand where you are in the process. You can use the [Documenso Free Plan](https://app.documenso.com/signup?utm_source=blog-freelancer-contract) to send 5 contracts per month. Digital signing in 2024 is the best practice for professionals seeking the most efficient way to get business done.
Let us know what you think and what we can improve. Connect with us on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord) if you have any questions or comments! We're always here and would love to hear from you :)
Best from Hamburg\
Timur
@@ -1,76 +0,0 @@
---
title: How Documenso Helps Freelancers Close More Clients Efficiently
description: Reducing friction when sending proposals is critical to closing new clients. By using Documenso, freelancers can save time, enhance client interactions, and ultimately close more deals efficiently.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-06-14
tags:
- Freelancer
- Proposal
- Productivity
---
Getting new clients, or maybe even your first client, to sign with you is at the very core of freelance work. Whether you develop software, create designs, or market products, it all starts with a signature from you and your future customer. Closing a customer usually means agreeing on a proposal first and then signing a formal agreement afterward. Signing proposals and contracts is fast, easy, and painless with Documenso, so let's take a look.
## Understanding Proposal and Contracts
### 1. Initial Proposal
> Agreeing on what needs to be done and terms for payment
A proposal will include the scope of the work (what does the customer want done?), desired deliverables (documents, code, features, videos, etc.), timelines, payment terms (one-time, monthly, per hour), and prices (e.g. $60/ hour, $5k one-time). A proposal is important for both sides to be clear about the goal and the terms that apply. Customers usually decide based on the proposal if your offer is what they want.
### 2. Formal Contract
> After the client signs the proposal, a contract can be signed, formalizing the agreement and adding detailed legal terms.
Once the terms are agreed upon, a more formal document should specify the terms of working together, especially legal details such as confidentiality, indemnification, governing law, etc. The contract provides clarity on legal details for both sides and formalizes their claims.
Sending one or even multiple documents to a potential client and having them send them back signed (in the worst case, they send it via actual mail) is time-consuming for both sides. It also introduces friction at a time when making it as easy as possible for your potential client to say yes should be your number one goal.
### **Digital Signatures for the Win**
Signing documents digitally makes closing proposals and contracts faster and more secure. Each party can review and sign the documents in minutes instead of days (inserting the signatures manually via PDF editor) or even weeks (using conventional mail). Apart from the efficiency gains, signing digitally also increases trust by making the process more secure and auditable. Digitally signed documents cant be changed after the fact, and every step of the process is logged.
Documenso lets you reap these benefits by sending proposals and contracts with minimal effort. Being open source, the whole world can verify our product and how we deliver on these promises, which is why thousands of users already trust Documenso for their signing needs: [https://documen.so/open](https://documen.so/open).
## Preparing the Proposal
If you already have a proposal template, create a new version for your client and export it to PDF. If your tool doesnt support that, your system's “PDF printer” lets you create a PDF from almost any tool by using the print function. If you do not have a template yet, you can find a lot of content and guides on the matter through a quick Google search. Here is a quick checklist of what your proposal should cover:
- A clear and concise title
- Your contact information
- Date of the proposal/ validity period
- Experience, qualifications, and prior relevant projects
- A summary of the project
- A detailed description of the project
- Goals, outcomes, and deliverables
- Tasks and activities to achieve the goals and outcomes
- A timeline with milestones and deadlines
- Pricing terms and payment schedule
- Summary of major terms for the coming contract
## Sending the Proposal
If you dont have a Documenso Account yet, you can [create one for free](https://documen.so/signup?utm_source=blog-freelancer-proposal). Once you sign up, you can upload your proposal PDF by simply dragging it into the upload area. Add your potential client as a recipient, add a signature field, and you are done! You can track the status of your proposal simply by clicking the Document in the overview. Documenso will also notify you once the proposal is signed.
<video
id="vid"
width="100%"
src="https://github.com/documenso/design/assets/1309312/050a1501-b562-4b1e-97b5-a46fc0da8246"
autoPlay
loop
muted
></video>
### Conclusion
Sending a proposal to potential clients using Documenso makes getting to the first “yes” fast and easy. Seeing if your proposal was signed or even read helps you to get a feel for where you are in the process. You can use the [Documenso Free Plan](https://app.documenso.com/signup?utm_source=blog-freelancer-proposal) to send 5 proposals per month. Digital Signing in 2024 is the best practice for all professionals looking for the most efficient way to get business done.
> [Check out Part 2](https://documen.so/freelance-contract) to learn about signing freelance contracts with Documenso.
Let us know what you think and what we can improve. Which field types are you missing? Connect with us on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord) if you have any questions or comments! We're always here and would love to hear from you :)
Best from Hamburg\
Timur
@@ -1,95 +0,0 @@
---
title: How to Sign an NDA online (fast)
description: Signing an NDA with Documenso direct links is amazingly fast. Lets look at how to make it even faster.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-06-27
tags:
- NDA
- Direct Links
- Productivity
---
<figure>
<MdxNextImage
src="/blog/nda.jpg"
width="1400"
height="884"
alt="Direct Links in Templates List View"
/>
<figcaption className="text-center">A generic document saying "NDA" to underline this article is about NDAs.</figcaption>
</figure>
> TLDR; Documenso makes sending NDAs faster, faster with templates, and even still faster using direct links.
## What is an NDA?
An NDA, or non-disclosure agreement, is a legal contract that establishes a confidential relationship. The parties involved agree not to disclose information covered by the agreement. NDAs are often used to protect sensitive information or trade secrets and to ensure that such information isn't made public by the recipient without permission. They are commonly used in business settings, such as during negotiations or when new employees are hired who will have access to proprietary information.
## Do I need an NDA?
> Disclaimer: This is not legal advice, and the most important legal questions are often ultra-case-specific and should be discussed with a legal professional
There is a solid amount of debate around the question of whether an NDA actually protects anyone and is worth the friction. Investors scoff at the idea of a startup requiring them to sign an NDA before disclosing their "billion dollar idea" as they see hundreds of them and are aware that without proper execution, there is nothing to protect.
In another classical example, a big company and a small company e.g. a startup, sign an NDA before going into detail for partnership talks. While this seems prudent, given the resource asymmetry, the startup probably won't be able to litigate against breach of contract successfully. If Microsoft (not saying they do) breaks your NDA, you will hardly sue them into a settlement.
That being said, as with most contracts, NDAs can be useful if both parties keep the spirit of the agreement. In this case, detailing in writing what can and cannot be disclosed is good for managing expectations and building trust. NDAs are also common practice in merger and acquisition projects and are often part of hiring critical roles within a company.
### Level 1: Basic Signing
If you need to sign an NDA, signing it with Documenso is incredibly fast already. Let's take a look at how to make it even faster. Simply uploading and sending is the most straightforward way to get this done. It works like this:
- Upload the NDA PDF template
- Add the recipients
- place the signature fields
- Hit send
- Sign the NDA while or after waiting for your counterpart to sign
<video
id="vid"
width="100%"
src="https://github.com/documenso/design/raw/main/blog/nda1.webm"
autoPlay
loop
muted
controls
></video>
### Level 2: Using Templates
If you have to sign the same NDA multiple times with different people, you can create a template to save time. Creating a template is just as easy as creating a document, just skipping the sending step. After creating the template for your NDA, you can create a signable document with just 1 click. Simply fill in the recipient email, and you are done.
> Pro Tip: Check "Send Document" to immediately send it after filling out the recipient if you are familiar with the template.
<video
id="vid"
width="100%"
src="https://github.com/documenso/design/raw/main/blog/nda2.webm"
autoPlay
loop
muted
controls
></video>
<figcaption className="text-center">
You can send out templates without even going through the full flow.
</figcaption>
### Level 3: Using a Direct Link
Using a pre-defined template is pretty fast, but can we make it even faster? Yes, we can! By adding a direct link to your NDA template and publishing it (internally or even externally). A [Direct Link](http://documenso.com/blog/announcing-direct-links) lets people sign your NDA without you lifting a finger. Everyone with access to the link can sign it at any time, making discussions of who sends what when a thing of the past. You can use Direct links with a pre-signed template for maximum convenience or with a second signer/ approver from your side to keep control over the process.
You can try it here and [sign a demo NDA](https://documen.so/demo-nda) with me.
> Pro Tip: Use [Zapier](https://documen.so/zapier) to get notified of that platform of your choice as soon as someone signs your link.
## Conclusion
Signing NDAs is not always effective, but it can be necessary, so be sure to use a tool to make it easy and fast. Documenso is a great DocuSign alternative that helps you get it done. If you need to get an NDA out today, you can use the [Documenso Free plan](https://documen.so/free), which gives you 5 signatures per month and 3 Direct Link templates.
As always, we want to hear from you on [Twitter / X](https://twitter.com/eltimuro) (DMs are open) or [Discord](https://documen.so/discord) if you have any questions, ideas, or comments.
Best from Hamburg\
Timur
@@ -1,52 +0,0 @@
---
title: Sunsetting the Early Adopters Plan
description: We reached or Early Adopter cap and not transition to our regular pricing 🎉
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-06-12
tags:
- Early Adopters
- Pricing
- Open Startup
---
<figure>
<MdxNextImage
src="/blog/sunset.jpg"
width="1260"
height="630"
alt="A beautiful sunset as a metaphor for the Early Adopter phase ending"
/>
<figcaption className="text-center">
"Being early is, uh, good." -Unknown
</figcaption>
</figure>
> TLDR; The Early Adopters Plan ended, and we have a new pricing. If you are an Early Adopter, reach out for a Discord community badge 🏅
# The End of the Beginning
12 months, 13 releases, and 344 merged pull requests after announcing the Early Adopter plan in our first-ever launch week, and we hit the cap of 100. Documenso has changed and grown a lot since then. For us, this is a great milestone towards our broader mission of bringing open signing to the world since now we are joined by a community consisting of contributors and early customers all around the world.
# The New Plans
Starting today, we are sunsetting the Early Adopter Plan in favor of our new, more nuanced pricing model. The Early Adopter plan will succeeded by the **Individual plan**, which is still priced at $30/mo. The Individual plans will still include unlimited signatures and recipients since this aligns with our core belief of empowering our users wherever possible. If you managed to grab an Early Adopter plan, reach out on X or Discord to receive a special community badge. Early Adopters are meant to get preferential treatment where possible.
Previously soft-launched as part of Early Adopters, we are officially introducing the **Team Plan** to our pricing for customers requiring multi-user accounts. Priced at $50/ mo. for 5 users, this plan offers unlimited signature volume as well. Additional users can be added for $10/mo. as needed. We have carefully crafted the billing of teams to ensure that dynamic changes are accurately reflected at the end of each billing cycle, providing you with a fair-value pricing structure.
Our **Free Plan** stays unchanged, offering coverage to casual users and an easy way to try out Documenso or start developing.
Check out our [new pricing page here](https://documen.so/pricing). We also updated our [open page](https://documen.so/open) to reflect the end of Early Adopters. The metric now counts active subscriptions from Individuals and Teams.
# API Access
All plans include access to the API as per our philosophy, making Documenso an open platform, and allowing everyone to build on it, no matter how big or small. Besides the Free Plan's 5 signatures per month limit, the API does not have access restrictions. Even the free plan can keep using the API after using its signature volume for non-signing operations like reading, editing, and even creating documents. Since the individual plan technically allows for running a Fortune 500 company for $30/ mo., plan we are adding a fair use clause here: You are free to use the API "a lot" if you are a big organization trying to stay on the Individual Plan we will ask to have a word about upgrading (which might make sense anyway considering your requirements). Fair use excludes Early Adopters, which we consider limitless by any measure. If you need clarification on whether your case is covered under fair use, you can contact us on Discord or support@documenso.com. It's probably fine, though.
We also have a lot in the pipeline, and we are excited to share everything with you soon. A Big Shoutout to all Early Adopters. We salute you, and you will receive the preferred treatment where possible.
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).
Best from Hamburg\
Timur
@@ -1,51 +0,0 @@
---
title: Top 3 Signing Efficiency Hacks for Freelancers
description: Streamlining signing contracts and paperwork is crucial for freelancers looking to save time and focus on their business. Take a look at these 3 tips on how Documenso can help you.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-07-04
tags:
- Productivity
- Zapier
- Direct Links
---
<figure>
<MdxNextImage
src="/blog/zen.webp"
width="1400"
height="884"
alt="Direct Links in Templates List View"
/>
<figcaption className="text-center">Documenso helps you reduce your cognitive load while running your business.</figcaption>
</figure>
> TLDR; Set up notifications using Zapier, use a redirect to send users to the next step, and start working async with direct links.
Signing all the paperwork of a freelance business can be a headache. Besides adding to the daily workload, it can pull much focus from your core tasks. Let's take a look at how Documenso can help you keep the cognitive load of admin tasks to a minimum:
## Tip 1: Set Up Notifications
> At Documenso, we pipe almost everything relevant into Discord. This helps us naturally keep track and start conversations.
Getting customers and partners to sign off on critical agreements is no easy feat. It's even harder when keeping track forces you to switch between channels (email, WhatsApp, Discord, Slack, Notion, etc.). Setting up custom notifications for read or signed documents can help you keep track naturally. Using the Documenso [Zapier Integration](https://documen.so/zapier) lets you funnel important document events to the place where your focus is. Pipe send, read, and sign notifications directly to your Telegram account. Keep up to date on the go. Sync document completion events to your CRM. Ideally, a signed document does not give you any "homework”. Besides, you are already busy getting started on the actual work.
## Tip 2: Use Redirects
Your client signed the deal? Your designer signed their statement of work? Great. Don't risk losing steam getting from one step to the next. Your customers and partners have just as much noise around them as you. Help them to stay on task by doing the next thing easily. Documenso lets you set up redirects to send signers to where they need to go next. Send your newest customer to a booking page for onboarding. Forward your designer to the next briefing you prepared. Keeping up flow is critical; sending everyone where they need to go while in focus mode helps get things done faster.
## Tip 3: Go Async with Direct Links
> Try Direct Links by signing our [Supporters Pledge](https://documen.so/pledge)
Your customer wanting to approve an additional budget is great. Its so great. In fact, you want to get on it as soon as possible and not have a calm moment until you send them something to sign. Even if you are fast, you are not mind-ready, though. So, what if your customers could do it whenever they like?
Direct Links lets you set up proposal templates and contracts, which are ready to sign anytime. You can include them in your [initial proposal](https://documen.so/freelance-proposal) or on your [public profile](https://documen.so/profiles). This way, customers can get more of your offering whenever they want.
## Conclusion
Setting up notifications, redirects, and direct signing links using your favorite open source DocuSign alternative helps streamline your business and reduce the cognitive load of managing it. You can get everything mentioned here plus 5 free monthly signatures in the [Documenso Free Plan](https://documen.so/free).
As always, we want to hear from you on [Twitter / X](https://twitter.com/eltimuro) (DMs are open) or [Discord](https://documen.so/discord) if you have any questions, ideas, or comments.
Best from Hamburg\
Timur
-75
View File
@@ -1,75 +0,0 @@
---
title: Changelog - Documenso
---
# Changelog
Check out what's new in the latest version and read our thoughts on it. For more technical details, you can find our releases on GitHub [here](https://github.com/documenso/documenso/releases). You can find our [release candidates here](https://github.com/documenso/documenso/tags).
---
## v1.5.6 (latest)
### <small>Released 28th June 2024</small>
> This release contains [11 fixes](https://github.com/documenso/documenso/releases/tag/v1.5.6)
### 🕗 Show Creation Time
We are now displaying the document creation time in the documents view. This allows for easier identification of multiple documents created on the same day.
### 🔗 Direct Template Links
With this release, we are introducing direct link templates. This allows you to statically link to any template and let anyone with the link sign it at any time. A new document is created in your account when a template is signed. Templates with direct links still support all other template features, allowing you to create intricate workflows triggered by the signers.
Learn more about Direct Links [on our blog](https://documenso.com/blog/announcing-direct-links) or try them by signing the [Documenso Supporters Pledge](https://documen.so/pledge).
### 🛂 OpenID Connect (OIDC) Support
Thanks to [Matt Kilgore](https://github.com/tankerkiller125), Documenso now supports OIDC as an authentication provider. This allows self-hosted users to define whatever identity provider they want as long as it supports the OIDC. Azure, Zitadel, Authentik, KeyCloak, and Google all support OIDC.
---
## v1.5.5
### <small>Released 6th May 2024</small>
> This release contains [20 fixes](https://github.com/documenso/documenso/releases/tag/v1.5.5)
### ✅ Show Completed Fields
Fields completed by other recipients are now visible to everyone to communicate the state of the document better and allow users an informed decision on what they are signing.
### ⬇️ Download Completed Documents via API
Completed documents can now be downloaded via the API using this new endpoint:
**GET /API/V1//DOCUMENTS/\{ID\}/DOWNLOAD**
Check out the full Open API docs here: [https://documen.so/openapi](https://documen.so/openapi)
### Adding Yourself as a Signer
Adding yourself as a signer is now just one click away.
---
## v1.5.4
### <small>Released 11th April 2024</small>
> This release contains [21 fixes](https://github.com/documenso/documenso/releases/tag/v1.5.4)
#### 🔑 Passkeys
To improve security and usability for high-security setups, we added passkeys with this release. Passkeys can now be used to log in or re-authenticate each signature for high-compliance cases.
#### 📄 Signing Certificate & Audit Log
On the security/ compliance side, we also added Signing Certificates and Audit Logs. Every signed document now has a certificate attached, showing technical details of the signature to improve transparency and security. Further, every action on a document from creation to completion is now logged in the audit log to guarantee the integrity of the process.
#### 🔏🦀 @documenso/pdf-sign
We are pretty hyped about this one: Since version 0.9, we relied on https://github.com/vbuch/node-signpdf to add the digital signatures to our documents. Since signing is at the heart of Documenso, we created our own rust-based library for signing. As of 1.5.4, Documenso's signing runs on @documenso/pdf-sign. The library offers a better architecture to enable signing with private keys that are not stored locally (e.g. via HSM). We are in the process of cleaning up the library to open source it like the rest of Documenso 🌱 The library will also help us to offer Long Term Validation (LTV) for signatures soon. While we are currently limited to signing with PKCS7-B, eventually, we plan to support all common signing standards like PAdES, CAdES, and XAdES.
---
-5
View File
@@ -18,10 +18,6 @@ const FONT_CAVEAT_BYTES = fs.readFileSync(
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
);
const FONT_NOTO_SANS_BYTES = fs.readFileSync(
path.join(__dirname, '../../packages/assets/fonts/noto-sans.ttf'),
);
/** @type {import('next').NextConfig} */
const config = {
experimental: {
@@ -42,7 +38,6 @@ const config = {
env: {
NEXT_PUBLIC_PROJECT: 'marketing',
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
FONT_NOTO_SANS_URI: `data:font/ttf;base64,${FONT_NOTO_SANS_BYTES.toString('base64')}`,
},
modularizeImports: {
'lucide-react': {
+2 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/marketing",
"version": "1.6.0-rc.2",
"version": "1.2.3",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@@ -21,9 +21,6 @@
"@hookform/resolvers": "^3.1.0",
"@openstatus/react": "^0.0.3",
"contentlayer": "^0.3.4",
"embla-carousel": "^8.1.3",
"embla-carousel-autoplay": "^8.1.3",
"embla-carousel-react": "^8.1.3",
"framer-motion": "^10.12.8",
"lucide-react": "^0.279.0",
"luxon": "^3.4.0",
@@ -41,7 +38,7 @@
"react-hook-form": "^7.43.9",
"react-icons": "^4.11.0",
"recharts": "^2.7.2",
"sharp": "0.32.6",
"sharp": "^0.33.1",
"typescript": "5.2.2",
"zod": "^3.22.4"
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -47,6 +47,14 @@ export const TEAM_MEMBERS = [
engagement: 'Full-Time',
joinDate: 'October 9th, 2023',
},
{
name: 'Adithya Krishna',
role: 'Software Engineer - II',
salary: '-',
location: 'India',
engagement: 'Full-Time',
joinDate: 'December 1st, 2023',
},
];
export const FUNDING_RAISED = [
@@ -3,11 +3,11 @@
import { DateTime } from 'luxon';
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import type { GetCompletedDocumentsMonthlyResult } from '@documenso/lib/server-only/user/get-monthly-completed-document';
import type { GetUserMonthlyGrowthResult } from '@documenso/lib/server-only/user/get-user-monthly-growth';
export type MonthlyCompletedDocumentsChartProps = {
className?: string;
data: GetCompletedDocumentsMonthlyResult;
data: GetUserMonthlyGrowthResult;
};
export const MonthlyCompletedDocumentsChart = ({
@@ -247,8 +247,8 @@ export default async function OpenPage() {
<BarMetric<EarlyAdoptersType>
data={EARLY_ADOPTERS_DATA}
metricKey="earlyAdopters"
title="Total Customers"
label="Total Customers"
title="Early Adopters"
label="Early Adopters"
className="col-span-12 lg:col-span-6"
extraInfo={<OpenPageTooltip />}
/>
@@ -29,7 +29,7 @@ export function OpenPageTooltip() {
</svg>
</TooltipTrigger>
<TooltipContent>
<p>Customers with an Active Subscriptions.</p>
<p>Active Subscriptions.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
@@ -3,11 +3,11 @@
import { DateTime } from 'luxon';
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import type { GetCompletedDocumentsMonthlyResult } from '@documenso/lib/server-only/user/get-monthly-completed-document';
import type { GetUserMonthlyGrowthResult } from '@documenso/lib/server-only/user/get-user-monthly-growth';
export type TotalSignedDocumentsChartProps = {
className?: string;
data: GetCompletedDocumentsMonthlyResult;
data: GetUserMonthlyGrowthResult;
};
export const TotalSignedDocumentsChart = ({ className, data }: TotalSignedDocumentsChartProps) => {
@@ -9,7 +9,6 @@ import {
} from '@documenso/ui/primitives/accordion';
import { Button } from '@documenso/ui/primitives/button';
import { Enterprise } from '~/components/(marketing)/enterprise';
import { PricingTable } from '~/components/(marketing)/pricing-table';
export const metadata: Metadata = {
@@ -43,10 +42,6 @@ export default function PricingPage() {
<PricingTable />
</div>
<div className="mt-12">
<Enterprise />
</div>
<div className="mx-auto mt-36 max-w-2xl">
<h2 className="text-center text-2xl font-semibold">
None of these work for you? Try self-hosting!
@@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { base64 } from '@documenso/lib/universal/base64';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentDataType, Prisma } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
@@ -115,7 +115,7 @@ export const SinglePlayerClient = () => {
}
try {
const putFileData = await putPdfFile(uploadedFile.file);
const putFileData = await putFile(uploadedFile.file);
const documentToken = await createSinglePlayerDocument({
documentData: {
@@ -248,7 +248,6 @@ export const SinglePlayerClient = () => {
recipients={uploadedFile ? [placeholderRecipient] : []}
fields={fields}
onSubmit={onFieldsSubmit}
canGoBack={true}
isDocumentPdfLoaded={true}
/>
</fieldset>
@@ -34,18 +34,17 @@ export const Callout = ({ starCount }: CalloutProps) => {
return (
<div className="mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-4">
<Link href="https://app.documenso.com/signup?utm_source=marketing-callout">
<Button
type="button"
variant="outline"
className="rounded-full bg-transparent backdrop-blur-sm"
>
Try our Free Plan
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
No Credit Card required
</span>
</Button>
</Link>
<Button
type="button"
variant="outline"
className="rounded-full bg-transparent backdrop-blur-sm"
onClick={onSignUpClick}
>
Claim Early Adopter Plan
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
$30/mo
</span>
</Button>
<Link
href="https://github.com/documenso/documenso"
@@ -1,267 +0,0 @@
'use client';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import Autoplay from 'embla-carousel-autoplay';
import useEmblaCarousel from 'embla-carousel-react';
import { useTheme } from 'next-themes';
import { Card } from '@documenso/ui/primitives/card';
import { Progress } from '@documenso/ui/primitives/progress';
import { Slide } from './slide';
const SLIDES = [
{
label: 'Signing Process',
type: 'video',
srcLight: 'https://github.com/documenso/design/raw/main/marketing/signing.webm',
srcDark: 'https://github.com/documenso/design/raw/main/marketing/dark/signing.webm',
},
{
label: 'Teams',
type: 'video',
srcLight: 'https://github.com/documenso/design/raw/main/marketing/teams.webm',
srcDark: 'https://github.com/documenso/design/raw/main/marketing/dark/teams.webm',
},
{
label: 'Zapier',
type: 'video',
srcLight: 'https://github.com/documenso/design/raw/main/marketing/zapier.webm',
srcDark: 'https://github.com/documenso/design/raw/main/marketing/dark/zapier.webm',
},
{
label: 'Direct Link',
type: 'video',
srcLight: 'https://github.com/documenso/design/raw/main/marketing/direct-links.webm',
srcDark: 'https://github.com/documenso/design/raw/main/marketing/dark/direct-links.webm',
},
{
label: 'Webhooks',
type: 'video',
srcLight: 'https://github.com/documenso/design/raw/main/marketing/webhooks.webm',
srcDark: 'https://github.com/documenso/design/raw/main/marketing/dark/webhooks.webm',
},
{
label: 'API',
type: 'video',
srcLight: 'https://github.com/documenso/design/raw/main/marketing/api.webm',
srcDark: 'https://github.com/documenso/design/raw/main/marketing/dark/api.webm',
},
{
label: 'Profile',
type: 'video',
srcLight: 'https://github.com/documenso/design/raw/main/marketing/profile_teaser.webm',
srcDark: 'https://github.com/documenso/design/raw/main/marketing/dark/profile_teaser.webm',
},
];
export const Carousel = () => {
const slides = SLIDES;
const [_isPlaying, setIsPlaying] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const [progress, setProgress] = useState(0);
const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
const [autoplayDelay, setAutoplayDelay] = useState<number[]>([]);
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, [
Autoplay({ playOnInit: true, delay: autoplayDelay[selectedIndex] || 5000 }),
]);
const [emblaThumbsRef, emblaThumbsApi] = useEmblaCarousel(
{
loop: true,
containScroll: 'keepSnaps',
dragFree: true,
},
[Autoplay({ playOnInit: true, delay: autoplayDelay[selectedIndex] || 5000 })],
);
const onThumbClick = useCallback(
(index: number) => {
if (!emblaApi || !emblaThumbsApi) return;
emblaApi.scrollTo(index);
},
[emblaApi, emblaThumbsApi],
);
const onSelect = useCallback(() => {
if (!emblaApi || !emblaThumbsApi) return;
setSelectedIndex(emblaApi.selectedScrollSnap());
emblaThumbsApi.scrollTo(emblaApi.selectedScrollSnap());
resetProgress();
const autoplay = emblaApi.plugins()?.autoplay;
if (autoplay) {
autoplay.reset();
}
}, [emblaApi, emblaThumbsApi, setSelectedIndex]);
const resetProgress = useCallback(() => {
setProgress(0);
}, []);
useEffect(() => {
const setVideoDurations = async () => {
const durations = await Promise.all(
videoRefs.current.map(
async (video) =>
new Promise<number>((resolve) => {
if (video) {
video.onloadedmetadata = () => resolve(video.duration * 1000);
} else {
resolve(5000);
}
}),
),
);
setAutoplayDelay(durations);
};
void setVideoDurations();
}, [slides, mounted, resolvedTheme]);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const video = entry.target as HTMLVideoElement;
video
.play()
.catch((error) => console.log('Error attempting to play the video:', error));
} else {
const video = entry.target as HTMLVideoElement;
video.pause();
}
});
},
{
threshold: 0.5,
},
);
videoRefs.current.forEach((video) => {
if (video) {
observer.observe(video);
}
});
return () => {
observer.disconnect();
};
}, [slides, mounted, resolvedTheme]);
useEffect(() => {
if (!emblaApi) return;
onSelect();
emblaApi.on('select', onSelect).on('reInit', onSelect);
}, [emblaApi, onSelect, mounted, resolvedTheme]);
useEffect(() => {
const autoplay = emblaApi?.plugins()?.autoplay;
if (!autoplay) return;
setIsPlaying(autoplay.isPlaying());
emblaApi
.on('autoplay:play', () => setIsPlaying(true))
.on('autoplay:stop', () => setIsPlaying(false))
.on('reInit', () => setIsPlaying(autoplay.isPlaying()));
}, [emblaApi, mounted, resolvedTheme]);
useEffect(() => {
if (autoplayDelay[selectedIndex] === undefined) return;
const updateInterval = 50;
const increment = 100 / (autoplayDelay[selectedIndex] / updateInterval);
let progressValue = 0;
const timer = setInterval(() => {
setProgress((prevProgress) => {
progressValue = prevProgress + increment;
if (progressValue >= 100) {
clearInterval(timer);
if (emblaApi) {
emblaApi.scrollNext();
}
return 100;
}
return progressValue;
});
}, updateInterval);
return () => clearInterval(timer);
}, [selectedIndex, autoplayDelay, emblaApi, mounted, resolvedTheme]);
useEffect(() => {
if (!emblaApi) return;
const resetCarousel = () => {
emblaApi.reInit();
emblaApi.scrollTo(0);
};
resetCarousel();
}, [emblaApi, autoplayDelay, mounted, resolvedTheme]);
// Ensure the component renders only after mounting to avoid theme issues
if (!mounted) return null;
return (
<>
<Card className="mx-auto mt-12 w-full max-w-4xl rounded-2xl p-1 before:rounded-2xl" gradient>
<div className="overflow-hidden rounded-xl" ref={emblaRef}>
<div className="flex touch-pan-y rounded-xl">
{slides.map((slide, index) => (
<div className="min-w-[10rem] flex-none basis-full rounded-xl" key={index}>
{slide.type === 'video' && (
<video
key={`${resolvedTheme}-${index}`}
ref={(el) => (videoRefs.current[index] = el)}
muted
loop
className="h-auto w-full rounded-xl"
>
<source
src={resolvedTheme === 'dark' ? slide.srcDark : slide.srcLight}
type="video/webm"
/>
Your browser does not support the video tag.
</video>
)}
</div>
))}
</div>
</div>
<div className="dark:bg-background absolute bottom-2 right-2 flex w-[20%] flex-col items-center space-y-1 rounded-lg bg-white p-1.5 sm:w-[5%]">
<span className="text-foreground dark:text-muted-foreground text-[10px] sm:text-xs">
{selectedIndex + 1}/{slides.length}
</span>
<Progress value={progress} className="h-1" />
</div>
</Card>
<div className="mx-auto mt-6 w-full max-w-4xl px-2 sm:mt-12">
<div className="mt-2 flex flex-wrap justify-between gap-6" ref={emblaThumbsRef}>
{slides.map((slide, index) => (
<Slide
key={index}
onClick={() => onThumbClick(index)}
selected={index === selectedIndex}
index={index}
label={slide.label}
/>
))}
</div>
</div>
</>
);
};
@@ -1,36 +0,0 @@
'use client';
import Link from 'next/link';
import { usePlausible } from 'next-plausible';
import { Button } from '@documenso/ui/primitives/button';
export const Enterprise = () => {
const event = usePlausible();
return (
<div className="mx-auto mt-36 max-w-2xl">
<h2 className="text-center text-2xl font-semibold">
Enterprise Compliance, License or Technical Needs?
</h2>
<p className="text-muted-foreground mt-4 text-center leading-relaxed">
Our Enterprise License is great large organizations looking to switch to Documenso for all
their signing needs. It's availible for our cloud offering as well as self-hosted setups and
offer a wide range of compliance and Adminstration Features.
</p>
<div className="mt-4 flex justify-center">
<Link
href="https://dub.sh/enterprise"
target="_blank"
className="mt-6"
onClick={() => event('enterprise-contact')}
>
<Button className="rounded-full text-base">Contact Us</Button>
</Link>
</div>
</div>
);
};
@@ -35,7 +35,6 @@ const FOOTER_LINKS = [
{ href: '/oss-friends', text: 'OSS Friends' },
{ href: '/careers', text: 'Careers' },
{ href: '/privacy', text: 'Privacy' },
{ href: '/changelog', text: 'Changelog' },
];
export const Footer = ({ className, ...props }: FooterProps) => {
@@ -14,7 +14,7 @@ import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-fl
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Carousel } from './carousel';
import { Widget } from './widget';
export type HeroProps = {
className?: string;
@@ -50,21 +50,6 @@ const HeroTitleVariants: Variants = {
},
};
const HeroCarouselVariants: Variants = {
initial: {
opacity: 0,
y: 60,
},
animate: {
opacity: 1,
y: 0,
transition: {
delay: 0.5,
duration: 0.8,
},
},
};
export const Hero = ({ className, ...props }: HeroProps) => {
const event = usePlausible();
@@ -72,6 +57,23 @@ export const Hero = ({ className, ...props }: HeroProps) => {
const heroMarketingCTA = getFlag('marketing_landing_hero_cta');
const onSignUpClick = () => {
const el = document.getElementById('email');
if (el) {
const { top } = el.getBoundingClientRect();
window.scrollTo({
top: top - 120,
behavior: 'smooth',
});
requestAnimationFrame(() => {
el.focus();
});
}
};
return (
<motion.div className={cn('relative', className)} {...props}>
<div className="absolute -inset-24 -z-10">
@@ -106,18 +108,18 @@ export const Hero = ({ className, ...props }: HeroProps) => {
animate="animate"
className="mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-4"
>
<Link href="https://app.documenso.com/signup?utm_source=marketing-hero">
<Button
type="button"
variant="outline"
className="rounded-full bg-transparent backdrop-blur-sm"
>
Try our Free Plan
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
No Credit Card required
</span>
</Button>
</Link>
<Button
type="button"
variant="outline"
className="rounded-full bg-transparent backdrop-blur-sm"
onClick={onSignUpClick}
>
Claim Early Adopter Plan
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
$30/mo
</span>
</Button>
<Link href="https://github.com/documenso/documenso" onClick={() => event('view-github')}>
<Button variant="outline" className="rounded-full bg-transparent backdrop-blur-sm">
<LuGithub className="mr-2 h-5 w-5" />
@@ -168,11 +170,74 @@ export const Hero = ({ className, ...props }: HeroProps) => {
<motion.div
className="mt-12"
variants={HeroCarouselVariants}
variants={{
initial: {
scale: 0.2,
opacity: 0,
},
animate: {
scale: 1,
opacity: 1,
transition: {
ease: 'easeInOut',
delay: 0.5,
duration: 0.8,
},
},
}}
initial="initial"
animate="animate"
>
<Carousel />
<Widget className="mt-12">
<strong>Documenso Supporter Pledge</strong>
<p className="w-full max-w-[70ch]">
Our mission is to create an open signing infrastructure that empowers the world,
enabling businesses to embrace openness, cooperation, and transparency. We believe
that signing, as a fundamental act, should embody these values. By offering an
open-source signing solution, we aim to make document signing accessible, transparent,
and trustworthy.
</p>
<p className="w-full max-w-[70ch]">
Through our platform, called Documenso, we strive to earn your trust by allowing
self-hosting and providing complete visibility into its inner workings. We value
inclusivity and foster an environment where diverse perspectives and contributions are
welcomed, even though we may not implement them all.
</p>
<p className="w-full max-w-[70ch]">
At Documenso, we envision a web-enabled future for business and contracts, and we are
committed to being the leading provider of open signing infrastructure. By combining
exceptional product design with open-source principles, we aim to deliver a robust and
well-designed application that exceeds your expectations.
</p>
<p className="w-full max-w-[70ch]">
We understand that exceptional products are born from exceptional communities, and we
invite you to join our open-source community. Your contributions, whether technical or
non-technical, will help shape the future of signing. Together, we can create a better
future for everyone.
</p>
<p className="w-full max-w-[70ch]">
Today we invite you to join us on this journey: By signing this mission statement you
signal your support of Documenso's mission{' '}
<span className="bg-primary text-black">
(in a non-legally binding, but heartfelt way)
</span>{' '}
and lock in the early adopter plan for forever, including everything we build this
year.
</p>
<div className="flex h-24 items-center">
<p className={cn('text-5xl [font-family:var(--font-caveat)]')}>Timur & Lucas</p>
</div>
<div>
<strong>Timur Ercan & Lucas Smith</strong>
<p className="mt-1">Co-Founders, Documenso</p>
</div>
</Widget>
</motion.div>
</div>
</motion.div>
@@ -58,7 +58,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
>
Yearly
<div className="bg-muted text-foreground block rounded-full px-2 py-0.5 text-xs">
Save $60 or $120
Save $60
</div>
{period === 'YEARLY' && (
<motion.div
@@ -75,7 +75,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
data-plan="free"
className="bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border px-8 py-12 shadow-lg"
>
<p className="text-foreground text-4xl font-medium">Free</p>
<p className="text-foreground text-4xl font-medium">Free Plan</p>
<p className="text-primary mt-2.5 text-xl font-medium">$0</p>
<p className="text-foreground mt-4 max-w-[30ch] text-center">
@@ -102,10 +102,10 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
</div>
<div
data-plan="individual"
className="bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border-2 px-8 py-12 shadow-[0px_0px_0px_4px_#E3E3E380]"
data-plan="early-adopter"
className="border-primary bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border-2 px-8 py-12 shadow-[0px_0px_0px_4px_#E3E3E380]"
>
<p className="text-foreground text-4xl font-medium">Individual</p>
<p className="text-foreground text-4xl font-medium">Early Adopters</p>
<div className="text-primary mt-2.5 text-xl font-medium">
<AnimatePresence mode="wait">
{period === 'MONTHLY' && <motion.div layoutId="pricing">$30</motion.div>}
@@ -114,12 +114,12 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
</div>
<p className="text-foreground mt-4 max-w-[30ch] text-center">
Everything you need for a great signing experience.
For fast-growing companies that aim to scale across multiple teams.
</p>
<Button className="mt-6 rounded-full text-base" asChild>
<Link
href={`${NEXT_PUBLIC_WEBAPP_URL()}/signup?utm_source=pricing-individual-plan`}
href={`${NEXT_PUBLIC_WEBAPP_URL()}/signup?utm_source=pricing-early-adopter`}
target="_blank"
>
Signup Now
@@ -127,48 +127,51 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
</Button>
<div className="mt-8 flex w-full flex-col divide-y">
<p className="text-foreground py-4">Unlimited Documents per Month</p>
<p className="text-foreground py-4">API Accesss</p>
<p className="text-foreground py-4">Email and Discord Support</p>
<p className="text-foreground py-4">Premium Profile Name</p>
<p className="text-foreground py-4">
<a
href="https://documen.so/early-adopters-pricing-page"
target="_blank"
rel="noreferrer"
>
Limited Time Offer: <span className="text-documenso-700">Read More</span>
</a>
</p>
<p className="text-foreground py-4">Unlimited Teams</p>
<p className="text-foreground py-4">Unlimited Users</p>
<p className="text-foreground py-4">Unlimited Documents per month</p>
<p className="text-foreground py-4">Includes all upcoming features</p>
<p className="text-foreground py-4">Email, Discord and Slack assistance</p>
</div>
<div className="flex-1" />
</div>
<div
data-plan="teams"
className="border-primary bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border px-8 py-12 shadow-lg"
data-plan="enterprise"
className="bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border px-8 py-12 shadow-lg"
>
<p className="text-foreground text-4xl font-medium">Teams</p>
<div className="text-primary mt-2.5 text-xl font-medium">
<AnimatePresence mode="wait">
{period === 'MONTHLY' && <motion.div layoutId="pricingTeams">$50</motion.div>}
{period === 'YEARLY' && <motion.div layoutId="pricingTeams">$480</motion.div>}
</AnimatePresence>
</div>
<p className="text-foreground text-4xl font-medium">Enterprise</p>
<p className="text-primary mt-2.5 text-xl font-medium">Pricing on request</p>
<p className="text-foreground mt-4 max-w-[30ch] text-center">
For companies looking to scale across multiple teams.
For large organizations that need extra flexibility and control.
</p>
<Button className="mt-6 rounded-full text-base" asChild>
<Link
href={`${NEXT_PUBLIC_WEBAPP_URL()}/signup?utm_source=pricing-teams-plan`}
target="_blank"
>
Signup Now
</Link>
</Button>
<Link
href="https://dub.sh/enterprise"
target="_blank"
className="mt-6"
onClick={() => event('enterprise-contact')}
>
<Button className="rounded-full text-base">Contact Us</Button>
</Link>
<div className="mt-8 flex w-full flex-col divide-y">
<p className="text-foreground py-4">Unlimited Documents per Month</p>
<p className="text-foreground py-4">API Accesss</p>
<p className="text-foreground py-4">Email and Discord Support</p>
<p className="text-foreground py-4 font-medium">Team Inbox</p>
<p className="text-foreground py-4">5 Users Included</p>
<p className="text-foreground py-4">
Add More Users for {period === 'MONTHLY' ? '$10/ mo.' : '$96/ yr.'}
</p>
<p className="text-foreground py-4 font-medium">Everything in Early Adopters, plus:</p>
<p className="text-foreground py-4">Custom Subdomain</p>
<p className="text-foreground py-4">Compliance Check</p>
<p className="text-foreground py-4">Guaranteed Uptime</p>
<p className="text-foreground py-4">Reporting & Analysis</p>
<p className="text-foreground py-4">24/7 Support</p>
</div>
</div>
</div>
@@ -70,7 +70,7 @@ export const ShareConnectPaidWidgetBento = ({
<CardContent className="grid grid-cols-1 gap-8 p-6">
<p className="text-foreground/80 leading-relaxed">
<strong className="block">Get paid (Soon).</strong>
Integrated payments with Stripe so you dont have to worry about getting paid.
Integrated payments with stripe so you dont have to worry about getting paid.
</p>
<div className="flex items-center justify-center p-8">
@@ -1,29 +0,0 @@
import React from 'react';
import { cn } from '@documenso/ui/lib/utils';
type SlideProps = {
selected: boolean;
index: number;
onClick: () => void;
label: string;
};
export const Slide: React.FC<SlideProps> = (props) => {
const { selected, label, onClick } = props;
return (
<button
onClick={onClick}
type="button"
className={cn(
'text-muted-foreground dark:text-muted-foreground/60 border-b-2 border-transparent py-1 text-xs sm:py-4 sm:text-base',
{
'border-primary text-foreground dark:text-muted-foreground border-b-2': selected,
},
)}
>
{label}
</button>
);
};
@@ -0,0 +1,421 @@
'use client';
import type { HTMLAttributes, KeyboardEvent } from 'react';
import { useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { AnimatePresence, motion } from 'framer-motion';
import { Loader } from 'lucide-react';
import { usePlausible } from 'next-plausible';
import { env } from 'next-runtime-env';
import { Controller, useForm } from 'react-hook-form';
import { z } from 'zod';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { claimPlan } from '~/api/claim-plan/fetcher';
import { STEP } from '../constants';
import { FormErrorMessage } from '../form/form-error-message';
const ZWidgetFormSchema = z
.object({
email: z.string().email({ message: 'Please enter a valid email address.' }),
name: z.string().trim().min(3, { message: 'Please enter a valid name.' }),
})
.and(
z.union([
z.object({
signatureDataUrl: z.string().min(1),
signatureText: z.null().or(z.string().max(0)),
}),
z.object({
signatureDataUrl: z.null().or(z.string().max(0)),
signatureText: z.string().trim().min(1),
}),
]),
);
export type TWidgetFormSchema = z.infer<typeof ZWidgetFormSchema>;
type StepKeys = keyof typeof STEP;
type StepValues = (typeof STEP)[StepKeys];
export type WidgetProps = HTMLAttributes<HTMLDivElement>;
export const Widget = ({ className, children, ...props }: WidgetProps) => {
const { toast } = useToast();
const event = usePlausible();
const [step, setStep] = useState<StepValues>(STEP.EMAIL);
const [showSigningDialog, setShowSigningDialog] = useState(false);
const [draftSignatureDataUrl, setDraftSignatureDataUrl] = useState<string | null>(null);
const {
control,
register,
handleSubmit,
setValue,
trigger,
watch,
formState: { errors, isSubmitting, isValid },
} = useForm<TWidgetFormSchema>({
mode: 'onChange',
defaultValues: {
email: '',
name: '',
signatureDataUrl: null,
signatureText: '',
},
resolver: zodResolver(ZWidgetFormSchema),
});
const signatureDataUrl = watch('signatureDataUrl');
const signatureText = watch('signatureText');
const stepsRemaining = useMemo(() => {
if (step === STEP.NAME) {
return 2;
}
if (step === STEP.EMAIL) {
return 3;
}
return 1;
}, [step]);
const onNextStepClick = () => {
if (step === STEP.EMAIL) {
setStep(STEP.NAME);
setTimeout(() => {
document.querySelector<HTMLElement>('#name')?.focus();
}, 0);
}
if (step === STEP.NAME) {
setStep(STEP.SIGN);
setTimeout(() => {
document.querySelector<HTMLElement>('#signatureText')?.focus();
}, 0);
}
};
const onEnterPress = (callback: () => void) => {
return (e: KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
callback();
}
};
};
const onSignatureConfirmClick = () => {
setValue('signatureDataUrl', draftSignatureDataUrl);
setValue('signatureText', '');
void trigger('signatureDataUrl');
setShowSigningDialog(false);
};
const onFormSubmit = async ({
email,
name,
signatureDataUrl,
signatureText,
}: TWidgetFormSchema) => {
try {
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
const planId = env('NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID');
if (!planId) {
throw new Error('No plan ID found.');
}
const claimPlanInput = signatureDataUrl
? {
name,
email,
planId,
signatureDataUrl: signatureDataUrl,
signatureText: null,
}
: {
name,
email,
planId,
signatureDataUrl: null,
signatureText: signatureText ?? '',
};
const [result] = await Promise.all([claimPlan(claimPlanInput), delay]);
event('claim-plan-widget');
window.location.href = result;
} catch (error) {
event('claim-plan-failed');
toast({
title: 'Something went wrong',
description: error instanceof Error ? error.message : 'Please try again later.',
variant: 'destructive',
});
}
};
return (
<>
<Card
className={cn('mx-auto w-full max-w-4xl rounded-3xl before:rounded-3xl', className)}
gradient
{...props}
>
<div className="grid grid-cols-12 gap-y-8 overflow-hidden p-2 lg:gap-x-8">
<div className="text-muted-foreground col-span-12 flex flex-col gap-y-4 p-4 text-xs leading-relaxed lg:col-span-7">
{children}
</div>
<form
className="bg-foreground/5 col-span-12 flex flex-col rounded-2xl p-6 lg:col-span-5"
onSubmit={handleSubmit(onFormSubmit)}
>
<h3 className="text-xl font-semibold">Sign up to Early Adopter Plan</h3>
<p className="text-muted-foreground mt-2 text-xs">
with Timur Ercan & Lucas Smith from Documenso
</p>
<hr className="mb-6 mt-4" />
<AnimatePresence>
<motion.div key="email">
<label htmlFor="email" className="text-foreground font-medium ">
Whats your email?
</label>
<Controller
control={control}
name="email"
render={({ field }) => (
<div className="relative mt-2">
<Input
id="email"
type="email"
placeholder="your@example.com"
className="bg-background w-full pr-16"
disabled={isSubmitting}
onKeyDown={(e) =>
field.value !== '' &&
!errors.email?.message &&
onEnterPress(onNextStepClick)(e)
}
{...field}
/>
<div className="absolute inset-y-0 right-0 p-1.5">
<Button
type="button"
className="bg-primary h-full w-14 rounded"
disabled={!field.value || !!errors.email?.message}
onClick={() => step === STEP.EMAIL && onNextStepClick()}
>
Next
</Button>
</div>
</div>
)}
/>
<FormErrorMessage error={errors.email} className="mt-1" />
</motion.div>
{(step === STEP.NAME || step === STEP.SIGN) && (
<motion.div
key="name"
className="mt-4"
animate={{
opacity: 1,
transform: 'translateX(0)',
}}
initial={{
opacity: 0,
transform: 'translateX(-25%)',
}}
exit={{
opacity: 0,
transform: 'translateX(25%)',
}}
>
<label htmlFor="name" className="text-foreground font-medium ">
And your name?
</label>
<Controller
control={control}
name="name"
render={({ field }) => (
<div className="relative mt-2">
<Input
id="name"
type="text"
placeholder=""
className="bg-background w-full pr-16"
disabled={isSubmitting}
onKeyDown={(e) =>
field.value !== '' &&
!errors.name?.message &&
onEnterPress(onNextStepClick)(e)
}
{...field}
/>
<div className="absolute inset-y-0 right-0 p-1.5">
<Button
type="button"
className="bg-primary h-full w-14 rounded"
disabled={!field.value || !!errors.name?.message}
onClick={() => onNextStepClick()}
>
Next
</Button>
</div>
</div>
)}
/>
<FormErrorMessage error={errors.name} className="mt-1" />
</motion.div>
)}
</AnimatePresence>
<div className="mt-12 flex-1" />
<div className="flex items-center justify-between">
<p className="text-muted-foreground text-xs">
{isValid ? 'Ready for Signing' : `${stepsRemaining} step(s) until signed`}
</p>
<p className="text-muted-foreground block text-xs md:hidden">Minimise contract</p>
</div>
<div className="bg-background relative mt-2.5 h-[2px] w-full">
<div
className={cn('bg-primary/60 absolute inset-y-0 left-0 duration-200', {
'w-1/3': stepsRemaining === 3,
'w-2/3': stepsRemaining === 2,
'w-11/12': stepsRemaining === 1,
'w-full': isValid,
})}
/>
</div>
<Card id="signature" className="mt-4" degrees={-140} gradient>
<CardContent
role="button"
className="relative cursor-pointer pt-6"
onClick={() => setShowSigningDialog(true)}
>
<div className="flex h-28 items-center justify-center pb-6">
{!signatureText && signatureDataUrl && (
<img
src={signatureDataUrl}
alt="user signature"
className="h-full dark:invert"
/>
)}
{signatureText && (
<p
className={cn(
'text-foreground truncate text-4xl font-semibold [font-family:var(--font-caveat)]',
)}
>
{signatureText}
</p>
)}
</div>
<div
className="absolute inset-x-0 bottom-0 flex cursor-auto items-center justify-between px-4 pb-2"
onClick={(e) => e.stopPropagation()}
>
<Input
id="signatureText"
className="text-foreground placeholder:text-muted-foreground truncate border-none p-0 text-sm focus-visible:ring-0"
placeholder="Draw or type name here"
disabled={isSubmitting}
{...register('signatureText', {
onChange: (e) => {
if (e.target.value !== '') {
setValue('signatureDataUrl', null);
}
},
})}
/>
<Button
type="submit"
className="disabled:bg-muted disabled:text-muted-foreground disabled:hover:bg-muted h-8"
disabled={!isValid || isSubmitting}
>
{isSubmitting && <Loader className="mr-2 h-4 w-4 animate-spin" />}
Sign
</Button>
</div>
</CardContent>
</Card>
</form>
</div>
</Card>
<Dialog open={showSigningDialog} onOpenChange={setShowSigningDialog}>
<DialogContent position="center">
<DialogHeader>
<DialogTitle>Add your signature</DialogTitle>
</DialogHeader>
<DialogDescription>
By signing you signal your support of Documenso's mission in a <br></br>
<strong>non-legally binding, but heartfelt way</strong>. <br></br>
<br></br>You also unlock the option to purchase the early supporter plan including
everything we build this year for fixed price.
</DialogDescription>
<SignaturePad
disabled={isSubmitting}
className="aspect-video w-full rounded-md border"
defaultValue={signatureDataUrl || ''}
onChange={setDraftSignatureDataUrl}
/>
<DialogFooter>
<Button variant="ghost" onClick={() => setShowSigningDialog(false)}>
Cancel
</Button>
<Button onClick={() => onSignatureConfirmClick()}>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
};
@@ -13,7 +13,6 @@ import { updateFile } from '@documenso/lib/universal/upload/update-file';
import { prisma } from '@documenso/prisma';
import {
DocumentDataType,
DocumentSource,
DocumentStatus,
FieldType,
ReadStatus,
@@ -105,7 +104,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const document = await prisma.document.create({
data: {
source: DocumentSource.DOCUMENT,
title: 'Documenso Supporter Pledge.pdf',
status: DocumentStatus.COMPLETED,
userId: user.id,
-5
View File
@@ -18,10 +18,6 @@ const FONT_CAVEAT_BYTES = fs.readFileSync(
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
);
const FONT_NOTO_SANS_BYTES = fs.readFileSync(
path.join(__dirname, '../../packages/assets/fonts/noto-sans.ttf'),
);
/** @type {import('next').NextConfig} */
const config = {
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
@@ -46,7 +42,6 @@ const config = {
APP_VERSION: version,
NEXT_PUBLIC_PROJECT: 'web',
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
FONT_NOTO_SANS_URI: `data:font/ttf;base64,${FONT_NOTO_SANS_BYTES.toString('base64')}`,
},
modularizeImports: {
'lucide-react': {
+2 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/web",
"version": "1.6.0-rc.2",
"version": "1.2.3",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@@ -28,7 +28,6 @@
"cookie-es": "^1.0.0",
"formidable": "^2.1.1",
"framer-motion": "^10.12.8",
"input-otp": "^1.2.4",
"lucide-react": "^0.279.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
@@ -48,9 +47,8 @@
"react-hotkeys-hook": "^4.4.1",
"react-icons": "^4.11.0",
"react-rnd": "^10.4.1",
"recharts": "^2.7.2",
"remeda": "^1.27.1",
"sharp": "0.32.6",
"sharp": "^0.33.1",
"ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37",
"uqr": "^0.1.2",
-4
View File
@@ -12,9 +12,5 @@ declare namespace NodeJS {
NEXT_PRIVATE_GOOGLE_CLIENT_ID: string;
NEXT_PRIVATE_GOOGLE_CLIENT_SECRET: string;
NEXT_PRIVATE_OIDC_WELL_KNOWN: string;
NEXT_PRIVATE_OIDC_CLIENT_ID: string;
NEXT_PRIVATE_OIDC_CLIENT_SECRET: string;
}
}
+56590 -1
View File
File diff suppressed because one or more lines are too long
@@ -1,33 +0,0 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1080_12656)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.56772 0.890928C9.5882 -0.296974 11.4118 -0.296978 12.4323 0.890927L13.2272 1.81624C13.3589 1.96964 13.5596 2.0435 13.758 2.01166L14.955 1.81961C16.4917 1.57307 17.8887 2.75864 17.9154 4.33206L17.9363 5.55768C17.9398 5.76086 18.0465 5.94788 18.2188 6.0525L19.2578 6.68358C20.5916 7.49375 20.9083 9.31015 19.9288 10.5329L19.1659 11.4853C19.0394 11.6432 19.0023 11.8559 19.0678 12.048L19.4627 13.2069C19.9696 14.6947 19.0578 16.292 17.5304 16.5919L16.3406 16.8255C16.1434 16.8643 15.9798 17.0031 15.9079 17.1928L15.4738 18.3373C14.9166 19.8066 13.203 20.4374 11.8423 19.6741L10.7825 19.0796C10.6068 18.981 10.3932 18.981 10.2175 19.0796L9.15768 19.6741C7.79704 20.4374 6.08341 19.8066 5.52618 18.3373L5.09212 17.1928C5.02017 17.0031 4.8566 16.8643 4.65937 16.8255L3.46962 16.5919C1.94224 16.292 1.03044 14.6947 1.53734 13.2069L1.93219 12.048C1.99765 11.8559 1.96057 11.6432 1.8341 11.4853L1.07116 10.5329C0.0917119 9.31015 0.408373 7.49375 1.74223 6.68358L2.78123 6.0525C2.95348 5.94788 3.06024 5.76086 3.0637 5.55768L3.08456 4.33206C3.11133 2.75864 4.50829 1.57307 6.04498 1.81961L7.24197 2.01166C7.4404 2.0435 7.64105 1.96964 7.77282 1.81624L8.56772 0.890928Z" fill="url(#paint0_linear_1080_12656)"/>
<g filter="url(#filter0_di_1080_12656)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3714 14.5609C13.5195 14.6358 13.6925 14.5149 13.6642 14.3563L13.1163 11.2805L15.4388 9.10299C15.5586 8.9907 15.4925 8.79506 15.327 8.77192L12.1176 8.32508L10.681 5.52519C10.6069 5.38093 10.3931 5.38093 10.319 5.52519L8.88116 8.32354L5.673 8.77192C5.50748 8.79506 5.44139 8.9907 5.56116 9.10299L7.8843 11.2803L7.33579 14.3563C7.30752 14.5149 7.48055 14.6358 7.62859 14.5609L10.5014 13.1083L13.3714 14.5609Z" fill="#FFFCEB"/>
</g>
</g>
<defs>
<filter id="filter0_di_1080_12656" x="5.33521" y="5.41699" width="10.6591" height="9.90853" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.164785" dy="0.411963"/>
<feGaussianBlur stdDeviation="0.164785"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.414307 0 0 0 0 0.24341 0 0 0 0 0.0856598 0 0 0 0.1 0"/>
<feBlend mode="plus-darker" in2="BackgroundImageFix" result="effect1_dropShadow_1080_12656"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1080_12656" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.164785" dy="0.164785"/>
<feGaussianBlur stdDeviation="0.0823927"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend mode="screen" in2="shape" result="effect2_innerShadow_1080_12656"/>
</filter>
<linearGradient id="paint0_linear_1080_12656" x1="12.5596" y1="-9.0568e-08" x2="6.25112" y2="19.9592" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE76A"/>
<stop offset="1" stop-color="#E8C445"/>
</linearGradient>
<clipPath id="clip0_1080_12656">
<rect width="20" height="20" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

@@ -1,9 +0,0 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.54474 0.890944C9.57689 -0.296979 11.4213 -0.296983 12.4535 0.890943L13.2575 1.81628C13.3908 1.96967 13.5937 2.04354 13.7944 2.0117L15.0051 1.81965C16.5593 1.57309 17.9723 2.75869 17.9994 4.33214L18.0205 5.55778C18.024 5.76096 18.1319 5.94799 18.3061 6.05261L19.357 6.6837C20.7061 7.49389 21.0264 9.31032 20.0358 10.5331L19.2641 11.4855C19.1362 11.6434 19.0987 11.8561 19.1649 12.0482L19.5643 13.2072C20.077 14.695 19.1547 16.2923 17.6099 16.5922L16.4065 16.8258C16.207 16.8646 16.0416 17.0034 15.9688 17.1931L15.5298 18.3376C14.9662 19.8069 13.233 20.4378 11.8568 19.6745L10.7848 19.08C10.6071 18.9814 10.3911 18.9814 10.2134 19.08L9.14145 19.6745C7.76525 20.4378 6.03203 19.8069 5.46842 18.3376L5.0294 17.1931C4.95662 17.0034 4.79119 16.8646 4.5917 16.8258L3.38834 16.5922C1.8435 16.2923 0.921268 14.695 1.43397 13.2072L1.83334 12.0482C1.89954 11.8561 1.86204 11.6434 1.73412 11.4855L0.962455 10.5331C-0.0281913 9.31032 0.292091 7.49389 1.6412 6.6837L2.69209 6.05261C2.8663 5.94799 2.97428 5.76096 2.97778 5.55778L2.99888 4.33214C3.02596 2.75869 4.4389 1.57309 5.99315 1.81965L7.20383 2.0117C7.40454 2.04354 7.60747 1.96967 7.74076 1.81628L8.54474 0.890944ZM13.7062 9.20711C14.0968 8.81658 14.0968 8.18342 13.7062 7.79289C13.3157 7.40237 12.6825 7.40237 12.292 7.79289L9.49912 10.5858L8.70622 9.79289C8.3157 9.40237 7.68253 9.40237 7.29201 9.79289C6.90148 10.1834 6.90148 10.8166 7.29201 11.2071L8.43846 12.3536C9.02425 12.9393 9.97399 12.9393 10.5598 12.3536L13.7062 9.20711Z" fill="url(#paint0_linear_1080_12647)"/>
<defs>
<linearGradient id="paint0_linear_1080_12647" x1="12.5823" y1="-9.05696e-08" x2="6.33214" y2="20.0004" gradientUnits="userSpaceOnUse">
<stop stop-color="#96D766"/>
<stop offset="1" stop-color="#5AAE30"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

Some files were not shown because too many files have changed in this diff Show More