Compare commits
22 Commits
v1.8.1-rc.
...
v1.9.0-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9fe134852 | ||
|
|
f2149719e3 | ||
|
|
161d40cde7 | ||
|
|
76028771b8 | ||
|
|
5df1a6602e | ||
|
|
3d7b28a92b | ||
|
|
ed862413b1 | ||
|
|
9d02ab4a5e | ||
|
|
34c0868d77 | ||
|
|
fae9c0ca24 | ||
|
|
dd162205fa | ||
|
|
a88ae1cc1e | ||
|
|
904948e2bc | ||
|
|
3b6b96f551 | ||
|
|
67e49c82a3 | ||
|
|
9f45fe62e4 | ||
|
|
9e8094e34c | ||
|
|
0e7e9e17c9 | ||
|
|
b3ccb3d26f | ||
|
|
b17370c153 | ||
|
|
0c53f5b061 | ||
|
|
ed6157de80 |
@@ -10,13 +10,7 @@
|
|||||||
"ghcr.io/devcontainers/features/node:1": {}
|
"ghcr.io/devcontainers/features/node:1": {}
|
||||||
},
|
},
|
||||||
"onCreateCommand": "./.devcontainer/on-create.sh",
|
"onCreateCommand": "./.devcontainer/on-create.sh",
|
||||||
"forwardPorts": [
|
"forwardPorts": [3000, 54320, 9000, 2500, 1100],
|
||||||
3000,
|
|
||||||
54320,
|
|
||||||
9000,
|
|
||||||
2500,
|
|
||||||
1100
|
|
||||||
],
|
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
> 🚨 We are live on Product Hunt 🎉 Check out our latest launch: <a href="documen.so/sign-everywhere">The Platform Plan</a>!
|
||||||
|
|
||||||
|
<a href="https://www.producthunt.com/posts/documenso-platform-plan?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-documenso-platform-plan" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=670576&theme=light" alt="Documenso Platform Plan - Whitelabeled signing flows in your product | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
<img src="https://github.com/documenso/documenso/assets/13398220/a643571f-0239-46a6-a73e-6bef38d1228b" alt="Documenso Logo">
|
<img src="https://github.com/documenso/documenso/assets/13398220/a643571f-0239-46a6-a73e-6bef38d1228b" alt="Documenso Logo">
|
||||||
|
|
||||||
<p align="center" style="margin-top: 20px">
|
<p align="center" style="margin-top: 20px">
|
||||||
|
|||||||
@@ -1,36 +1 @@
|
|||||||
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).
|
# @documenso/documentation
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
First, run the development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
# or
|
|
||||||
bun dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://localhost:3002](http://localhost:3002) 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.
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/marketing",
|
"name": "@documenso/marketing",
|
||||||
"version": "1.8.1-rc.7",
|
"version": "1.9.0-rc.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"recharts": "^2.7.2",
|
"recharts": "^2.7.2",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lingui/loader": "^4.11.3",
|
"@lingui/loader": "^4.11.3",
|
||||||
|
|||||||
40
apps/openpage-api/.gitignore
vendored
Normal file
40
apps/openpage-api/.gitignore
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for commiting if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
1
apps/openpage-api/README.md
Normal file
1
apps/openpage-api/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# @documenso/openpage-api
|
||||||
36
apps/openpage-api/app/community/route.ts
Normal file
36
apps/openpage-api/app/community/route.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
{ path: '/total-prs', description: 'Total GitHub Merged PRs' },
|
||||||
|
{ path: '/total-stars', description: 'Total GitHub Stars' },
|
||||||
|
{ path: '/total-forks', description: 'Total GitHub Forks' },
|
||||||
|
{ path: '/total-issues', description: 'Total GitHub Issues' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function GET(request: NextRequest) {
|
||||||
|
const url = request.nextUrl.toString();
|
||||||
|
const apis = paths.map(({ path, description }) => {
|
||||||
|
return { path: url + path, description };
|
||||||
|
});
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(apis), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
27
apps/openpage-api/app/community/total-forks/route.ts
Normal file
27
apps/openpage-api/app/community/total-forks/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { transformData } from '@/lib/transform-data';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
|
||||||
|
const data = await res.json();
|
||||||
|
const transformedData = transformData({ data, metric: 'forks' });
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(transformedData), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
27
apps/openpage-api/app/community/total-issues/route.ts
Normal file
27
apps/openpage-api/app/community/total-issues/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { transformData } from '@/lib/transform-data';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
|
||||||
|
const data = await res.json();
|
||||||
|
const transformedData = transformData({ data, metric: 'openIssues' });
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(transformedData), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
27
apps/openpage-api/app/community/total-prs/route.ts
Normal file
27
apps/openpage-api/app/community/total-prs/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { transformData } from '@/lib/transform-data';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
|
||||||
|
const data = await res.json();
|
||||||
|
const transformedData = transformData({ data, metric: 'mergedPRs' });
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(transformedData), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
27
apps/openpage-api/app/community/total-stars/route.ts
Normal file
27
apps/openpage-api/app/community/total-stars/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { transformData } from '@/lib/transform-data';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
|
||||||
|
const data = await res.json();
|
||||||
|
const transformedData = transformData({ data, metric: 'stars' });
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(transformedData), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
25
apps/openpage-api/app/github/forks/route.ts
Normal file
25
apps/openpage-api/app/github/forks/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch('https://api.github.com/repos/documenso/documenso');
|
||||||
|
const { forks_count } = await res.json();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify({ data: forks_count }), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
27
apps/openpage-api/app/github/issues/route.ts
Normal file
27
apps/openpage-api/app/github/issues/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch(
|
||||||
|
'https://api.github.com/search/issues?q=repo:documenso/documenso+type:issue+state:open&page=0&per_page=1',
|
||||||
|
);
|
||||||
|
const { total_count } = await res.json();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify({ data: total_count }), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
27
apps/openpage-api/app/github/prs/route.ts
Normal file
27
apps/openpage-api/app/github/prs/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch(
|
||||||
|
'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1',
|
||||||
|
);
|
||||||
|
const { total_count } = await res.json();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify({ data: total_count }), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
36
apps/openpage-api/app/github/route.ts
Normal file
36
apps/openpage-api/app/github/route.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
{ path: '/forks', description: 'GitHub Forks' },
|
||||||
|
{ path: '/stars', description: 'GitHub Stars' },
|
||||||
|
{ path: '/issues', description: 'GitHub Merged Issues' },
|
||||||
|
{ path: '/prs', description: 'GitHub Pull Requests' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function GET(request: NextRequest) {
|
||||||
|
const url = request.nextUrl.toString();
|
||||||
|
const apis = paths.map(({ path, description }) => {
|
||||||
|
return { path: url + path, description };
|
||||||
|
});
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(apis), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
25
apps/openpage-api/app/github/stars/route.ts
Normal file
25
apps/openpage-api/app/github/stars/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch('https://api.github.com/repos/documenso/documenso');
|
||||||
|
const { stargazers_count } = await res.json();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify({ data: stargazers_count }), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
25
apps/openpage-api/app/growth/completed-documents/route.ts
Normal file
25
apps/openpage-api/app/growth/completed-documents/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getCompletedDocumentsMonthly } from '@/lib/growth/get-monthly-completed-document';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const completedDocuments = await getCompletedDocumentsMonthly();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(completedDocuments), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
25
apps/openpage-api/app/growth/new-users/route.ts
Normal file
25
apps/openpage-api/app/growth/new-users/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getUserMonthlyGrowth } from '@/lib/growth/get-user-monthly-growth';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const monthlyUsers = await getUserMonthlyGrowth();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(monthlyUsers), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
39
apps/openpage-api/app/growth/route.ts
Normal file
39
apps/openpage-api/app/growth/route.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
{ path: '/total-customers', description: 'Total Customers' },
|
||||||
|
{ path: '/total-users', description: 'Total Users' },
|
||||||
|
{ path: '/new-users', description: 'New Users' },
|
||||||
|
{ path: '/completed-documents', description: 'Completed Documents per Month' },
|
||||||
|
{ path: '/total-completed-documents', description: 'Total Completed Documents' },
|
||||||
|
{ path: '/signer-conversion', description: 'Signers That Signed Up' },
|
||||||
|
{ path: '/total-signer-conversion', description: 'Total Signers That Signed Up' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function GET(request: NextRequest) {
|
||||||
|
const url = request.nextUrl.toString();
|
||||||
|
const apis = paths.map(({ path, description }) => {
|
||||||
|
return { path: url + path, description };
|
||||||
|
});
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(apis), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
25
apps/openpage-api/app/growth/signer-conversion/route.ts
Normal file
25
apps/openpage-api/app/growth/signer-conversion/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getSignerConversionMonthly } from '@/lib/growth/get-signer-conversion';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const signers = await getSignerConversionMonthly();
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(signers), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getCompletedDocumentsMonthly } from '@/lib/growth/get-monthly-completed-document';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const totalCompletedDocuments = await getCompletedDocumentsMonthly('cumulative');
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(totalCompletedDocuments), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
31
apps/openpage-api/app/growth/total-customers/route.ts
Normal file
31
apps/openpage-api/app/growth/total-customers/route.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { transformData } from '@/lib/transform-data';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const res = await fetch('https://stargrazer-live.onrender.com/api/stats/stripe');
|
||||||
|
const EARLY_ADOPTERS_DATA = await res.json();
|
||||||
|
|
||||||
|
const transformedData = transformData({
|
||||||
|
data: EARLY_ADOPTERS_DATA,
|
||||||
|
metric: 'earlyAdopters',
|
||||||
|
});
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(transformedData), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getSignerConversionMonthly } from '@/lib/growth/get-signer-conversion';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const totalSigners = await getSignerConversionMonthly('cumulative');
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(totalSigners), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
25
apps/openpage-api/app/growth/total-users/route.ts
Normal file
25
apps/openpage-api/app/growth/total-users/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import cors from '@/lib/cors';
|
||||||
|
import { getUserMonthlyGrowth } from '@/lib/growth/get-user-monthly-growth';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const totalUsers = await getUserMonthlyGrowth("cumulative");
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(totalUsers), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
35
apps/openpage-api/app/route.ts
Normal file
35
apps/openpage-api/app/route.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
{ path: 'github', description: 'GitHub Data' },
|
||||||
|
{ path: 'community', description: 'Community Data' },
|
||||||
|
{ path: 'growth', description: 'Growth Data' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function GET(request: NextRequest) {
|
||||||
|
const url = request.nextUrl.toString();
|
||||||
|
const apis = paths.map(({ path, description }) => {
|
||||||
|
return { path: url + path, description };
|
||||||
|
});
|
||||||
|
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(apis), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
135
apps/openpage-api/lib/cors.ts
Normal file
135
apps/openpage-api/lib/cors.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* Multi purpose CORS lib.
|
||||||
|
* Note: Based on the `cors` package in npm but using only web APIs.
|
||||||
|
* Taken from: https://github.com/vercel/examples/blob/main/edge-functions/cors/lib/cors.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[];
|
||||||
|
|
||||||
|
type OriginFn = (origin: string | undefined, req: Request) => StaticOrigin | Promise<StaticOrigin>;
|
||||||
|
|
||||||
|
interface CorsOptions {
|
||||||
|
origin?: StaticOrigin | OriginFn;
|
||||||
|
methods?: string | string[];
|
||||||
|
allowedHeaders?: string | string[];
|
||||||
|
exposedHeaders?: string | string[];
|
||||||
|
credentials?: boolean;
|
||||||
|
maxAge?: number;
|
||||||
|
preflightContinue?: boolean;
|
||||||
|
optionsSuccessStatus?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: CorsOptions = {
|
||||||
|
origin: '*',
|
||||||
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
|
preflightContinue: false,
|
||||||
|
optionsSuccessStatus: 204,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean {
|
||||||
|
return Array.isArray(allowed)
|
||||||
|
? allowed.some((o) => isOriginAllowed(origin, o))
|
||||||
|
: typeof allowed === 'string'
|
||||||
|
? origin === allowed
|
||||||
|
: allowed instanceof RegExp
|
||||||
|
? allowed.test(origin)
|
||||||
|
: !!allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) {
|
||||||
|
const headers = new Headers();
|
||||||
|
|
||||||
|
if (origin === '*') {
|
||||||
|
headers.set('Access-Control-Allow-Origin', '*');
|
||||||
|
} else if (typeof origin === 'string') {
|
||||||
|
headers.set('Access-Control-Allow-Origin', origin);
|
||||||
|
headers.append('Vary', 'Origin');
|
||||||
|
} else {
|
||||||
|
const allowed = isOriginAllowed(reqOrigin ?? '', origin);
|
||||||
|
|
||||||
|
if (allowed && reqOrigin) {
|
||||||
|
headers.set('Access-Control-Allow-Origin', reqOrigin);
|
||||||
|
}
|
||||||
|
headers.append('Vary', 'Origin');
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function originHeadersFromReq(req: Request, origin: StaticOrigin | OriginFn) {
|
||||||
|
const reqOrigin = req.headers.get('Origin') || undefined;
|
||||||
|
const value = typeof origin === 'function' ? await origin(reqOrigin, req) : origin;
|
||||||
|
|
||||||
|
if (!value) return;
|
||||||
|
return getOriginHeaders(reqOrigin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllowedHeaders(req: Request, allowed?: string | string[]) {
|
||||||
|
const headers = new Headers();
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
allowed = req.headers.get('Access-Control-Request-Headers')!;
|
||||||
|
headers.append('Vary', 'Access-Control-Request-Headers');
|
||||||
|
} else if (Array.isArray(allowed)) {
|
||||||
|
allowed = allowed.join(',');
|
||||||
|
}
|
||||||
|
if (allowed) {
|
||||||
|
headers.set('Access-Control-Allow-Headers', allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function cors(req: Request, res: Response, options?: CorsOptions) {
|
||||||
|
const opts = { ...defaultOptions, ...options };
|
||||||
|
const { headers } = res;
|
||||||
|
const originHeaders = await originHeadersFromReq(req, opts.origin ?? false);
|
||||||
|
const mergeHeaders = (v: string, k: string) => {
|
||||||
|
if (k === 'Vary') headers.append(k, v);
|
||||||
|
else headers.set(k, v);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If there's no origin we won't touch the response
|
||||||
|
if (!originHeaders) return res;
|
||||||
|
|
||||||
|
originHeaders.forEach(mergeHeaders);
|
||||||
|
|
||||||
|
if (opts.credentials) {
|
||||||
|
headers.set('Access-Control-Allow-Credentials', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
const exposed = Array.isArray(opts.exposedHeaders)
|
||||||
|
? opts.exposedHeaders.join(',')
|
||||||
|
: opts.exposedHeaders;
|
||||||
|
|
||||||
|
if (exposed) {
|
||||||
|
headers.set('Access-Control-Expose-Headers', exposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the preflight request
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
if (opts.methods) {
|
||||||
|
const methods = Array.isArray(opts.methods) ? opts.methods.join(',') : opts.methods;
|
||||||
|
|
||||||
|
headers.set('Access-Control-Allow-Methods', methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllowedHeaders(req, opts.allowedHeaders).forEach(mergeHeaders);
|
||||||
|
|
||||||
|
if (typeof opts.maxAge === 'number') {
|
||||||
|
headers.set('Access-Control-Max-Age', String(opts.maxAge));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.preflightContinue) return res;
|
||||||
|
|
||||||
|
headers.set('Content-Length', '0');
|
||||||
|
return new Response(null, { status: opts.optionsSuccessStatus, headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got here, it's a normal request
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initCors(options?: CorsOptions) {
|
||||||
|
return async (req: Request, res: Response) => cors(req, res, options);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||||
|
import { DocumentStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative' = 'count') => {
|
||||||
|
const qb = kyselyPrisma.$kysely
|
||||||
|
.selectFrom('Document')
|
||||||
|
.select(({ fn }) => [
|
||||||
|
fn<Date>('DATE_TRUNC', [sql.lit('MONTH'), 'Document.updatedAt']).as('month'),
|
||||||
|
fn.count('id').as('count'),
|
||||||
|
fn
|
||||||
|
.sum(fn.count('id'))
|
||||||
|
// Feels like a bug in the Kysely extension but I just can not do this orderBy in a type-safe manner
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
||||||
|
.over((ob) => ob.orderBy(fn('DATE_TRUNC', [sql.lit('MONTH'), 'Document.updatedAt']) as any))
|
||||||
|
.as('cume_count'),
|
||||||
|
])
|
||||||
|
.where(() => sql`"Document"."status" = ${DocumentStatus.COMPLETED}::"DocumentStatus"`)
|
||||||
|
.groupBy('month')
|
||||||
|
.orderBy('month', 'desc')
|
||||||
|
.limit(12);
|
||||||
|
|
||||||
|
const result = await qb.execute();
|
||||||
|
|
||||||
|
const transformedData = {
|
||||||
|
labels: result.map((row) => DateTime.fromJSDate(row.month).toFormat('MMM yyyy')).reverse(),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: type === 'count' ? 'Completed Documents per Month' : 'Total Completed Documents',
|
||||||
|
data: result
|
||||||
|
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
|
||||||
|
.reverse(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetCompletedDocumentsMonthlyResult = Awaited<
|
||||||
|
ReturnType<typeof getCompletedDocumentsMonthly>
|
||||||
|
>;
|
||||||
42
apps/openpage-api/lib/growth/get-signer-conversion.ts
Normal file
42
apps/openpage-api/lib/growth/get-signer-conversion.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||||
|
|
||||||
|
export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' = 'count') => {
|
||||||
|
const qb = kyselyPrisma.$kysely
|
||||||
|
.selectFrom('Recipient')
|
||||||
|
.innerJoin('User', 'Recipient.email', 'User.email')
|
||||||
|
.select(({ fn }) => [
|
||||||
|
fn<Date>('DATE_TRUNC', [sql.lit('MONTH'), 'User.createdAt']).as('month'),
|
||||||
|
fn.count('Recipient.email').distinct().as('count'),
|
||||||
|
fn
|
||||||
|
.sum(fn.count('Recipient.email').distinct())
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
||||||
|
.over((ob) => ob.orderBy(fn('DATE_TRUNC', [sql.lit('MONTH'), 'User.createdAt']) as any))
|
||||||
|
.as('cume_count'),
|
||||||
|
])
|
||||||
|
.where('Recipient.signedAt', 'is not', null)
|
||||||
|
.where('Recipient.signedAt', '<', (eb) => eb.ref('User.createdAt'))
|
||||||
|
.groupBy(({ fn }) => fn('DATE_TRUNC', [sql.lit('MONTH'), 'User.createdAt']))
|
||||||
|
.orderBy('month', 'desc');
|
||||||
|
|
||||||
|
const result = await qb.execute();
|
||||||
|
|
||||||
|
const transformedData = {
|
||||||
|
labels: result.map((row) => DateTime.fromJSDate(row.month).toFormat('MMM yyyy')).reverse(),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: type === 'count' ? 'Signers That Signed Up' : 'Total Signers That Signed Up',
|
||||||
|
data: result
|
||||||
|
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
|
||||||
|
.reverse(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetSignerConversionMonthlyResult = Awaited<
|
||||||
|
ReturnType<typeof getSignerConversionMonthly>
|
||||||
|
>;
|
||||||
38
apps/openpage-api/lib/growth/get-user-monthly-growth.ts
Normal file
38
apps/openpage-api/lib/growth/get-user-monthly-growth.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||||
|
|
||||||
|
export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count') => {
|
||||||
|
const qb = kyselyPrisma.$kysely
|
||||||
|
.selectFrom('User')
|
||||||
|
.select(({ fn }) => [
|
||||||
|
fn<Date>('DATE_TRUNC', [sql.lit('MONTH'), 'User.createdAt']).as('month'),
|
||||||
|
fn.count('id').as('count'),
|
||||||
|
fn
|
||||||
|
.sum(fn.count('id'))
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
||||||
|
.over((ob) => ob.orderBy(fn('DATE_TRUNC', [sql.lit('MONTH'), 'User.createdAt']) as any))
|
||||||
|
.as('cume_count'),
|
||||||
|
])
|
||||||
|
.groupBy('month')
|
||||||
|
.orderBy('month', 'desc')
|
||||||
|
.limit(12);
|
||||||
|
|
||||||
|
const result = await qb.execute();
|
||||||
|
|
||||||
|
const transformedData = {
|
||||||
|
labels: result.map((row) => DateTime.fromJSDate(row.month).toFormat('MMM yyyy')).reverse(),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: type === 'count' ? 'New Users' : 'Total Users',
|
||||||
|
data: result
|
||||||
|
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
|
||||||
|
.reverse(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetUserMonthlyGrowthResult = Awaited<ReturnType<typeof getUserMonthlyGrowth>>;
|
||||||
68
apps/openpage-api/lib/transform-data.ts
Normal file
68
apps/openpage-api/lib/transform-data.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
type MetricKeys = {
|
||||||
|
stars: number;
|
||||||
|
forks: number;
|
||||||
|
mergedPRs: number;
|
||||||
|
openIssues: number;
|
||||||
|
earlyAdopters: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DataEntry = {
|
||||||
|
[key: string]: MetricKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TransformData = {
|
||||||
|
labels: string[];
|
||||||
|
datasets: {
|
||||||
|
label: string;
|
||||||
|
data: number[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type MetricKey = keyof MetricKeys;
|
||||||
|
|
||||||
|
const FRIENDLY_METRIC_NAMES: { [key in MetricKey]: string } = {
|
||||||
|
stars: 'Stars',
|
||||||
|
forks: 'Forks',
|
||||||
|
mergedPRs: 'Merged PRs',
|
||||||
|
openIssues: 'Open Issues',
|
||||||
|
earlyAdopters: 'Customers',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function transformData({
|
||||||
|
data,
|
||||||
|
metric,
|
||||||
|
}: {
|
||||||
|
data: DataEntry;
|
||||||
|
metric: MetricKey;
|
||||||
|
}): TransformData {
|
||||||
|
const sortedEntries = Object.entries(data).sort(([dateA], [dateB]) => {
|
||||||
|
const [yearA, monthA] = dateA.split('-').map(Number);
|
||||||
|
const [yearB, monthB] = dateB.split('-').map(Number);
|
||||||
|
|
||||||
|
return DateTime.local(yearA, monthA).toMillis() - DateTime.local(yearB, monthB).toMillis();
|
||||||
|
});
|
||||||
|
|
||||||
|
const labels = sortedEntries.map(([date]) => {
|
||||||
|
const [year, month] = date.split('-');
|
||||||
|
const dateTime = DateTime.fromObject({
|
||||||
|
year: Number(year),
|
||||||
|
month: Number(month),
|
||||||
|
});
|
||||||
|
return dateTime.toFormat('MMM yyyy');
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`,
|
||||||
|
data: sortedEntries.map(([_, stats]) => stats[metric]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// To be on the safer side
|
||||||
|
export const transformRepoStats = transformData;
|
||||||
4
apps/openpage-api/next.config.js
Normal file
4
apps/openpage-api/next.config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
23
apps/openpage-api/package.json
Normal file
23
apps/openpage-api/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@documenso/openpage-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev -p 3003",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint:fix": "next lint --fix",
|
||||||
|
"clean": "rimraf .next && rimraf node_modules",
|
||||||
|
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@documenso/prisma": "*",
|
||||||
|
"luxon": "^3.5.0",
|
||||||
|
"next": "14.2.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "20.16.5",
|
||||||
|
"@types/react": "18.3.5",
|
||||||
|
"typescript": "5.5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
apps/openpage-api/tsconfig.json
Normal file
27
apps/openpage-api/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/web",
|
"name": "@documenso/web",
|
||||||
"version": "1.8.1-rc.7",
|
"version": "1.9.0-rc.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"uqr": "^0.1.2",
|
"uqr": "^0.1.2",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const AdminDocumentResults = () => {
|
|||||||
const { data: findDocumentsData, isLoading: isFindDocumentsLoading } =
|
const { data: findDocumentsData, isLoading: isFindDocumentsLoading } =
|
||||||
trpc.admin.findDocuments.useQuery(
|
trpc.admin.findDocuments.useQuery(
|
||||||
{
|
{
|
||||||
term: debouncedTerm,
|
query: debouncedTerm,
|
||||||
page: page || 1,
|
page: page || 1,
|
||||||
perPage: perPage || 20,
|
perPage: perPage || 20,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const DocumentPageViewButton = ({ document, team }: DocumentPageViewButto
|
|||||||
const onDownloadClick = async () => {
|
const onDownloadClick = async () => {
|
||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query({
|
const documentWithData = await trpcClient.document.getDocumentById.query({
|
||||||
id: document.id,
|
documentId: document.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
|
|||||||
const onDownloadClick = async () => {
|
const onDownloadClick = async () => {
|
||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query({
|
const documentWithData = await trpcClient.document.getDocumentById.query({
|
||||||
id: document.id,
|
documentId: document.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
|||||||
const { user } = await getRequiredServerComponentSession();
|
const { user } = await getRequiredServerComponentSession();
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: documentId,
|
documentId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const EditDocumentForm = ({
|
|||||||
const { data: document, refetch: refetchDocument } =
|
const { data: document, refetch: refetchDocument } =
|
||||||
trpc.document.getDocumentWithDetailsById.useQuery(
|
trpc.document.getDocumentWithDetailsById.useQuery(
|
||||||
{
|
{
|
||||||
id: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -79,7 +79,7 @@ export const EditDocumentForm = ({
|
|||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
id: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
||||||
@@ -93,7 +93,7 @@ export const EditDocumentForm = ({
|
|||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
id: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), ...newData, id: Number(newData.id) }),
|
(oldData) => ({ ...(oldData || initialDocument), ...newData, id: Number(newData.id) }),
|
||||||
@@ -106,7 +106,7 @@ export const EditDocumentForm = ({
|
|||||||
onSuccess: (newFields) => {
|
onSuccess: (newFields) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
id: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), Field: newFields }),
|
(oldData) => ({ ...(oldData || initialDocument), Field: newFields }),
|
||||||
@@ -120,7 +120,7 @@ export const EditDocumentForm = ({
|
|||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
id: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({
|
(oldData) => ({
|
||||||
@@ -137,7 +137,7 @@ export const EditDocumentForm = ({
|
|||||||
onSuccess: (newRecipients) => {
|
onSuccess: (newRecipients) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
id: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), Recipient: newRecipients }),
|
(oldData) => ({ ...(oldData || initialDocument), Recipient: newRecipients }),
|
||||||
@@ -150,7 +150,7 @@ export const EditDocumentForm = ({
|
|||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
id: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
|
|||||||
const { user } = await getRequiredServerComponentSession();
|
const { user } = await getRequiredServerComponentSession();
|
||||||
|
|
||||||
const document = await getDocumentWithDetailsById({
|
const document = await getDocumentWithDetailsById({
|
||||||
id: documentId,
|
documentId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type { DateTimeFormatOptions } from 'luxon';
|
|||||||
import { UAParser } from 'ua-parser-js';
|
import { UAParser } from 'ua-parser-js';
|
||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
|
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
@@ -35,9 +35,7 @@ export const DocumentLogsDataTable = ({ documentId }: DocumentLogsDataTableProps
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
|
|
||||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
Object.fromEntries(searchParams ?? []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isInitialLoading, isLoadingError } =
|
const { data, isLoading, isInitialLoading, isLoadingError } =
|
||||||
trpc.document.findDocumentAuditLogs.useQuery(
|
trpc.document.findDocumentAuditLogs.useQuery(
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
|
|||||||
|
|
||||||
const [document, recipients] = await Promise.all([
|
const [document, recipients] = await Promise.all([
|
||||||
getDocumentById({
|
getDocumentById({
|
||||||
id: documentId,
|
documentId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
}).catch(() => null),
|
}).catch(() => null),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const DataTableActionButton = ({ row, team }: DataTableActionButtonProps)
|
|||||||
|
|
||||||
if (!recipient) {
|
if (!recipient) {
|
||||||
document = await trpcClient.document.getDocumentById.query({
|
document = await trpcClient.document.getDocumentById.query({
|
||||||
id: row.id,
|
documentId: row.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
|||||||
|
|
||||||
if (!recipient) {
|
if (!recipient) {
|
||||||
document = await trpcClient.document.getDocumentById.query({
|
document = await trpcClient.document.getDocumentById.query({
|
||||||
id: row.id,
|
documentId: row.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { DateTime } from 'luxon';
|
|||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import type { FindResultSet } from '@documenso/lib/types/find-result-set';
|
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||||
import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
|
import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
|
||||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
@@ -24,7 +24,7 @@ import { DataTableActionDropdown } from './data-table-action-dropdown';
|
|||||||
import { DataTableTitle } from './data-table-title';
|
import { DataTableTitle } from './data-table-title';
|
||||||
|
|
||||||
export type DocumentsDataTableProps = {
|
export type DocumentsDataTableProps = {
|
||||||
results: FindResultSet<
|
results: FindResultResponse<
|
||||||
Document & {
|
Document & {
|
||||||
Recipient: Recipient[];
|
Recipient: Recipient[];
|
||||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export const DeleteDocumentDialog = ({
|
|||||||
|
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
try {
|
try {
|
||||||
await deleteDocument({ id, teamId });
|
await deleteDocument({ documentId: id, teamId });
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Something went wrong`),
|
title: _(msg`Something went wrong`),
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
|||||||
perPage,
|
perPage,
|
||||||
period,
|
period,
|
||||||
senderIds,
|
senderIds,
|
||||||
search,
|
query: search,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTabHref = (value: typeof status) => {
|
const getTabHref = (value: typeof status) => {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const DuplicateDocumentDialog = ({
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery({
|
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery({
|
||||||
id,
|
documentId: id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ export const DuplicateDocumentDialog = ({
|
|||||||
|
|
||||||
const onDuplicate = async () => {
|
const onDuplicate = async () => {
|
||||||
try {
|
try {
|
||||||
await duplicateDocument({ id, teamId: team?.id });
|
await duplicateDocument({ documentId: id, teamId: team?.id });
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Something went wrong`),
|
title: _(msg`Something went wrong`),
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { signOut } from 'next-auth/react';
|
import { signOut } from 'next-auth/react';
|
||||||
|
|
||||||
import type { User } from '@documenso/prisma/client';
|
import type { User } from '@documenso/prisma/client';
|
||||||
import { TRPCClientError } from '@documenso/trpc/client';
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@@ -52,24 +51,14 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
|
|||||||
|
|
||||||
return await signOut({ callbackUrl: '/' });
|
return await signOut({ callbackUrl: '/' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
|
||||||
toast({
|
|
||||||
title: _(msg`An error occurred`),
|
|
||||||
description: err.message,
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An unknown error occurred`),
|
title: _(msg`An unknown error occurred`),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
description:
|
description: _(
|
||||||
err.message ??
|
|
||||||
_(
|
|
||||||
msg`We encountered an unknown error while attempting to delete your account. Please try again later.`,
|
msg`We encountered an unknown error while attempting to delete your account. Please try again later.`,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export const PublicProfilePageView = ({ user, team, profile }: PublicProfilePage
|
|||||||
|
|
||||||
const enabledPrivateDirectTemplates = useMemo(
|
const enabledPrivateDirectTemplates = useMemo(
|
||||||
() =>
|
() =>
|
||||||
(data?.templates ?? []).filter(
|
(data?.data ?? []).filter(
|
||||||
(template): template is DirectTemplate =>
|
(template): template is DirectTemplate =>
|
||||||
template.directLink?.enabled === true && template.type !== TemplateType.PUBLIC,
|
template.directLink?.enabled === true && template.type !== TemplateType.PUBLIC,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const PublicTemplatesDataTable = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { directTemplates, publicDirectTemplates, privateDirectTemplates } = useMemo(() => {
|
const { directTemplates, publicDirectTemplates, privateDirectTemplates } = useMemo(() => {
|
||||||
const directTemplates = (data?.templates ?? []).filter(
|
const directTemplates = (data?.data ?? []).filter(
|
||||||
(template): template is DirectTemplate => template.directLink?.enabled === true,
|
(template): template is DirectTemplate => template.directLink?.enabled === true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { UAParser } from 'ua-parser-js';
|
|||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { USER_SECURITY_AUDIT_LOG_MAP } from '@documenso/lib/constants/auth';
|
import { USER_SECURITY_AUDIT_LOG_MAP } from '@documenso/lib/constants/auth';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
import { DataTable } from '@documenso/ui/primitives/data-table';
|
import { DataTable } from '@documenso/ui/primitives/data-table';
|
||||||
@@ -33,9 +33,7 @@ export const UserSecurityActivityDataTable = () => {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
|
|
||||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
Object.fromEntries(searchParams ?? []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isInitialLoading, isLoadingError } =
|
const { data, isLoading, isInitialLoading, isLoadingError } =
|
||||||
trpc.profile.findUserSecurityAuditLogs.useQuery(
|
trpc.profile.findUserSecurityAuditLogs.useQuery(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
import { DataTable } from '@documenso/ui/primitives/data-table';
|
import { DataTable } from '@documenso/ui/primitives/data-table';
|
||||||
@@ -27,9 +27,7 @@ export const UserPasskeysDataTable = () => {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
|
|
||||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
Object.fromEntries(searchParams ?? []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.auth.findPasskeys.useQuery(
|
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.auth.findPasskeys.useQuery(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ export const EditTemplateForm = ({
|
|||||||
|
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
|
|
||||||
const { data: template, refetch: refetchTemplate } =
|
const { data: template, refetch: refetchTemplate } = trpc.template.getTemplateById.useQuery(
|
||||||
trpc.template.getTemplateWithDetailsById.useQuery(
|
|
||||||
{
|
{
|
||||||
id: initialTemplate.id,
|
templateId: initialTemplate.id,
|
||||||
|
teamId: initialTemplate.teamId || undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
initialData: initialTemplate,
|
initialData: initialTemplate,
|
||||||
@@ -92,12 +92,12 @@ export const EditTemplateForm = ({
|
|||||||
|
|
||||||
const currentDocumentFlow = documentFlow[step];
|
const currentDocumentFlow = documentFlow[step];
|
||||||
|
|
||||||
const { mutateAsync: updateTemplateSettings } = trpc.template.updateTemplateSettings.useMutation({
|
const { mutateAsync: updateTemplateSettings } = trpc.template.updateTemplate.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.template.getTemplateWithDetailsById.setData(
|
utils.template.getTemplateById.setData(
|
||||||
{
|
{
|
||||||
id: initialTemplate.id,
|
templateId: initialTemplate.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
||||||
);
|
);
|
||||||
@@ -108,9 +108,9 @@ export const EditTemplateForm = ({
|
|||||||
trpc.template.setSigningOrderForTemplate.useMutation({
|
trpc.template.setSigningOrderForTemplate.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.template.getTemplateWithDetailsById.setData(
|
utils.template.getTemplateById.setData(
|
||||||
{
|
{
|
||||||
id: initialTemplate.id,
|
templateId: initialTemplate.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
||||||
);
|
);
|
||||||
@@ -120,9 +120,9 @@ export const EditTemplateForm = ({
|
|||||||
const { mutateAsync: addTemplateFields } = trpc.field.addTemplateFields.useMutation({
|
const { mutateAsync: addTemplateFields } = trpc.field.addTemplateFields.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.template.getTemplateWithDetailsById.setData(
|
utils.template.getTemplateById.setData(
|
||||||
{
|
{
|
||||||
id: initialTemplate.id,
|
templateId: initialTemplate.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
||||||
);
|
);
|
||||||
@@ -132,9 +132,9 @@ export const EditTemplateForm = ({
|
|||||||
const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation({
|
const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.template.getTemplateWithDetailsById.setData(
|
utils.template.getTemplateById.setData(
|
||||||
{
|
{
|
||||||
id: initialTemplate.id,
|
templateId: initialTemplate.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
||||||
);
|
);
|
||||||
@@ -145,9 +145,9 @@ export const EditTemplateForm = ({
|
|||||||
trpc.template.updateTemplateTypedSignatureSettings.useMutation({
|
trpc.template.updateTemplateTypedSignatureSettings.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.template.getTemplateWithDetailsById.setData(
|
utils.template.getTemplateById.setData(
|
||||||
{
|
{
|
||||||
id: initialTemplate.id,
|
templateId: initialTemplate.id,
|
||||||
},
|
},
|
||||||
(oldData) => ({
|
(oldData) => ({
|
||||||
...(oldData || initialTemplate),
|
...(oldData || initialTemplate),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ChevronLeft } from 'lucide-react';
|
|||||||
|
|
||||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||||
import { getTemplateWithDetailsById } from '@documenso/lib/server-only/template/get-template-with-details-by-id';
|
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||||
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
|
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||||
import type { Team } from '@documenso/prisma/client';
|
import type { Team } from '@documenso/prisma/client';
|
||||||
|
|
||||||
@@ -37,9 +37,10 @@ export const TemplateEditPageView = async ({ params, team }: TemplateEditPageVie
|
|||||||
|
|
||||||
const { user } = await getRequiredServerComponentSession();
|
const { user } = await getRequiredServerComponentSession();
|
||||||
|
|
||||||
const template = await getTemplateWithDetailsById({
|
const template = await getTemplateById({
|
||||||
id: templateId,
|
id: templateId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|
||||||
if (!template || !template.templateDocumentData) {
|
if (!template || !template.templateDocumentData) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { DateTime } from 'luxon';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import type { Team } from '@documenso/prisma/client';
|
import type { Team } from '@documenso/prisma/client';
|
||||||
import { DocumentSource, DocumentStatus as DocumentStatusEnum } from '@documenso/prisma/client';
|
import { DocumentSource, DocumentStatus as DocumentStatusEnum } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@@ -40,7 +40,7 @@ const DOCUMENT_SOURCE_LABELS: { [key in DocumentSource]: MessageDescriptor } = {
|
|||||||
TEMPLATE_DIRECT_LINK: msg`Direct link`,
|
TEMPLATE_DIRECT_LINK: msg`Direct link`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZTemplateSearchParamsSchema = ZBaseTableSearchParamsSchema.extend({
|
const ZDocumentSearchParamsSchema = ZUrlSearchParamsSchema.extend({
|
||||||
source: z
|
source: z
|
||||||
.nativeEnum(DocumentSource)
|
.nativeEnum(DocumentSource)
|
||||||
.optional()
|
.optional()
|
||||||
@@ -49,10 +49,6 @@ const ZTemplateSearchParamsSchema = ZBaseTableSearchParamsSchema.extend({
|
|||||||
.nativeEnum(DocumentStatusEnum)
|
.nativeEnum(DocumentStatusEnum)
|
||||||
.optional()
|
.optional()
|
||||||
.catch(() => undefined),
|
.catch(() => undefined),
|
||||||
search: z.coerce
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.catch(() => undefined),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type TemplatePageViewDocumentsTableProps = {
|
type TemplatePageViewDocumentsTableProps = {
|
||||||
@@ -69,7 +65,7 @@ export const TemplatePageViewDocumentsTable = ({
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
|
|
||||||
const parsedSearchParams = ZTemplateSearchParamsSchema.parse(
|
const parsedSearchParams = ZDocumentSearchParamsSchema.parse(
|
||||||
Object.fromEntries(searchParams ?? []),
|
Object.fromEntries(searchParams ?? []),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -80,7 +76,7 @@ export const TemplatePageViewDocumentsTable = ({
|
|||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
perPage: parsedSearchParams.perPage,
|
perPage: parsedSearchParams.perPage,
|
||||||
search: parsedSearchParams.search,
|
query: parsedSearchParams.query,
|
||||||
source: parsedSearchParams.source,
|
source: parsedSearchParams.source,
|
||||||
status: parsedSearchParams.status,
|
status: parsedSearchParams.status,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const DeleteTemplateDialog = ({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onClick={async () => deleteTemplate({ id, teamId })}
|
onClick={async () => deleteTemplate({ templateId: id, teamId })}
|
||||||
>
|
>
|
||||||
<Trans>Delete</Trans>
|
<Trans>Delete</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const TemplatesPageView = async ({ searchParams = {}, team }: TemplatesPa
|
|||||||
const documentRootPath = formatDocumentsPath(team?.url);
|
const documentRootPath = formatDocumentsPath(team?.url);
|
||||||
const templateRootPath = formatTemplatesPath(team?.url);
|
const templateRootPath = formatTemplatesPath(team?.url);
|
||||||
|
|
||||||
const { templates, totalPages } = await findTemplates({
|
const { data: templates, totalPages } = await findTemplates({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
page: page,
|
page: page,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Trans } from '@lingui/macro';
|
|||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon } from 'lucide-react';
|
||||||
|
|
||||||
import LogoIcon from '@documenso/assets/logo_icon.png';
|
import LogoIcon from '@documenso/assets/logo_icon.png';
|
||||||
import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||||
import type { User } from '@documenso/prisma/client';
|
import type { User } from '@documenso/prisma/client';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@@ -19,7 +19,7 @@ import { Logo } from '~/components/branding/logo';
|
|||||||
|
|
||||||
type ProfileHeaderProps = {
|
type ProfileHeaderProps = {
|
||||||
user?: User | null;
|
user?: User | null;
|
||||||
teams?: GetTeamsResponse;
|
teams?: TGetTeamsResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProfileHeader = ({ user, teams = [] }: ProfileHeaderProps) => {
|
export const ProfileHeader = ({ user, teams = [] }: ProfileHeaderProps) => {
|
||||||
@@ -58,7 +58,7 @@ export const ProfileHeader = ({ user, teams = [] }: ProfileHeaderProps) => {
|
|||||||
alt="Documenso Logo"
|
alt="Documenso Logo"
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
className="h-10 w-auto dark:invert sm:hidden"
|
className="h-10 w-auto sm:hidden dark:invert"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export type TConfigureDirectTemplateFormSchema = z.infer<typeof ZConfigureDirect
|
|||||||
export type ConfigureDirectTemplateFormProps = {
|
export type ConfigureDirectTemplateFormProps = {
|
||||||
flowStep: DocumentFlowStep;
|
flowStep: DocumentFlowStep;
|
||||||
isDocumentPdfLoaded: boolean;
|
isDocumentPdfLoaded: boolean;
|
||||||
template: TemplateWithDetails;
|
template: Omit<TemplateWithDetails, 'User'>;
|
||||||
directTemplateRecipient: Recipient & { Field: Field[] };
|
directTemplateRecipient: Recipient & { Field: Field[] };
|
||||||
initialEmail?: string;
|
initialEmail?: string;
|
||||||
onSubmit: (_data: TConfigureDirectTemplateFormSchema) => void;
|
onSubmit: (_data: TConfigureDirectTemplateFormSchema) => void;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import type { DirectTemplateLocalField } from './sign-direct-template';
|
|||||||
import { SignDirectTemplateForm } from './sign-direct-template';
|
import { SignDirectTemplateForm } from './sign-direct-template';
|
||||||
|
|
||||||
export type TemplatesDirectPageViewProps = {
|
export type TemplatesDirectPageViewProps = {
|
||||||
template: TemplateWithDetails;
|
template: Omit<TemplateWithDetails, 'User'>;
|
||||||
directTemplateToken: string;
|
directTemplateToken: string;
|
||||||
directTemplateRecipient: Recipient & { Field: Field[] };
|
directTemplateRecipient: Recipient & { Field: Field[] };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ export type SignDirectTemplateFormProps = {
|
|||||||
flowStep: DocumentFlowStep;
|
flowStep: DocumentFlowStep;
|
||||||
directRecipient: Recipient;
|
directRecipient: Recipient;
|
||||||
directRecipientFields: Field[];
|
directRecipientFields: Field[];
|
||||||
template: TemplateWithDetails;
|
template: Omit<TemplateWithDetails, 'User'>;
|
||||||
onSubmit: (_data: DirectTemplateLocalField[]) => Promise<void>;
|
onSubmit: (_data: DirectTemplateLocalField[]) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,9 +71,8 @@ export const SignDirectTemplateForm = ({
|
|||||||
template,
|
template,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: SignDirectTemplateFormProps) => {
|
}: SignDirectTemplateFormProps) => {
|
||||||
const { _ } = useLingui();
|
const { fullName, signature, signatureValid, setFullName, setSignature } =
|
||||||
|
useRequiredSigningContext();
|
||||||
const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext();
|
|
||||||
|
|
||||||
const [localFields, setLocalFields] = useState<DirectTemplateLocalField[]>(directRecipientFields);
|
const [localFields, setLocalFields] = useState<DirectTemplateLocalField[]>(directRecipientFields);
|
||||||
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
||||||
@@ -135,6 +133,8 @@ export const SignDirectTemplateForm = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasSignatureField = localFields.some((field) => field.type === FieldType.SIGNATURE);
|
||||||
|
|
||||||
const uninsertedFields = useMemo(() => {
|
const uninsertedFields = useMemo(() => {
|
||||||
return sortFieldsByPosition(localFields.filter((field) => !field.inserted));
|
return sortFieldsByPosition(localFields.filter((field) => !field.inserted));
|
||||||
}, [localFields]);
|
}, [localFields]);
|
||||||
@@ -147,6 +147,10 @@ export const SignDirectTemplateForm = ({
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
setValidateUninsertedFields(true);
|
setValidateUninsertedFields(true);
|
||||||
|
|
||||||
|
if (hasSignatureField && !signatureValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isFieldsValid = validateFieldsInserted(localFields);
|
const isFieldsValid = validateFieldsInserted(localFields);
|
||||||
|
|
||||||
if (!isFieldsValid) {
|
if (!isFieldsValid) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||||
import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||||
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
|
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
|
||||||
|
|
||||||
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
|
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
|
||||||
@@ -23,7 +23,7 @@ export default async function RecipientLayout({ children }: RecipientLayoutProps
|
|||||||
|
|
||||||
const { user, session } = await getServerComponentSession();
|
const { user, session } = await getServerComponentSession();
|
||||||
|
|
||||||
let teams: GetTeamsResponse = [];
|
let teams: TGetTeamsResponse = [];
|
||||||
|
|
||||||
if (user && session) {
|
if (user && session) {
|
||||||
teams = await getTeams({ userId: user.id });
|
teams = await getTeams({ userId: user.id });
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||||
import { TRPCClientError } from '@documenso/trpc/client';
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@@ -25,6 +25,8 @@ import { Input } from '@documenso/ui/primitives/input';
|
|||||||
import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
import { signupErrorMessages } from '~/components/forms/v2/signup';
|
||||||
|
|
||||||
export type ClaimAccountProps = {
|
export type ClaimAccountProps = {
|
||||||
defaultName: string;
|
defaultName: string;
|
||||||
defaultEmail: string;
|
defaultEmail: string;
|
||||||
@@ -33,7 +35,10 @@ export type ClaimAccountProps = {
|
|||||||
|
|
||||||
export const ZClaimAccountFormSchema = z
|
export const ZClaimAccountFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: msg`Please enter a valid name.`.id }),
|
||||||
email: z.string().email().min(1),
|
email: z.string().email().min(1),
|
||||||
password: ZPasswordSchema,
|
password: ZPasswordSchema,
|
||||||
})
|
})
|
||||||
@@ -43,7 +48,7 @@ export const ZClaimAccountFormSchema = z
|
|||||||
return !password.includes(name) && !password.includes(email.split('@')[0]);
|
return !password.includes(name) && !password.includes(email.split('@')[0]);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: 'Password should not be common or based on personal information',
|
message: msg`Password should not be common or based on personal information`.id,
|
||||||
path: ['password'],
|
path: ['password'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -86,22 +91,16 @@ export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) =
|
|||||||
email,
|
email,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
if (error instanceof TRPCClientError && error.data?.code === 'BAD_REQUEST') {
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
|
const errorMessage = signupErrorMessages[error.code] ?? signupErrorMessages.INVALID_REQUEST;
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An error occurred`),
|
title: _(msg`An error occurred`),
|
||||||
description: error.message,
|
description: _(errorMessage),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: _(msg`An unknown error occurred`),
|
|
||||||
description: _(
|
|
||||||
msg`We encountered an unknown error while attempting to sign you up. Please try again later.`,
|
|
||||||
),
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
|||||||
import type { DocumentAndSender } from '@documenso/lib/server-only/document/get-document-by-token';
|
import type { DocumentAndSender } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||||
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
|
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
|
||||||
import { type Field, type Recipient, RecipientRole } from '@documenso/prisma/client';
|
import { type Field, FieldType, type Recipient, RecipientRole } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@@ -44,7 +44,8 @@ export const SigningForm = ({
|
|||||||
const analytics = useAnalytics();
|
const analytics = useAnalytics();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext();
|
const { fullName, signature, setFullName, setSignature, signatureValid, setSignatureValid } =
|
||||||
|
useRequiredSigningContext();
|
||||||
|
|
||||||
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ export const SigningForm = ({
|
|||||||
// Keep the loading state going if successful since the redirect may take some time.
|
// Keep the loading state going if successful since the redirect may take some time.
|
||||||
const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful;
|
const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful;
|
||||||
|
|
||||||
|
const hasSignatureField = fields.some((field) => field.type === FieldType.SIGNATURE);
|
||||||
|
|
||||||
const uninsertedFields = useMemo(() => {
|
const uninsertedFields = useMemo(() => {
|
||||||
return sortFieldsByPosition(fields.filter((field) => !field.inserted));
|
return sortFieldsByPosition(fields.filter((field) => !field.inserted));
|
||||||
}, [fields]);
|
}, [fields]);
|
||||||
@@ -68,6 +71,10 @@ export const SigningForm = ({
|
|||||||
const onFormSubmit = async () => {
|
const onFormSubmit = async () => {
|
||||||
setValidateUninsertedFields(true);
|
setValidateUninsertedFields(true);
|
||||||
|
|
||||||
|
if (hasSignatureField && !signatureValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isFieldsValid = validateFieldsInserted(fields);
|
const isFieldsValid = validateFieldsInserted(fields);
|
||||||
|
|
||||||
if (!isFieldsValid) {
|
if (!isFieldsValid) {
|
||||||
@@ -198,13 +205,26 @@ export const SigningForm = ({
|
|||||||
className="h-44 w-full"
|
className="h-44 w-full"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
defaultValue={signature ?? undefined}
|
defaultValue={signature ?? undefined}
|
||||||
|
onValidityChange={(isValid) => {
|
||||||
|
setSignatureValid(isValid);
|
||||||
|
}}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
if (signatureValid) {
|
||||||
setSignature(value);
|
setSignature(value);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
allowTypedSignature={document.documentMeta?.typedSignatureEnabled}
|
allowTypedSignature={document.documentMeta?.typedSignatureEnabled}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{hasSignatureField && !signatureValid && (
|
||||||
|
<div className="text-destructive mt-2 text-sm">
|
||||||
|
<Trans>
|
||||||
|
Signature is too small. Please provide a more complete signature.
|
||||||
|
</Trans>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||||
import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||||
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
|
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
|
||||||
|
|
||||||
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
|
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
|
||||||
@@ -17,7 +17,7 @@ export default async function SigningLayout({ children }: SigningLayoutProps) {
|
|||||||
|
|
||||||
const { user, session } = await getServerComponentSession();
|
const { user, session } = await getServerComponentSession();
|
||||||
|
|
||||||
let teams: GetTeamsResponse = [];
|
let teams: TGetTeamsResponse = [];
|
||||||
|
|
||||||
if (user && session) {
|
if (user && session) {
|
||||||
teams = await getTeams({ userId: user.id });
|
teams = await getTeams({ userId: user.id });
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export type SigningContextValue = {
|
|||||||
setEmail: (_value: string) => void;
|
setEmail: (_value: string) => void;
|
||||||
signature: string | null;
|
signature: string | null;
|
||||||
setSignature: (_value: string | null) => void;
|
setSignature: (_value: string | null) => void;
|
||||||
|
signatureValid: boolean;
|
||||||
|
setSignatureValid: (_valid: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SigningContext = createContext<SigningContextValue | null>(null);
|
const SigningContext = createContext<SigningContextValue | null>(null);
|
||||||
@@ -43,6 +45,7 @@ export const SigningProvider = ({
|
|||||||
const [fullName, setFullName] = useState(initialFullName || '');
|
const [fullName, setFullName] = useState(initialFullName || '');
|
||||||
const [email, setEmail] = useState(initialEmail || '');
|
const [email, setEmail] = useState(initialEmail || '');
|
||||||
const [signature, setSignature] = useState(initialSignature || null);
|
const [signature, setSignature] = useState(initialSignature || null);
|
||||||
|
const [signatureValid, setSignatureValid] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialSignature) {
|
if (initialSignature) {
|
||||||
@@ -59,6 +62,8 @@ export const SigningProvider = ({
|
|||||||
setEmail,
|
setEmail,
|
||||||
signature,
|
signature,
|
||||||
setSignature,
|
setSignature,
|
||||||
|
signatureValid,
|
||||||
|
setSignatureValid,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -55,8 +55,12 @@ export const SignatureField = ({
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [fontSize, setFontSize] = useState(2);
|
const [fontSize, setFontSize] = useState(2);
|
||||||
|
|
||||||
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
const {
|
||||||
useRequiredSigningContext();
|
signature: providedSignature,
|
||||||
|
setSignature: setProvidedSignature,
|
||||||
|
signatureValid,
|
||||||
|
setSignatureValid,
|
||||||
|
} = useRequiredSigningContext();
|
||||||
|
|
||||||
const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
|
const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
|
||||||
|
|
||||||
@@ -90,7 +94,7 @@ export const SignatureField = ({
|
|||||||
}, [field.inserted, signature?.signatureImageAsBase64]);
|
}, [field.inserted, signature?.signatureImageAsBase64]);
|
||||||
|
|
||||||
const onPreSign = () => {
|
const onPreSign = () => {
|
||||||
if (!providedSignature) {
|
if (!providedSignature || !signatureValid) {
|
||||||
setShowSignatureModal(true);
|
setShowSignatureModal(true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -117,7 +121,7 @@ export const SignatureField = ({
|
|||||||
try {
|
try {
|
||||||
const value = signature || providedSignature;
|
const value = signature || providedSignature;
|
||||||
|
|
||||||
if (!value) {
|
if (!value || (signature && !signatureValid)) {
|
||||||
setShowSignatureModal(true);
|
setShowSignatureModal(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -282,14 +286,25 @@ export const SignatureField = ({
|
|||||||
<Trans>Signature</Trans>
|
<Trans>Signature</Trans>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
|
<div className="border-border mt-2 rounded-md border">
|
||||||
<SignaturePad
|
<SignaturePad
|
||||||
id="signature"
|
id="signature"
|
||||||
className="border-border mt-2 h-44 w-full rounded-md border"
|
className="h-44 w-full"
|
||||||
onChange={(value) => setLocalSignature(value)}
|
onChange={(value) => setLocalSignature(value)}
|
||||||
allowTypedSignature={typedSignatureEnabled}
|
allowTypedSignature={typedSignatureEnabled}
|
||||||
|
onValidityChange={(isValid) => {
|
||||||
|
setSignatureValid(isValid);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!signatureValid && (
|
||||||
|
<div className="text-destructive mt-2 text-sm">
|
||||||
|
<Trans>Signature is too small. Please provide a more complete signature.</Trans>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<SigningDisclosure />
|
<SigningDisclosure />
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<div className="flex w-full flex-1 flex-nowrap gap-4">
|
<div className="flex w-full flex-1 flex-nowrap gap-4">
|
||||||
@@ -307,7 +322,7 @@ export const SignatureField = ({
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
disabled={!localSignature}
|
disabled={!localSignature || !signatureValid}
|
||||||
onClick={() => onDialogSignClick()}
|
onClick={() => onDialogSignClick()}
|
||||||
>
|
>
|
||||||
<Trans>Sign</Trans>
|
<Trans>Sign</Trans>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default async function WaitingForTurnToSignPage({
|
|||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
isOwnerOrTeamMember = await getDocumentById({
|
isOwnerOrTeamMember = await getDocumentById({
|
||||||
id: document.id,
|
documentId: document.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: document.teamId ?? undefined,
|
teamId: document.teamId ?? undefined,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { colord } from 'colord';
|
import { colord } from 'colord';
|
||||||
import { toSnakeCase } from 'remeda';
|
import { toKebabCase } from 'remeda';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const ZCssVarsSchema = z
|
export const ZCssVarsSchema = z
|
||||||
@@ -47,7 +47,7 @@ export const toNativeCssVars = (vars: TCssVarsSchema) => {
|
|||||||
const color = colord(value);
|
const color = colord(value);
|
||||||
const { h, s, l } = color.toHsl();
|
const { h, s, l } = color.toHsl();
|
||||||
|
|
||||||
cssVars[`--${toSnakeCase(key)}`] = `${h} ${s} ${l}`;
|
cssVars[`--${toKebabCase(key)}`] = `${h} ${s} ${l}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,8 +67,16 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const { fullName, email, signature, setFullName, setEmail, setSignature } =
|
const {
|
||||||
useRequiredSigningContext();
|
fullName,
|
||||||
|
email,
|
||||||
|
signature,
|
||||||
|
signatureValid,
|
||||||
|
setFullName,
|
||||||
|
setEmail,
|
||||||
|
setSignature,
|
||||||
|
setSignatureValid,
|
||||||
|
} = useRequiredSigningContext();
|
||||||
|
|
||||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||||
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
||||||
@@ -90,6 +98,8 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
localFields.filter((field) => field.inserted),
|
localFields.filter((field) => field.inserted),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const hasSignatureField = localFields.some((field) => field.type === FieldType.SIGNATURE);
|
||||||
|
|
||||||
const { mutateAsync: createDocumentFromDirectTemplate, isLoading: isSubmitting } =
|
const { mutateAsync: createDocumentFromDirectTemplate, isLoading: isSubmitting } =
|
||||||
trpc.template.createDocumentFromDirectTemplate.useMutation();
|
trpc.template.createDocumentFromDirectTemplate.useMutation();
|
||||||
|
|
||||||
@@ -180,6 +190,10 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
|
|
||||||
const onCompleteClick = async () => {
|
const onCompleteClick = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (hasSignatureField && !signatureValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const valid = validateFieldsInserted(localFields);
|
const valid = validateFieldsInserted(localFields);
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
@@ -417,6 +431,9 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSignature(value);
|
setSignature(value);
|
||||||
}}
|
}}
|
||||||
|
onValidityChange={(isValid) => {
|
||||||
|
setSignatureValid(isValid);
|
||||||
|
}}
|
||||||
allowTypedSignature={Boolean(
|
allowTypedSignature={Boolean(
|
||||||
metadata &&
|
metadata &&
|
||||||
'typedSignatureEnabled' in metadata &&
|
'typedSignatureEnabled' in metadata &&
|
||||||
@@ -425,6 +442,14 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{hasSignatureField && !signatureValid && (
|
||||||
|
<div className="text-destructive mt-2 text-sm">
|
||||||
|
<Trans>
|
||||||
|
Signature is too small. Please provide a more complete signature.
|
||||||
|
</Trans>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useThrottleFn } from '@documenso/lib/client-only/hooks/use-throttle-fn'
|
|||||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||||
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
|
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
|
||||||
import type { DocumentMeta, Recipient, TemplateMeta } from '@documenso/prisma/client';
|
import type { DocumentMeta, Recipient, TemplateMeta } from '@documenso/prisma/client';
|
||||||
import { type DocumentData, type Field } from '@documenso/prisma/client';
|
import { type DocumentData, type Field, FieldType } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@@ -57,7 +57,15 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { fullName, email, signature, setFullName, setSignature } = useRequiredSigningContext();
|
const {
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
signature,
|
||||||
|
signatureValid,
|
||||||
|
setFullName,
|
||||||
|
setSignature,
|
||||||
|
setSignatureValid,
|
||||||
|
} = useRequiredSigningContext();
|
||||||
|
|
||||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||||
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
||||||
@@ -79,6 +87,8 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
const { mutateAsync: completeDocumentWithToken, isLoading: isSubmitting } =
|
const { mutateAsync: completeDocumentWithToken, isLoading: isSubmitting } =
|
||||||
trpc.recipient.completeDocumentWithToken.useMutation();
|
trpc.recipient.completeDocumentWithToken.useMutation();
|
||||||
|
|
||||||
|
const hasSignatureField = fields.some((field) => field.type === FieldType.SIGNATURE);
|
||||||
|
|
||||||
const onNextFieldClick = () => {
|
const onNextFieldClick = () => {
|
||||||
validateFieldsInserted(fields);
|
validateFieldsInserted(fields);
|
||||||
|
|
||||||
@@ -88,6 +98,10 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
|
|
||||||
const onCompleteClick = async () => {
|
const onCompleteClick = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (hasSignatureField && !signatureValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const valid = validateFieldsInserted(fields);
|
const valid = validateFieldsInserted(fields);
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
@@ -296,6 +310,9 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSignature(value);
|
setSignature(value);
|
||||||
}}
|
}}
|
||||||
|
onValidityChange={(isValid) => {
|
||||||
|
setSignatureValid(isValid);
|
||||||
|
}}
|
||||||
allowTypedSignature={Boolean(
|
allowTypedSignature={Boolean(
|
||||||
metadata &&
|
metadata &&
|
||||||
'typedSignatureEnabled' in metadata &&
|
'typedSignatureEnabled' in metadata &&
|
||||||
@@ -304,6 +321,14 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{hasSignatureField && !signatureValid && (
|
||||||
|
<div className="text-destructive mt-2 text-sm">
|
||||||
|
<Trans>
|
||||||
|
Signature is too small. Please provide a more complete signature.
|
||||||
|
</Trans>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,7 +343,7 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
className="col-start-2"
|
className="col-start-2"
|
||||||
disabled={isThrottled}
|
disabled={isThrottled || (hasSignatureField && !signatureValid)}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
onClick={() => throttledOnCompleteClick()}
|
onClick={() => throttledOnCompleteClick()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { usePathname } from 'next/navigation';
|
|||||||
|
|
||||||
import { MenuIcon, SearchIcon } from 'lucide-react';
|
import { MenuIcon, SearchIcon } from 'lucide-react';
|
||||||
|
|
||||||
import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||||
import { getRootHref } from '@documenso/lib/utils/params';
|
import { getRootHref } from '@documenso/lib/utils/params';
|
||||||
import type { User } from '@documenso/prisma/client';
|
import type { User } from '@documenso/prisma/client';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@@ -22,7 +22,7 @@ import { MobileNavigation } from './mobile-navigation';
|
|||||||
|
|
||||||
export type HeaderProps = HTMLAttributes<HTMLDivElement> & {
|
export type HeaderProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
user: User;
|
user: User;
|
||||||
teams: GetTeamsResponse;
|
teams: TGetTeamsResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Header = ({ className, user, teams, ...props }: HeaderProps) => {
|
export const Header = ({ className, user, teams, ...props }: HeaderProps) => {
|
||||||
@@ -75,7 +75,7 @@ export const Header = ({ className, user, teams, ...props }: HeaderProps) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className="flex gap-x-4 md:ml-8"
|
className="flex gap-x-4 md:ml-8"
|
||||||
title={selectedTeam ? selectedTeam.name : user.name ?? ''}
|
title={selectedTeam ? selectedTeam.name : (user.name ?? '')}
|
||||||
>
|
>
|
||||||
<MenuSwitcher user={user} teams={teams} />
|
<MenuSwitcher user={user} teams={teams} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { signOut } from 'next-auth/react';
|
|||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
import { TEAM_MEMBER_ROLE_MAP, TEAM_URL_REGEX } from '@documenso/lib/constants/teams';
|
import { TEAM_MEMBER_ROLE_MAP, TEAM_URL_REGEX } from '@documenso/lib/constants/teams';
|
||||||
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
|
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
|
||||||
import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
||||||
import type { User } from '@documenso/prisma/client';
|
import type { User } from '@documenso/prisma/client';
|
||||||
@@ -36,7 +36,7 @@ const MotionLink = motion(Link);
|
|||||||
|
|
||||||
export type MenuSwitcherProps = {
|
export type MenuSwitcherProps = {
|
||||||
user: User;
|
user: User;
|
||||||
teams: GetTeamsResponse;
|
teams: TGetTeamsResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProps) => {
|
export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProps) => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL, WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL, WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||||
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||||
@@ -30,13 +30,11 @@ export const CurrentUserTeamsDataTable = () => {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
|
|
||||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
Object.fromEntries(searchParams ?? []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.team.findTeams.useQuery(
|
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.team.findTeams.useQuery(
|
||||||
{
|
{
|
||||||
term: parsedSearchParams.query,
|
query: parsedSearchParams.query,
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
perPage: parsedSearchParams.perPage,
|
perPage: parsedSearchParams.perPage,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
@@ -27,15 +27,13 @@ export const PendingUserTeamsDataTable = () => {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
|
|
||||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
Object.fromEntries(searchParams ?? []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const [checkoutPendingTeamId, setCheckoutPendingTeamId] = useState<number | null>(null);
|
const [checkoutPendingTeamId, setCheckoutPendingTeamId] = useState<number | null>(null);
|
||||||
|
|
||||||
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.team.findTeamsPending.useQuery(
|
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.team.findTeamsPending.useQuery(
|
||||||
{
|
{
|
||||||
term: parsedSearchParams.query,
|
query: parsedSearchParams.query,
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
perPage: parsedSearchParams.perPage,
|
perPage: parsedSearchParams.perPage,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { History, MoreHorizontal, Trash2 } from 'lucide-react';
|
|||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
@@ -38,15 +38,13 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
|||||||
const { _, i18n } = useLingui();
|
const { _, i18n } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
Object.fromEntries(searchParams ?? []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isInitialLoading, isLoadingError } =
|
const { data, isLoading, isInitialLoading, isLoadingError } =
|
||||||
trpc.team.findTeamMemberInvites.useQuery(
|
trpc.team.findTeamMemberInvites.useQuery(
|
||||||
{
|
{
|
||||||
teamId,
|
teamId,
|
||||||
term: parsedSearchParams.query,
|
query: parsedSearchParams.query,
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
perPage: parsedSearchParams.perPage,
|
perPage: parsedSearchParams.perPage,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Edit, MoreHorizontal, Trash2 } from 'lucide-react';
|
|||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
||||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||||
import type { TeamMemberRole } from '@documenso/prisma/client';
|
import type { TeamMemberRole } from '@documenso/prisma/client';
|
||||||
@@ -50,14 +50,12 @@ export const TeamMembersDataTable = ({
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
|
|
||||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
Object.fromEntries(searchParams ?? []),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.team.findTeamMembers.useQuery(
|
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.team.findTeamMembers.useQuery(
|
||||||
{
|
{
|
||||||
teamId,
|
teamId,
|
||||||
term: parsedSearchParams.query,
|
query: parsedSearchParams.query,
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
perPage: parsedSearchParams.perPage,
|
perPage: parsedSearchParams.perPage,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import { match } from 'ts-pattern';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
import { base64 } from '@documenso/lib/universal/base64';
|
import { base64 } from '@documenso/lib/universal/base64';
|
||||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import type { Team, User } from '@documenso/prisma/client';
|
import type { Team, User } from '@documenso/prisma/client';
|
||||||
import { TRPCClientError } from '@documenso/trpc/client';
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||||
@@ -111,21 +111,18 @@ export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps)
|
|||||||
|
|
||||||
router.refresh();
|
router.refresh();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
|
const errorMessage = match(error.code).otherwise(
|
||||||
|
() =>
|
||||||
|
msg`We encountered an unknown error while attempting to update your password. Please try again later.`,
|
||||||
|
);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An error occurred`),
|
title: _(msg`An error occurred`),
|
||||||
description: err.message,
|
description: _(errorMessage),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: _(msg`An unknown error occurred`),
|
|
||||||
description: _(
|
|
||||||
msg`We encountered an unknown error while attempting to update the avatar. Please try again later.`,
|
|
||||||
),
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { Trans, msg } from '@lingui/macro';
|
import { Trans, msg } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { match } from 'ts-pattern';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
import type { User } from '@documenso/prisma/client';
|
import type { User } from '@documenso/prisma/client';
|
||||||
import { TRPCClientError } from '@documenso/trpc/client';
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@@ -73,21 +74,25 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
|
const errorMessage = match(error.code)
|
||||||
|
.with('NO_PASSWORD', () => msg`User has no password.`)
|
||||||
|
.with('INCORRECT_PASSWORD', () => msg`Current password is incorrect.`)
|
||||||
|
.with(
|
||||||
|
'SAME_PASSWORD',
|
||||||
|
() => msg`Your new password cannot be the same as your old password.`,
|
||||||
|
)
|
||||||
|
.otherwise(
|
||||||
|
() =>
|
||||||
|
msg`We encountered an unknown error while attempting to update your password. Please try again later.`,
|
||||||
|
);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An error occurred`),
|
title: _(msg`An error occurred`),
|
||||||
description: err.message,
|
description: _(errorMessage),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: _(msg`An unknown error occurred`),
|
|
||||||
description: _(
|
|
||||||
msg`We encountered an unknown error while attempting to update your password. Please try again later.`,
|
|
||||||
),
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { Trans, msg } from '@lingui/macro';
|
import { Trans, msg } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { match } from 'ts-pattern';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { TRPCClientError } from '@documenso/trpc/client';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@@ -76,21 +77,25 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
|
|||||||
|
|
||||||
router.push('/signin');
|
router.push('/signin');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
|
const errorMessage = match(error.code)
|
||||||
|
.with(AppErrorCode.EXPIRED_CODE, () => msg`Token has expired. Please try again.`)
|
||||||
|
.with('INVALID_TOKEN', () => msg`Invalid token provided. Please try again.`)
|
||||||
|
.with(
|
||||||
|
'SAME_PASSWORD',
|
||||||
|
() => msg`Your new password cannot be the same as your old password.`,
|
||||||
|
)
|
||||||
|
.otherwise(
|
||||||
|
() =>
|
||||||
|
msg`We encountered an unknown error while attempting to reset your password. Please try again later.`,
|
||||||
|
);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An error occurred`),
|
title: _(msg`An error occurred`),
|
||||||
description: err.message,
|
description: _(errorMessage),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: _(msg`An unknown error occurred`),
|
|
||||||
description: _(
|
|
||||||
msg`We encountered an unknown error while attempting to reset your password. Please try again later.`,
|
|
||||||
),
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { FcGoogle } from 'react-icons/fc';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||||
import { TRPCClientError } from '@documenso/trpc/client';
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@@ -29,6 +29,8 @@ import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
|||||||
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
|
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
import { signupErrorMessages } from './v2/signup';
|
||||||
|
|
||||||
const SIGN_UP_REDIRECT_PATH = '/documents';
|
const SIGN_UP_REDIRECT_PATH = '/documents';
|
||||||
|
|
||||||
export const ZSignUpFormSchema = z
|
export const ZSignUpFormSchema = z
|
||||||
@@ -102,21 +104,15 @@ export const SignUpForm = ({
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
|
const errorMessage = signupErrorMessages[error.code] ?? signupErrorMessages.INVALID_REQUEST;
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An error occurred`),
|
title: _(msg`An error occurred`),
|
||||||
description: err.message,
|
description: _(errorMessage),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: _(msg`An unknown error occurred`),
|
|
||||||
description: _(
|
|
||||||
msg`We encountered an unknown error while attempting to sign you up. Please try again later.`,
|
|
||||||
),
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Link from 'next/link';
|
|||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
import { Trans, msg } from '@lingui/macro';
|
import { Trans, msg } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
@@ -20,7 +21,6 @@ import communityCardsImage from '@documenso/assets/images/community-cards.png';
|
|||||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { TRPCClientError } from '@documenso/trpc/client';
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@@ -47,17 +47,20 @@ type SignUpStep = 'BASIC_DETAILS' | 'CLAIM_USERNAME';
|
|||||||
|
|
||||||
export const ZSignUpFormV2Schema = z
|
export const ZSignUpFormV2Schema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: msg`Please enter a valid name.`.id }),
|
||||||
email: z.string().email().min(1),
|
email: z.string().email().min(1),
|
||||||
password: ZPasswordSchema,
|
password: ZPasswordSchema,
|
||||||
signature: z.string().min(1, { message: 'We need your signature to sign documents' }),
|
signature: z.string().min(1, { message: msg`We need your signature to sign documents`.id }),
|
||||||
url: z
|
url: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.min(1, { message: 'We need a username to create your profile' })
|
.min(1, { message: msg`We need a username to create your profile`.id })
|
||||||
.regex(/^[a-z0-9-]+$/, {
|
.regex(/^[a-z0-9-]+$/, {
|
||||||
message: 'Username can only container alphanumeric characters and dashes.',
|
message: msg`Username can only container alphanumeric characters and dashes.`.id,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@@ -66,10 +69,18 @@ export const ZSignUpFormV2Schema = z
|
|||||||
return !password.includes(name) && !password.includes(email.split('@')[0]);
|
return !password.includes(name) && !password.includes(email.split('@')[0]);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: 'Password should not be common or based on personal information',
|
message: msg`Password should not be common or based on personal information`.id,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const signupErrorMessages: Record<string, MessageDescriptor> = {
|
||||||
|
SIGNUP_DISABLED: msg`Signups are disabled.`,
|
||||||
|
[AppErrorCode.ALREADY_EXISTS]: msg`User with this email already exists. Please use a different email address.`,
|
||||||
|
[AppErrorCode.INVALID_REQUEST]: msg`We were unable to create your account. Please review the information you provided and try again.`,
|
||||||
|
[AppErrorCode.PROFILE_URL_TAKEN]: msg`This username has already been taken`,
|
||||||
|
[AppErrorCode.PREMIUM_PROFILE_URL]: msg`Only subscribers can have a username shorter than 6 characters`,
|
||||||
|
};
|
||||||
|
|
||||||
export type TSignUpFormV2Schema = z.infer<typeof ZSignUpFormV2Schema>;
|
export type TSignUpFormV2Schema = z.infer<typeof ZSignUpFormV2Schema>;
|
||||||
|
|
||||||
export type SignUpFormV2Props = {
|
export type SignUpFormV2Props = {
|
||||||
@@ -139,28 +150,20 @@ export const SignUpFormV2 = ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
if (error.code === AppErrorCode.PROFILE_URL_TAKEN) {
|
const errorMessage = signupErrorMessages[error.code] ?? signupErrorMessages.INVALID_REQUEST;
|
||||||
|
|
||||||
|
if (
|
||||||
|
error.code === AppErrorCode.PROFILE_URL_TAKEN ||
|
||||||
|
error.code === AppErrorCode.PREMIUM_PROFILE_URL
|
||||||
|
) {
|
||||||
form.setError('url', {
|
form.setError('url', {
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
message: _(msg`This username has already been taken`),
|
message: _(errorMessage),
|
||||||
});
|
|
||||||
} else if (error.code === AppErrorCode.PREMIUM_PROFILE_URL) {
|
|
||||||
form.setError('url', {
|
|
||||||
type: 'manual',
|
|
||||||
message: error.message,
|
|
||||||
});
|
|
||||||
} else if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
|
||||||
toast({
|
|
||||||
title: _(msg`An error occurred`),
|
|
||||||
description: err.message,
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An unknown error occurred`),
|
title: _(msg`An error occurred`),
|
||||||
description: _(
|
description: _(errorMessage),
|
||||||
msg`We encountered an unknown error while attempting to sign you up. Please try again later.`,
|
|
||||||
),
|
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export const ManagePublicTemplateDialog = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: updateTemplateSettings, isLoading: isUpdatingTemplateSettings } =
|
const { mutateAsync: updateTemplateSettings, isLoading: isUpdatingTemplateSettings } =
|
||||||
trpc.template.updateTemplateSettings.useMutation();
|
trpc.template.updateTemplate.useMutation();
|
||||||
|
|
||||||
const setTemplateToPrivate = async (templateId: number) => {
|
const setTemplateToPrivate = async (templateId: number) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ export default trpcNext.createNextApiHandler({
|
|||||||
onError(opts) {
|
onError(opts) {
|
||||||
const { error, path } = opts;
|
const { error, path } = opts;
|
||||||
|
|
||||||
// Currently trialing changes with template and team router only.
|
if (!path) {
|
||||||
if (!path || (!path.startsWith('template') && !path.startsWith('team'))) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { GetTeamResponse } from '@documenso/lib/server-only/team/get-team';
|
import type { TGetTeamByIdResponse } from '@documenso/lib/server-only/team/get-team';
|
||||||
|
|
||||||
interface TeamProviderProps {
|
interface TeamProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
team: GetTeamResponse;
|
team: TGetTeamByIdResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TeamContext = createContext<GetTeamResponse | null>(null);
|
const TeamContext = createContext<TGetTeamByIdResponse | null>(null);
|
||||||
|
|
||||||
export const useCurrentTeam = () => {
|
export const useCurrentTeam = () => {
|
||||||
const context = useContext(TeamContext);
|
const context = useContext(TeamContext);
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ module.exports = {
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
content: [
|
content: [
|
||||||
...baseConfig.content,
|
...baseConfig.content,
|
||||||
`${path.join(require.resolve('@documenso/ui'), '..')}/**/*.{ts,tsx}`,
|
`${path.join(require.resolve('@documenso/ui'), '..')}/components/**/*.{ts,tsx}`,
|
||||||
`${path.join(require.resolve('@documenso/email'), '..')}/**/*.{ts,tsx}`,
|
`${path.join(require.resolve('@documenso/ui'), '..')}/icons/**/*.{ts,tsx}`,
|
||||||
|
`${path.join(require.resolve('@documenso/ui'), '..')}/lib/**/*.{ts,tsx}`,
|
||||||
|
`${path.join(require.resolve('@documenso/ui'), '..')}/primitives/**/*.{ts,tsx}`,
|
||||||
|
`${path.join(require.resolve('@documenso/email'), '..')}/templates/**/*.{ts,tsx}`,
|
||||||
|
`${path.join(require.resolve('@documenso/email'), '..')}/template-components/**/*.{ts,tsx}`,
|
||||||
|
`${path.join(require.resolve('@documenso/email'), '..')}/providers/**/*.{ts,tsx}`,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
142
package-lock.json
generated
142
package-lock.json
generated
@@ -1,20 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "1.8.1-rc.7",
|
"version": "1.9.0-rc.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "1.8.1-rc.7",
|
"version": "1.9.0-rc.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/pdf-sign": "^0.1.0",
|
"@documenso/pdf-sign": "^0.1.0",
|
||||||
|
"@documenso/prisma": "^0.0.0",
|
||||||
"@lingui/core": "^4.11.3",
|
"@lingui/core": "^4.11.3",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
|
"luxon": "^3.5.0",
|
||||||
"next-runtime-env": "^3.2.0",
|
"next-runtime-env": "^3.2.0",
|
||||||
"react": "^18"
|
"react": "^18"
|
||||||
},
|
},
|
||||||
@@ -77,7 +79,7 @@
|
|||||||
},
|
},
|
||||||
"apps/marketing": {
|
"apps/marketing": {
|
||||||
"name": "@documenso/marketing",
|
"name": "@documenso/marketing",
|
||||||
"version": "1.8.1-rc.7",
|
"version": "1.9.0-rc.1",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/assets": "*",
|
"@documenso/assets": "*",
|
||||||
@@ -113,7 +115,7 @@
|
|||||||
"recharts": "^2.7.2",
|
"recharts": "^2.7.2",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lingui/loader": "^4.11.3",
|
"@lingui/loader": "^4.11.3",
|
||||||
@@ -436,9 +438,61 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"apps/openpage-api": {
|
||||||
|
"name": "@documenso/openpage-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@documenso/prisma": "*",
|
||||||
|
"luxon": "^3.5.0",
|
||||||
|
"next": "14.2.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "20.16.5",
|
||||||
|
"@types/react": "18.3.5",
|
||||||
|
"typescript": "5.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps/openpage-api/node_modules/@types/node": {
|
||||||
|
"version": "20.16.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
||||||
|
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.19.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps/openpage-api/node_modules/@types/react": {
|
||||||
|
"version": "18.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz",
|
||||||
|
"integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps/openpage-api/node_modules/typescript": {
|
||||||
|
"version": "5.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||||
|
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps/openpage-api/node_modules/undici-types": {
|
||||||
|
"version": "6.19.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"apps/web": {
|
"apps/web": {
|
||||||
"name": "@documenso/web",
|
"name": "@documenso/web",
|
||||||
"version": "1.8.1-rc.7",
|
"version": "1.9.0-rc.1",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/api": "*",
|
"@documenso/api": "*",
|
||||||
@@ -485,7 +539,7 @@
|
|||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"uqr": "^0.1.2",
|
"uqr": "^0.1.2",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
@@ -2669,6 +2723,10 @@
|
|||||||
"nodemailer": "^6.9.3"
|
"nodemailer": "^6.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@documenso/openpage-api": {
|
||||||
|
"resolved": "apps/openpage-api",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@documenso/pdf-sign": {
|
"node_modules/@documenso/pdf-sign": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@documenso/pdf-sign/-/pdf-sign-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@documenso/pdf-sign/-/pdf-sign-0.1.0.tgz",
|
||||||
@@ -14253,6 +14311,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/code-block-writer": {
|
||||||
|
"version": "12.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
|
||||||
|
"integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/color": {
|
"node_modules/color": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||||
@@ -19936,6 +20000,14 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inngest/node_modules/zod": {
|
||||||
|
"version": "3.22.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.5.tgz",
|
||||||
|
"integrity": "sha512-HqnGsCdVZ2xc0qWPLdO25WnseXThh0kEYKIdV5F/hTHO75hNZFp8thxSeHhiPrHZKrFTo1SOgkAj9po5bexZlw==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/input-otp": {
|
"node_modules/input-otp": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz",
|
||||||
@@ -22298,9 +22370,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/luxon": {
|
"node_modules/luxon": {
|
||||||
"version": "3.4.4",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
|
||||||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@@ -35337,9 +35409,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.22.4",
|
"version": "3.23.8",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||||
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
|
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -35352,6 +35424,36 @@
|
|||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/zod-prisma-types": {
|
||||||
|
"version": "3.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod-prisma-types/-/zod-prisma-types-3.1.8.tgz",
|
||||||
|
"integrity": "sha512-5oe0ays3ur4u2GtuUqlhgCraKBcsuMaMI8o7VMV4YAnFeOuVid7K2zGvjI19V0ue9PeNF2ICyVREQVohaQm5dw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/generator-helper": "^5.14.0",
|
||||||
|
"code-block-writer": "^12.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"zod-prisma-types": "dist/bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod-prisma-types/node_modules/@prisma/debug": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/zod-prisma-types/node_modules/@prisma/generator-helper": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-5.22.0.tgz",
|
||||||
|
"integrity": "sha512-LwqcBQ5/QsuAaLNQZAIVIAJDJBMjHwMwn16e06IYx/3Okj/xEEfw9IvrqB2cJCl3b2mCBlh3eVH0w9WGmi4aHg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zwitch": {
|
"node_modules/zwitch": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
||||||
@@ -35376,7 +35478,7 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"swagger-ui-react": "^5.11.0",
|
"swagger-ui-react": "^5.11.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/api/node_modules/@ts-rest/next": {
|
"packages/api/node_modules/@ts-rest/next": {
|
||||||
@@ -35441,7 +35543,7 @@
|
|||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/email": {
|
"packages/email": {
|
||||||
@@ -36650,7 +36752,7 @@
|
|||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/browser-chromium": "1.43.0",
|
"@playwright/browser-chromium": "1.43.0",
|
||||||
@@ -36721,7 +36823,8 @@
|
|||||||
"dotenv-cli": "^7.3.0",
|
"dotenv-cli": "^7.3.0",
|
||||||
"prisma-kysely": "^1.8.0",
|
"prisma-kysely": "^1.8.0",
|
||||||
"tsx": "^4.11.0",
|
"tsx": "^4.11.0",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2",
|
||||||
|
"zod-prisma-types": "^3.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/prisma/node_modules/ts-pattern": {
|
"packages/prisma/node_modules/ts-pattern": {
|
||||||
@@ -36787,9 +36890,8 @@
|
|||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
},
|
}
|
||||||
"devDependencies": {}
|
|
||||||
},
|
},
|
||||||
"packages/trpc/node_modules/@ts-rest/next": {
|
"packages/trpc/node_modules/@ts-rest/next": {
|
||||||
"version": "3.30.5",
|
"version": "3.30.5",
|
||||||
@@ -36868,7 +36970,7 @@
|
|||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.8.1-rc.7",
|
"version": "1.9.0-rc.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"build:web": "turbo run build --filter=@documenso/web",
|
"build:web": "turbo run build --filter=@documenso/web",
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
"dev:web": "turbo run dev --filter=@documenso/web",
|
"dev:web": "turbo run dev --filter=@documenso/web",
|
||||||
"dev:marketing": "turbo run dev --filter=@documenso/marketing",
|
"dev:marketing": "turbo run dev --filter=@documenso/marketing",
|
||||||
"dev:docs": "turbo run dev --filter=@documenso/documentation",
|
"dev:docs": "turbo run dev --filter=@documenso/documentation",
|
||||||
"start": "turbo run start --filter=@documenso/web --filter=@documenso/marketing --filter=@documenso/documentation",
|
"dev:openpage-api": "turbo run dev --filter=@documenso/openpage-api",
|
||||||
|
"start": "turbo run start --filter=@documenso/web --filter=@documenso/marketing --filter=@documenso/documentation --filter=@documenso/openpage-api",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"lint:fix": "turbo run lint:fix",
|
"lint:fix": "turbo run lint:fix",
|
||||||
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
|
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
"trigger:dev": "npm run with:env -- npx trigger-cli dev --handler-path=\"/api/jobs\"",
|
"trigger:dev": "npm run with:env -- npx trigger-cli dev --handler-path=\"/api/jobs\"",
|
||||||
"inngest:dev": "inngest dev -u http://localhost:3000/api/jobs",
|
"inngest:dev": "inngest dev -u http://localhost:3000/api/jobs",
|
||||||
"make:version": " npm version --workspace @documenso/web --workspace @documenso/marketing --include-workspace-root --no-git-tag-version -m \"v%s\"",
|
"make:version": " npm version --workspace @documenso/web --workspace @documenso/marketing --include-workspace-root --no-git-tag-version -m \"v%s\"",
|
||||||
"translate:extract": "lingui extract",
|
"translate:extract": "lingui extract --clean",
|
||||||
"translate:compile": "turbo run translate:compile --filter=@documenso/web --filter=@documenso/marketing --filter=@documenso/ui"
|
"translate:compile": "turbo run translate:compile --filter=@documenso/web --filter=@documenso/marketing --filter=@documenso/ui"
|
||||||
},
|
},
|
||||||
"packageManager": "npm@10.7.0",
|
"packageManager": "npm@10.7.0",
|
||||||
@@ -63,8 +64,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/pdf-sign": "^0.1.0",
|
"@documenso/pdf-sign": "^0.1.0",
|
||||||
|
"@documenso/prisma": "^0.0.0",
|
||||||
"@lingui/core": "^4.11.3",
|
"@lingui/core": "^4.11.3",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
|
"luxon": "^3.5.0",
|
||||||
"next-runtime-env": "^3.2.0",
|
"next-runtime-env": "^3.2.0",
|
||||||
"react": "^18"
|
"react": "^18"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,6 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"swagger-ui-react": "^5.11.0",
|
"swagger-ui-react": "^5.11.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ import { updateDocument } from '@documenso/lib/server-only/document/update-docum
|
|||||||
import { updateDocumentSettings } from '@documenso/lib/server-only/document/update-document-settings';
|
import { updateDocumentSettings } from '@documenso/lib/server-only/document/update-document-settings';
|
||||||
import { deleteField } from '@documenso/lib/server-only/field/delete-field';
|
import { deleteField } from '@documenso/lib/server-only/field/delete-field';
|
||||||
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
||||||
|
import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
|
||||||
import { updateField } from '@documenso/lib/server-only/field/update-field';
|
import { updateField } from '@documenso/lib/server-only/field/update-field';
|
||||||
import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf';
|
import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf';
|
||||||
import { deleteRecipient } from '@documenso/lib/server-only/recipient/delete-recipient';
|
import { deleteRecipient } from '@documenso/lib/server-only/recipient/delete-recipient';
|
||||||
@@ -87,7 +88,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
@@ -98,6 +99,30 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fields = await getFieldsForDocument({
|
||||||
|
documentId: Number(documentId),
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const parsedMetaFields = fields.map((field) => {
|
||||||
|
let parsedMetaOrNull = null;
|
||||||
|
|
||||||
|
if (field.fieldMeta) {
|
||||||
|
const result = ZFieldMetaSchema.safeParse(field.fieldMeta);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error('Field meta parsing failed for field ' + field.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedMetaOrNull = result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...field,
|
||||||
|
fieldMeta: parsedMetaOrNull,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
@@ -106,6 +131,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
...recipient,
|
...recipient,
|
||||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||||
})),
|
})),
|
||||||
|
fields: parsedMetaFields,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -132,7 +158,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
@@ -185,7 +211,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
@@ -401,7 +427,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
const perPage = Number(args.query.perPage) || 10;
|
const perPage = Number(args.query.perPage) || 10;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { templates, totalPages } = await findTemplates({
|
const { data: templates, totalPages } = await findTemplates({
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@@ -614,7 +640,11 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
const { id } = args.params;
|
const { id } = args.params;
|
||||||
const { sendEmail = true } = args.body ?? {};
|
const { sendEmail = true } = args.body ?? {};
|
||||||
|
|
||||||
const document = await getDocumentById({ id: Number(id), userId: user.id, teamId: team?.id });
|
const document = await getDocumentById({
|
||||||
|
documentId: Number(id),
|
||||||
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
return {
|
return {
|
||||||
@@ -731,7 +761,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
const { name, email, role, authOptions, signingOrder } = args.body;
|
const { name, email, role, authOptions, signingOrder } = args.body;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
@@ -822,7 +852,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
const { name, email, role, authOptions, signingOrder } = args.body;
|
const { name, email, role, authOptions, signingOrder } = args.body;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
@@ -881,7 +911,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
const { id: documentId, recipientId } = args.params;
|
const { id: documentId, recipientId } = args.params;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
@@ -1108,7 +1138,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
args.body;
|
args.body;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
@@ -1197,7 +1227,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
const { id: documentId, fieldId } = args.params;
|
const { id: documentId, fieldId } = args.params;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
id: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,22 @@ export const ZSuccessfulDocumentResponseSchema = z.object({
|
|||||||
|
|
||||||
export const ZSuccessfulGetDocumentResponseSchema = ZSuccessfulDocumentResponseSchema.extend({
|
export const ZSuccessfulGetDocumentResponseSchema = ZSuccessfulDocumentResponseSchema.extend({
|
||||||
recipients: z.lazy(() => z.array(ZSuccessfulRecipientResponseSchema)),
|
recipients: z.lazy(() => z.array(ZSuccessfulRecipientResponseSchema)),
|
||||||
|
fields: z.lazy(() =>
|
||||||
|
ZFieldSchema.pick({
|
||||||
|
id: true,
|
||||||
|
recipientId: true,
|
||||||
|
type: true,
|
||||||
|
page: true,
|
||||||
|
positionX: true,
|
||||||
|
positionY: true,
|
||||||
|
width: true,
|
||||||
|
height: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
fieldMeta: ZFieldMetaSchema.nullish(),
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuccessfulGetDocumentResponseSchema = z.infer<
|
export type TSuccessfulGetDocumentResponseSchema = z.infer<
|
||||||
@@ -145,7 +161,10 @@ export const ZCreateDocumentMutationSchema = z.object({
|
|||||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
||||||
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional()
|
||||||
|
.openapi({
|
||||||
|
description: 'The globalActionAuth property is only available for Enterprise accounts.',
|
||||||
|
}),
|
||||||
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,7 +328,10 @@ export const ZCreateRecipientMutationSchema = z.object({
|
|||||||
.object({
|
.object({
|
||||||
actionAuth: ZRecipientActionAuthTypesSchema.optional(),
|
actionAuth: ZRecipientActionAuthTypesSchema.optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional()
|
||||||
|
.openapi({
|
||||||
|
description: 'The authOptions property is only available for Enterprise accounts.',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -424,7 +446,7 @@ export const ZSuccessfulSigningResponseSchema = z
|
|||||||
.object({
|
.object({
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
})
|
})
|
||||||
.and(ZSuccessfulGetDocumentResponseSchema);
|
.and(ZSuccessfulGetDocumentResponseSchema.omit({ fields: true }));
|
||||||
|
|
||||||
export type TSuccessfulSigningResponseSchema = z.infer<typeof ZSuccessfulSigningResponseSchema>;
|
export type TSuccessfulSigningResponseSchema = z.infer<typeof ZSuccessfulSigningResponseSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ test('[DOCUMENT_AUTH]: should allow signing when no auth setup', async ({ page }
|
|||||||
const canvas = page.locator('canvas').first();
|
const canvas = page.locator('canvas').first();
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,9 +96,9 @@ test('[DOCUMENT_AUTH]: should allow signing with valid global auth', async ({ pa
|
|||||||
const canvas = page.locator('canvas').first();
|
const canvas = page.locator('canvas').first();
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,9 +265,9 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
|
|||||||
const canvas = page.locator('canvas').first();
|
const canvas = page.locator('canvas').first();
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,9 +376,9 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient an
|
|||||||
const canvas = page.locator('canvas').first();
|
const canvas = page.locator('canvas').first();
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document', async ({ page }) =>
|
|||||||
|
|
||||||
// Add subject and send
|
// Add subject and send
|
||||||
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
await page.getByRole('button', { name: 'Send' }).click();
|
await page.getByRole('button', { name: 'Send' }).click();
|
||||||
|
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
@@ -191,6 +192,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
|
|||||||
|
|
||||||
// Add subject and send
|
// Add subject and send
|
||||||
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
await page.getByRole('button', { name: 'Send' }).click();
|
await page.getByRole('button', { name: 'Send' }).click();
|
||||||
|
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
@@ -288,6 +290,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
|
|||||||
|
|
||||||
// Add subject and send
|
// Add subject and send
|
||||||
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
await page.getByRole('button', { name: 'Send' }).click();
|
await page.getByRole('button', { name: 'Send' }).click();
|
||||||
|
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
@@ -369,9 +372,9 @@ test('[DOCUMENT_FLOW]: should be able to approve a document', async ({ page }) =
|
|||||||
const canvas = page.locator('canvas');
|
const canvas = page.locator('canvas');
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,6 +429,7 @@ test('[DOCUMENT_FLOW]: should be able to create, send with redirect url, sign a
|
|||||||
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
await page.getByRole('button', { name: 'Send' }).click();
|
await page.getByRole('button', { name: 'Send' }).click();
|
||||||
|
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
@@ -567,6 +571,7 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
|
|||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Distribute Document' })).toBeVisible();
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
await page.getByRole('button', { name: 'Send' }).click();
|
await page.getByRole('button', { name: 'Send' }).click();
|
||||||
|
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
@@ -608,9 +613,9 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
|
|||||||
const canvas = page.locator('canvas#signature');
|
const canvas = page.locator('canvas#signature');
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ test.describe('Signing Certificate Tests', () => {
|
|||||||
const canvas = page.locator('canvas');
|
const canvas = page.locator('canvas');
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +116,9 @@ test.describe('Signing Certificate Tests', () => {
|
|||||||
const canvas = page.locator('canvas');
|
const canvas = page.locator('canvas');
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,9 +193,9 @@ test.describe('Signing Certificate Tests', () => {
|
|||||||
const canvas = page.locator('canvas');
|
const canvas = page.locator('canvas');
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
|
|||||||
|
|
||||||
const canvas = page.locator('canvas').first();
|
const canvas = page.locator('canvas').first();
|
||||||
const box = await canvas.boundingBox();
|
const box = await canvas.boundingBox();
|
||||||
|
|
||||||
if (box) {
|
if (box) {
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
await page.mouse.move(box.x + 40, box.y + 40);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,7 @@ test('[USER] update full name', async ({ page }) => {
|
|||||||
|
|
||||||
await page.getByLabel('Full Name').fill('John Doe');
|
await page.getByLabel('Full Name').fill('John Doe');
|
||||||
|
|
||||||
const canvas = page.locator('canvas').first();
|
await page.getByPlaceholder('Type your signature').fill('John Doe');
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Update profile' }).click();
|
await page.getByRole('button', { name: 'Update profile' }).click();
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,6 @@
|
|||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,3 +46,10 @@ export const PASSKEY_TIMEOUT = 60000;
|
|||||||
* The maximum number of passkeys are user can have.
|
* The maximum number of passkeys are user can have.
|
||||||
*/
|
*/
|
||||||
export const MAXIMUM_PASSKEYS = 50;
|
export const MAXIMUM_PASSKEYS = 50;
|
||||||
|
|
||||||
|
export const useSecureCookies =
|
||||||
|
process.env.NODE_ENV === 'production' && String(process.env.NEXTAUTH_URL).startsWith('https://');
|
||||||
|
|
||||||
|
const secureCookiePrefix = useSecureCookies ? '__Secure-' : '';
|
||||||
|
|
||||||
|
export const formatSecureCookieName = (name: string) => `${secureCookiePrefix}${name}`;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { env } from 'next-runtime-env';
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { IdentityProvider, UserSecurityAuditLogType } from '@documenso/prisma/client';
|
import { IdentityProvider, UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { formatSecureCookieName, useSecureCookies } from '../constants/auth';
|
||||||
import { AppError, AppErrorCode } from '../errors/app-error';
|
import { AppError, AppErrorCode } from '../errors/app-error';
|
||||||
import { jobsClient } from '../jobs/client';
|
import { jobsClient } from '../jobs/client';
|
||||||
import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble';
|
import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble';
|
||||||
@@ -26,10 +27,6 @@ import { extractNextAuthRequestMetadata } from '../universal/extract-request-met
|
|||||||
import { getAuthenticatorOptions } from '../utils/authenticator';
|
import { getAuthenticatorOptions } from '../utils/authenticator';
|
||||||
import { ErrorCode } from './error-codes';
|
import { ErrorCode } from './error-codes';
|
||||||
|
|
||||||
const useSecureCookies =
|
|
||||||
process.env.NODE_ENV === 'production' && String(process.env.NEXTAUTH_URL).startsWith('https://');
|
|
||||||
const cookiePrefix = useSecureCookies ? '__Secure-' : '';
|
|
||||||
|
|
||||||
export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
secret: process.env.NEXTAUTH_SECRET ?? 'secret',
|
secret: process.env.NEXTAUTH_SECRET ?? 'secret',
|
||||||
@@ -437,7 +434,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
|||||||
},
|
},
|
||||||
cookies: {
|
cookies: {
|
||||||
sessionToken: {
|
sessionToken: {
|
||||||
name: `${cookiePrefix}next-auth.session-token`,
|
name: formatSecureCookieName('next-auth.session-token'),
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: useSecureCookies ? 'none' : 'lax',
|
sameSite: useSecureCookies ? 'none' : 'lax',
|
||||||
@@ -446,7 +443,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
callbackUrl: {
|
callbackUrl: {
|
||||||
name: `${cookiePrefix}next-auth.callback-url`,
|
name: formatSecureCookieName('next-auth.callback-url'),
|
||||||
options: {
|
options: {
|
||||||
sameSite: useSecureCookies ? 'none' : 'lax',
|
sameSite: useSecureCookies ? 'none' : 'lax',
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -456,7 +453,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
|||||||
csrfToken: {
|
csrfToken: {
|
||||||
// Default to __Host- for CSRF token for additional protection if using useSecureCookies
|
// Default to __Host- for CSRF token for additional protection if using useSecureCookies
|
||||||
// NB: The `__Host-` prefix is stricter than the `__Secure-` prefix.
|
// NB: The `__Host-` prefix is stricter than the `__Secure-` prefix.
|
||||||
name: `${cookiePrefix}next-auth.csrf-token`,
|
name: formatSecureCookieName('next-auth.csrf-token'),
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: useSecureCookies ? 'none' : 'lax',
|
sameSite: useSecureCookies ? 'none' : 'lax',
|
||||||
@@ -465,7 +462,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pkceCodeVerifier: {
|
pkceCodeVerifier: {
|
||||||
name: `${cookiePrefix}next-auth.pkce.code_verifier`,
|
name: formatSecureCookieName('next-auth.pkce.code_verifier'),
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: useSecureCookies ? 'none' : 'lax',
|
sameSite: useSecureCookies ? 'none' : 'lax',
|
||||||
@@ -474,7 +471,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
name: `${cookiePrefix}next-auth.state`,
|
name: formatSecureCookieName('next-auth.state'),
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: useSecureCookies ? 'none' : 'lax',
|
sameSite: useSecureCookies ? 'none' : 'lax',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user