Compare commits
52 Commits
wip/rr7
...
feat/docum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b176ff884f | ||
|
|
667b8f1a85 | ||
|
|
00429038ac | ||
|
|
810d194cfd | ||
|
|
6743a108c0 | ||
|
|
d82a759acd | ||
|
|
edfb1f2157 | ||
|
|
7cc85ca6bc | ||
|
|
bc19fa0cbd | ||
|
|
a60f58e20b | ||
|
|
aca902b5ff | ||
|
|
2f866c41b4 | ||
|
|
7e4faef95f | ||
|
|
bcef84787d | ||
|
|
70a3ac0525 | ||
|
|
c6fb101a99 | ||
|
|
2984af769c | ||
|
|
9183f668d3 | ||
|
|
54ea96391a | ||
|
|
42d24fd1a1 | ||
|
|
dc36a8182c | ||
|
|
0ef85b47b1 | ||
|
|
058d9dd0ba | ||
|
|
74bb230247 | ||
|
|
7c1e0f34e8 | ||
|
|
7e31323faa | ||
|
|
a28cdf437b | ||
|
|
80dfbeb16f | ||
|
|
9de3a32ceb | ||
|
|
0d3864548c | ||
|
|
9e03747e43 | ||
|
|
5750f2b477 | ||
|
|
901be70f97 | ||
|
|
7d0a9c6439 | ||
|
|
48b55758e3 | ||
|
|
dcaccb65f2 | ||
|
|
723e1b4ea2 | ||
|
|
08a69c6168 | ||
|
|
948d9c24cf | ||
|
|
ebbe922982 | ||
|
|
6520bbd5e3 | ||
|
|
4e197ac24c | ||
|
|
04e5244a5a | ||
|
|
e367894218 | ||
|
|
f707e5fb10 | ||
|
|
6fc5e565d0 | ||
|
|
07c852744b | ||
|
|
99b0b80635 | ||
|
|
1b1cd0ba3d | ||
|
|
4fab98c633 | ||
|
|
1fe5cda0ed | ||
|
|
f82e71f11c |
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
2
.github/workflows/e2e-tests.yml
vendored
2
.github/workflows/e2e-tests.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
e2e_tests:
|
||||
name: 'E2E Tests'
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: warp-ubuntu-2204-x64-16x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"typescript": "^5"
|
||||
"typescript": "5.6.2"
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,6 @@
|
||||
"svelte": "Svelte Integration",
|
||||
"solid": "Solid Integration",
|
||||
"preact": "Preact Integration",
|
||||
"angular": "Angular Integration",
|
||||
"css-variables": "CSS Variables"
|
||||
}
|
||||
}
|
||||
|
||||
90
apps/documentation/pages/developers/embedding/angular.mdx
Normal file
90
apps/documentation/pages/developers/embedding/angular.mdx
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Angular Integration
|
||||
description: Learn how to use our embedding SDK within your Angular application.
|
||||
---
|
||||
|
||||
# Angular Integration
|
||||
|
||||
Our Angular SDK provides a simple way to embed a signing experience within your Angular application. It supports both direct link templates and signing tokens.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the SDK, run the following command:
|
||||
|
||||
```bash
|
||||
npm install @documenso/embed-angular
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To embed a signing experience, you'll need to provide the token for the document you want to embed. This can be done in a few different ways, depending on your use case.
|
||||
|
||||
### Direct Link Template
|
||||
|
||||
If you have a direct link template, you can simply provide the token for the template to the `EmbedDirectTemplate` component.
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { EmbedDirectTemplate } from '@documenso/embed-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-embedding',
|
||||
template: `
|
||||
<embed-direct-template [token]="token" />
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [EmbedDirectTemplate],
|
||||
})
|
||||
export class EmbeddingComponent {
|
||||
token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
}
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| email | string (optional) | The email the signer that will be used by default for signing |
|
||||
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
| onFieldSigned | function (optional) | A callback function that will be called when a field is signed |
|
||||
| onFieldUnsigned | function (optional) | A callback function that will be called when a field is unsigned |
|
||||
|
||||
### Signing Token
|
||||
|
||||
If you have a signing token, you can provide it to the `EmbedSignDocument` component.
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { EmbedSignDocument } from '@documenso/embed-angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-embedding',
|
||||
template: `
|
||||
<embed-sign-document [token]="token" />
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [EmbedSignDocument],
|
||||
})
|
||||
export class EmbeddingComponent {
|
||||
token = 'YOUR_TOKEN_HERE'; // Replace with the actual token
|
||||
}
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| token | string | The token for the document you want to embed |
|
||||
| host | string (optional) | The host to be used for the signing experience, relevant for self-hosters |
|
||||
| name | string (optional) | The name the signer that will be used by default for signing |
|
||||
| lockName | boolean (optional) | Whether or not the name field should be locked disallowing modifications |
|
||||
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
|
||||
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
|
||||
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
|
||||
@@ -5,7 +5,7 @@ description: Learn how to use embedding to bring signing to your own website or
|
||||
|
||||
# Embedding
|
||||
|
||||
Our embedding feature lets you integrate our document signing experience into your own application or website. Whether you're building with React, Preact, Vue, Svelte, Solid, or using generalized web components, this guide will help you get started with embedding Documenso.
|
||||
Our embedding feature lets you integrate our document signing experience into your own application or website. Whether you're building with React, Preact, Vue, Svelte, Solid, Angular, or using generalized web components, this guide will help you get started with embedding Documenso.
|
||||
|
||||
## Availability
|
||||
|
||||
@@ -73,13 +73,14 @@ These customization options are available for both Direct Templates and Signing
|
||||
|
||||
We support embedding across a range of popular JavaScript frameworks, including:
|
||||
|
||||
| Framework | Package |
|
||||
| --------- | -------------------------------------------------------------------------------- |
|
||||
| React | [@documenso/embed-react](https://www.npmjs.com/package/@documenso/embed-react) |
|
||||
| Preact | [@documenso/embed-preact](https://www.npmjs.com/package/@documenso/embed-preact) |
|
||||
| Vue | [@documenso/embed-vue](https://www.npmjs.com/package/@documenso/embed-vue) |
|
||||
| Svelte | [@documenso/embed-svelte](https://www.npmjs.com/package/@documenso/embed-svelte) |
|
||||
| Solid | [@documenso/embed-solid](https://www.npmjs.com/package/@documenso/embed-solid) |
|
||||
| Framework | Package |
|
||||
| --------- | ---------------------------------------------------------------------------------- |
|
||||
| React | [@documenso/embed-react](https://www.npmjs.com/package/@documenso/embed-react) |
|
||||
| Preact | [@documenso/embed-preact](https://www.npmjs.com/package/@documenso/embed-preact) |
|
||||
| Vue | [@documenso/embed-vue](https://www.npmjs.com/package/@documenso/embed-vue) |
|
||||
| Svelte | [@documenso/embed-svelte](https://www.npmjs.com/package/@documenso/embed-svelte) |
|
||||
| Solid | [@documenso/embed-solid](https://www.npmjs.com/package/@documenso/embed-solid) |
|
||||
| Angular | [@documenso/embed-angular](https://www.npmjs.com/package/@documenso/embed-angular) |
|
||||
|
||||
Additionally, we provide **web components** for more generalized use. However, please note that web components are still in their early stages and haven't been extensively tested.
|
||||
|
||||
@@ -127,7 +128,7 @@ This will show a dialog which will ask you to configure which recipient should b
|
||||
|
||||
## Embedding with Signing Tokens
|
||||
|
||||
To embed the signing process for an ordinary document, you’ll need a **document signing token** for the recipient. This token provides the necessary access to load the document and facilitate the signing process securely.
|
||||
To embed the signing process for an ordinary document, you'll need a **document signing token** for the recipient. This token provides the necessary access to load the document and facilitate the signing process securely.
|
||||
|
||||
#### Instructions
|
||||
|
||||
@@ -164,6 +165,7 @@ Once you've obtained the appropriate tokens, you can integrate the signing exper
|
||||
- [Vue](/developers/embedding/vue)
|
||||
- [Svelte](/developers/embedding/svelte)
|
||||
- [Solid](/developers/embedding/solid)
|
||||
- [Angular](/developers/embedding/angular)
|
||||
|
||||
If you're using **web components**, the integration process is slightly different. Keep in mind that web components are currently less tested but can still provide flexibility for general use cases.
|
||||
|
||||
@@ -174,4 +176,5 @@ If you're using **web components**, the integration process is slightly differen
|
||||
- [Svelte Integration](/developers/embedding/svelte)
|
||||
- [Solid Integration](/developers/embedding/solid)
|
||||
- [Preact Integration](/developers/embedding/preact)
|
||||
- [Angular Integration](/developers/embedding/angular)
|
||||
- [CSS Variables](/developers/embedding/css-variables)
|
||||
|
||||
@@ -21,6 +21,7 @@ Documenso supports Webhooks and allows you to subscribe to the following events:
|
||||
- `document.signed`
|
||||
- `document.completed`
|
||||
- `document.rejected`
|
||||
- `document.cancelled`
|
||||
|
||||
## Create a webhook subscription
|
||||
|
||||
@@ -37,7 +38,7 @@ Clicking on the "**Create Webhook**" button opens a modal to create a new webhoo
|
||||
To create a new webhook subscription, you need to provide the following information:
|
||||
|
||||
- Enter the webhook URL that will receive the event payload.
|
||||
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`, `document.rejected`.
|
||||
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`, `document.rejected`, `document.cancelled`.
|
||||
- Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Secret` header of the request.
|
||||
|
||||

|
||||
@@ -528,6 +529,96 @@ Example payload for the `document.rejected` event:
|
||||
}
|
||||
```
|
||||
|
||||
Example payload for the `document.rejected` event:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "DOCUMENT_CANCELLED",
|
||||
"payload": {
|
||||
"id": 7,
|
||||
"externalId": null,
|
||||
"userId": 3,
|
||||
"authOptions": null,
|
||||
"formValues": null,
|
||||
"visibility": "EVERYONE",
|
||||
"title": "documenso.pdf",
|
||||
"status": "PENDING",
|
||||
"documentDataId": "cm6exvn93006hi02ru90a265a",
|
||||
"createdAt": "2025-01-27T11:02:14.393Z",
|
||||
"updatedAt": "2025-01-27T11:03:16.387Z",
|
||||
"completedAt": null,
|
||||
"deletedAt": null,
|
||||
"teamId": null,
|
||||
"templateId": null,
|
||||
"source": "DOCUMENT",
|
||||
"documentMeta": {
|
||||
"id": "cm6exvn96006ji02rqvzjvwoy",
|
||||
"subject": "",
|
||||
"message": "",
|
||||
"timezone": "Etc/UTC",
|
||||
"password": null,
|
||||
"dateFormat": "yyyy-MM-dd hh:mm a",
|
||||
"redirectUrl": "",
|
||||
"signingOrder": "PARALLEL",
|
||||
"typedSignatureEnabled": true,
|
||||
"language": "en",
|
||||
"distributionMethod": "EMAIL",
|
||||
"emailSettings": {
|
||||
"documentDeleted": true,
|
||||
"documentPending": true,
|
||||
"recipientSigned": true,
|
||||
"recipientRemoved": true,
|
||||
"documentCompleted": true,
|
||||
"ownerDocumentCompleted": true,
|
||||
"recipientSigningRequest": true
|
||||
}
|
||||
},
|
||||
"recipients": [
|
||||
{
|
||||
"id": 7,
|
||||
"documentId": 7,
|
||||
"templateId": null,
|
||||
"email": "mybirihix@mailinator.com",
|
||||
"name": "Zorita Baird",
|
||||
"token": "XkKx1HCs6Znm2UBJA2j6o",
|
||||
"documentDeletedAt": null,
|
||||
"expired": null,
|
||||
"signedAt": null,
|
||||
"authOptions": { "accessAuth": null, "actionAuth": null },
|
||||
"signingOrder": 1,
|
||||
"rejectionReason": null,
|
||||
"role": "SIGNER",
|
||||
"readStatus": "NOT_OPENED",
|
||||
"signingStatus": "NOT_SIGNED",
|
||||
"sendStatus": "SENT"
|
||||
}
|
||||
],
|
||||
"Recipient": [
|
||||
{
|
||||
"id": 7,
|
||||
"documentId": 7,
|
||||
"templateId": null,
|
||||
"email": "signer@documenso.com",
|
||||
"name": "Signer",
|
||||
"token": "XkKx1HCs6Znm2UBJA2j6o",
|
||||
"documentDeletedAt": null,
|
||||
"expired": null,
|
||||
"signedAt": null,
|
||||
"authOptions": { "accessAuth": null, "actionAuth": null },
|
||||
"signingOrder": 1,
|
||||
"rejectionReason": null,
|
||||
"role": "SIGNER",
|
||||
"readStatus": "NOT_OPENED",
|
||||
"signingStatus": "NOT_SIGNED",
|
||||
"sendStatus": "SENT"
|
||||
}
|
||||
]
|
||||
},
|
||||
"createdAt": "2025-01-27T11:03:27.730Z",
|
||||
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
|
||||
}
|
||||
```
|
||||
|
||||
## Availability
|
||||
|
||||
Webhooks are available to individual users and teams.
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"next": "14.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.16.5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "18.3.5",
|
||||
"typescript": "5.5.4"
|
||||
"typescript": "5.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
.react-router
|
||||
build
|
||||
node_modules
|
||||
README.md
|
||||
9
apps/remix/.gitignore
vendored
9
apps/remix/.gitignore
vendored
@@ -1,9 +0,0 @@
|
||||
.DS_Store
|
||||
/node_modules/
|
||||
|
||||
# React Router
|
||||
/.react-router/
|
||||
/build/
|
||||
|
||||
# Vite
|
||||
vite.config.*.timestamp*
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM node:20-alpine AS development-dependencies-env
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN npm ci
|
||||
|
||||
FROM node:20-alpine AS production-dependencies-env
|
||||
COPY ./package.json package-lock.json /app/
|
||||
WORKDIR /app
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
FROM node:20-alpine AS build-env
|
||||
COPY . /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
COPY ./package.json package-lock.json /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -1,25 +0,0 @@
|
||||
FROM oven/bun:1 AS dependencies-env
|
||||
COPY . /app
|
||||
|
||||
FROM dependencies-env AS development-dependencies-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
WORKDIR /app
|
||||
RUN bun i --frozen-lockfile
|
||||
|
||||
FROM dependencies-env AS production-dependencies-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
WORKDIR /app
|
||||
RUN bun i --production
|
||||
|
||||
FROM dependencies-env AS build-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN bun run build
|
||||
|
||||
FROM dependencies-env
|
||||
COPY ./package.json bun.lockb /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["bun", "run", "start"]
|
||||
@@ -1,26 +0,0 @@
|
||||
FROM node:20-alpine AS dependencies-env
|
||||
RUN npm i -g pnpm
|
||||
COPY . /app
|
||||
|
||||
FROM dependencies-env AS development-dependencies-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
WORKDIR /app
|
||||
RUN pnpm i --frozen-lockfile
|
||||
|
||||
FROM dependencies-env AS production-dependencies-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
WORKDIR /app
|
||||
RUN pnpm i --prod --frozen-lockfile
|
||||
|
||||
FROM dependencies-env AS build-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN pnpm build
|
||||
|
||||
FROM dependencies-env
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["pnpm", "start"]
|
||||
@@ -1,100 +0,0 @@
|
||||
# Welcome to React Router!
|
||||
|
||||
A modern, production-ready template for building full-stack React applications using React Router.
|
||||
|
||||
[](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 Server-side rendering
|
||||
- ⚡️ Hot Module Replacement (HMR)
|
||||
- 📦 Asset bundling and optimization
|
||||
- 🔄 Data loading and mutations
|
||||
- 🔒 TypeScript by default
|
||||
- 🎉 TailwindCSS for styling
|
||||
- 📖 [React Router docs](https://reactrouter.com/)
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
Start the development server with HMR:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Your application will be available at `http://localhost:5173`.
|
||||
|
||||
## Building for Production
|
||||
|
||||
Create a production build:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
This template includes three Dockerfiles optimized for different package managers:
|
||||
|
||||
- `Dockerfile` - for npm
|
||||
- `Dockerfile.pnpm` - for pnpm
|
||||
- `Dockerfile.bun` - for bun
|
||||
|
||||
To build and run using Docker:
|
||||
|
||||
```bash
|
||||
# For npm
|
||||
docker build -t my-app .
|
||||
|
||||
# For pnpm
|
||||
docker build -f Dockerfile.pnpm -t my-app .
|
||||
|
||||
# For bun
|
||||
docker build -f Dockerfile.bun -t my-app .
|
||||
|
||||
# Run the container
|
||||
docker run -p 3000:3000 my-app
|
||||
```
|
||||
|
||||
The containerized application can be deployed to any platform that supports Docker, including:
|
||||
|
||||
- AWS ECS
|
||||
- Google Cloud Run
|
||||
- Azure Container Apps
|
||||
- Digital Ocean App Platform
|
||||
- Fly.io
|
||||
- Railway
|
||||
|
||||
### DIY Deployment
|
||||
|
||||
If you're familiar with deploying Node applications, the built-in app server is production-ready.
|
||||
|
||||
Make sure to deploy the output of `npm run build`
|
||||
|
||||
```
|
||||
├── package.json
|
||||
├── package-lock.json (or pnpm-lock.yaml, or bun.lockb)
|
||||
├── build/
|
||||
│ ├── client/ # Static assets
|
||||
│ └── server/ # Server-side code
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.
|
||||
|
||||
---
|
||||
|
||||
Built with ❤️ using React Router.
|
||||
@@ -1 +0,0 @@
|
||||
@import '@documenso/ui/styles/theme.css';
|
||||
@@ -1,74 +0,0 @@
|
||||
import {
|
||||
Links,
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
isRouteErrorResponse,
|
||||
} from 'react-router';
|
||||
|
||||
import type { Route } from './+types/root';
|
||||
import stylesheet from './app.css?url';
|
||||
|
||||
export const links: Route.LinksFunction = () => [
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{
|
||||
rel: 'preconnect',
|
||||
href: 'https://fonts.gstatic.com',
|
||||
crossOrigin: 'anonymous',
|
||||
},
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
|
||||
},
|
||||
{ rel: 'stylesheet', href: stylesheet },
|
||||
];
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
let message = 'Oops!';
|
||||
let details = 'An unexpected error occurred.';
|
||||
let stack: string | undefined;
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
message = error.status === 404 ? '404' : 'Error';
|
||||
details =
|
||||
error.status === 404 ? 'The requested page could not be found.' : error.statusText || details;
|
||||
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
||||
details = error.message;
|
||||
stack = error.stack;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="container mx-auto p-4 pt-16">
|
||||
<h1>{message}</h1>
|
||||
<p>{details}</p>
|
||||
{stack && (
|
||||
<pre className="w-full overflow-x-auto p-4">
|
||||
<code>{stack}</code>
|
||||
</pre>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { type RouteConfig, index } from '@react-router/dev/routes';
|
||||
|
||||
export default [index('routes/home.tsx')] satisfies RouteConfig;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Welcome } from '../welcome/welcome';
|
||||
import type { Route } from './+types/home';
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: 'New React Router App' },
|
||||
{ name: 'description', content: 'Welcome to React Router!' },
|
||||
];
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return <Welcome />;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<svg width="1080" height="174" viewBox="0 0 1080 174" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M231.527 86.9999C231.527 94.9642 228.297 102.173 223.067 107.387C217.837 112.606 210.614 115.835 202.634 115.835C194.654 115.835 187.43 119.059 182.206 124.278C176.977 129.498 173.741 136.707 173.741 144.671C173.741 152.635 170.51 159.844 165.281 165.058C160.051 170.277 152.828 173.507 144.847 173.507C136.867 173.507 129.644 170.277 124.42 165.058C119.19 159.844 115.954 152.635 115.954 144.671C115.954 136.707 119.19 129.498 124.42 124.278C129.644 119.059 136.867 115.835 144.847 115.835C152.828 115.835 160.051 112.606 165.281 107.387C170.51 102.173 173.741 94.9642 173.741 86.9999C173.741 71.0711 160.808 58.1643 144.847 58.1643C136.867 58.1643 129.644 54.9347 124.42 49.7155C119.19 44.502 115.954 37.2931 115.954 29.3287C115.954 21.3643 119.19 14.1555 124.42 8.93622C129.644 3.71698 136.867 0.493164 144.847 0.493164C160.808 0.493164 173.741 13.4 173.741 29.3287C173.741 37.2931 176.977 44.502 182.206 49.7155C187.43 54.9347 194.654 58.1643 202.634 58.1643C218.594 58.1643 231.527 71.0711 231.527 86.9999Z" fill="#F44250"/>
|
||||
<path d="M115.954 86.9996C115.954 71.0742 103.018 58.1641 87.061 58.1641C71.1037 58.1641 58.1677 71.0742 58.1677 86.9996C58.1677 102.925 71.1037 115.835 87.061 115.835C103.018 115.835 115.954 102.925 115.954 86.9996Z" fill="white"/>
|
||||
<path d="M58.1676 144.671C58.1676 128.745 45.2316 115.835 29.2743 115.835C13.317 115.835 0.381104 128.745 0.381104 144.671C0.381104 160.596 13.317 173.506 29.2743 173.506C45.2316 173.506 58.1676 160.596 58.1676 144.671Z" fill="white"/>
|
||||
<path d="M289.314 144.671C289.314 128.745 276.378 115.835 260.42 115.835C244.463 115.835 231.527 128.745 231.527 144.671C231.527 160.596 244.463 173.506 260.42 173.506C276.378 173.506 289.314 160.596 289.314 144.671Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_202_2131)">
|
||||
<path d="M562.482 173.247C524.388 173.247 498.363 147.49 498.363 110.468C498.363 73.4455 524.388 47.6885 562.482 47.6885C600.576 47.6885 626.869 73.7135 626.869 110.468C626.869 147.222 600.576 173.247 562.482 173.247ZM562.482 144.007C579.385 144.007 587.703 130.319 587.703 110.468C587.703 90.6168 579.385 76.9289 562.482 76.9289C545.579 76.9289 537.529 90.6168 537.529 110.468C537.529 130.319 545.311 144.007 562.482 144.007Z" fill="white"/>
|
||||
<path d="M833.64 141.116C824.217 141.116 819.237 136.684 819.237 126.156V74.8983H851.928V47.7792H819.237V1.15527H791.75L786.1 26.1978C783.343 36.4805 780.82 42.822 773.897 46.0821C773.105 46.4506 771.129 46.9976 769.409 47.3884C768.014 47.701 766.596 47.8573 765.167 47.8573H752.338V47.9243H734.832C723.578 47.9243 714.445 57.0459 714.445 68.3111V111.552C714.445 130.599 707.199 142.668 692.719 142.668C678.238 142.668 672.868 133.279 672.868 116.375V47.9243H634.249V125.765C634.249 151.254 644.442 173.248 676.63 173.248C691.915 173.248 703.895 167.231 711.096 157.182C712.145 155.72 714.445 156.49 714.445 158.276V170.022H753.332V83.8412C753.332 78.8953 757.34 74.8871 762.286 74.8871H779.882V136.952C779.882 164.663 797.89 173.248 817.842 173.248C833.908 173.248 844.436 169.374 853.58 162.441V136.126C846.1 139.453 839.725 141.116 833.629 141.116H833.64Z" fill="white"/>
|
||||
<path d="M981.561 130.865C975.387 157.962 954.197 173.258 923.07 173.258C885.243 173.258 858.415 150.18 858.415 112.354C858.415 74.5281 885.779 47.6992 922.266 47.6992C961.699 47.6992 982.365 74.796 982.365 107.263V113.884H896.509C894.555 135.711 909.382 144.017 924.409 144.017C937.829 144.017 946.136 138.915 950.434 127.918L981.561 130.865ZM945.075 94.9372C944.271 83.1361 936.757 75.8567 921.998 75.8567C906.434 75.8567 899.188 82.321 897.045 94.9372H945.064H945.075Z" fill="white"/>
|
||||
<path d="M1076.24 85.7486C1070.06 82.2652 1064.17 80.9142 1055.85 80.9142C1039.75 80.9142 1029.02 90.0358 1029.02 110.691V170.02H990.393V47.9225H1029.02V64.3235C1029.02 65.4623 1030.54 65.8195 1031.05 64.8035C1036.68 53.5718 1047.91 44.707 1062.03 44.707C1069.27 44.707 1075.45 46.8507 1078.66 49.5414L1076.25 85.7597L1076.24 85.7486Z" fill="white"/>
|
||||
<path d="M547.32 31.5345V23.9983H522.457V31.5345H515.378V2.23828H542.14C553.562 2.23828 554.365 2.95282 554.365 13.1239C554.365 17.4111 553.472 18.5611 551.329 19.6553L549.408 20.6378L551.317 21.6426C553.595 22.8372 554.365 23.2391 554.365 30.0273V31.5345H547.332H547.32ZM522.457 18.3601H547.32V7.88763H522.457V18.349V18.3601Z" fill="white"/>
|
||||
<path d="M578.493 2.23828H610.826V7.90996H580.067V14.5083H610.011V19.2868H580.067V25.8963H610.837V31.501L578.504 31.5345C575.344 31.5345 572.787 28.9778 572.787 25.8293V7.95462C572.787 4.80617 575.344 2.24945 578.493 2.24945V2.23828Z" fill="white"/>
|
||||
<path d="M655.562 31.5345L653.151 26.3429H633.746L631.335 31.5345H624.58L637.006 4.75034C637.71 3.22078 639.262 2.23828 640.936 2.23828H645.927C647.613 2.23828 649.154 3.22078 649.857 4.75034L662.283 31.5345H655.529H655.562ZM643.46 8.06627C642.712 8.06627 642.053 8.49053 641.729 9.17158L635.968 21.5756H650.94L645.19 9.17158C644.878 8.49053 644.208 8.06627 643.46 8.06627Z" fill="white"/>
|
||||
<path d="M694.862 32.4153C676.05 32.4153 675.313 32.4153 675.313 16.8852C675.313 1.35505 676.05 1.36621 694.862 1.36621C711.721 1.36621 713.764 2.06959 714.244 10.5325H707.333V7.01556H682.168V26.766H707.333V23.2714H714.244C713.775 31.7119 711.721 32.4153 694.862 32.4153Z" fill="white"/>
|
||||
<path d="M745.282 31.5345V7.02795H729.16V2.23828H768.147V7.02795H752.025V31.5345H745.282Z" fill="white"/>
|
||||
<path d="M454.419 169.819C450.935 165.264 448.792 154.814 447.452 137.397C446.112 118.104 437.806 113.817 422.532 113.817H392.254V169.83H347.494V0.986328H432.715C476.391 0.986328 498.106 21.6187 498.106 54.5882C498.106 79.2399 482.833 95.3171 462.201 98.0078C479.618 101.491 489.8 111.405 491.675 130.966C494.087 156.154 494.891 163.656 500.518 169.819H454.419ZM424.676 78.704C443.969 78.704 453.615 73.8808 453.615 58.3395C453.615 44.6739 443.969 37.4392 424.676 37.4392H392.254V78.7152H424.676V78.704Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_202_2131">
|
||||
<rect width="731.156" height="172.261" fill="white" transform="translate(347.494 0.986328)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.0 KiB |
@@ -1,23 +0,0 @@
|
||||
<svg width="1080" height="174" viewBox="0 0 1080 174" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M231.527 86.9999C231.527 94.9642 228.297 102.173 223.067 107.387C217.837 112.606 210.614 115.835 202.634 115.835C194.654 115.835 187.43 119.059 182.206 124.278C176.977 129.498 173.741 136.707 173.741 144.671C173.741 152.635 170.51 159.844 165.281 165.058C160.051 170.277 152.828 173.507 144.847 173.507C136.867 173.507 129.644 170.277 124.42 165.058C119.19 159.844 115.954 152.635 115.954 144.671C115.954 136.707 119.19 129.498 124.42 124.278C129.644 119.059 136.867 115.835 144.847 115.835C152.828 115.835 160.051 112.606 165.281 107.387C170.51 102.173 173.741 94.9642 173.741 86.9999C173.741 71.0711 160.808 58.1643 144.847 58.1643C136.867 58.1643 129.644 54.9347 124.42 49.7155C119.19 44.502 115.954 37.2931 115.954 29.3287C115.954 21.3643 119.19 14.1555 124.42 8.93622C129.644 3.71698 136.867 0.493164 144.847 0.493164C160.808 0.493164 173.741 13.4 173.741 29.3287C173.741 37.2931 176.977 44.502 182.206 49.7155C187.43 54.9347 194.654 58.1643 202.634 58.1643C218.594 58.1643 231.527 71.0711 231.527 86.9999Z" fill="#F44250"/>
|
||||
<path d="M115.954 86.9996C115.954 71.0742 103.018 58.1641 87.0608 58.1641C71.1035 58.1641 58.1676 71.0742 58.1676 86.9996C58.1676 102.925 71.1035 115.835 87.0608 115.835C103.018 115.835 115.954 102.925 115.954 86.9996Z" fill="#121212"/>
|
||||
<path d="M58.1676 144.671C58.1676 128.745 45.2316 115.835 29.2743 115.835C13.317 115.835 0.381104 128.745 0.381104 144.671C0.381104 160.596 13.317 173.506 29.2743 173.506C45.2316 173.506 58.1676 160.596 58.1676 144.671Z" fill="#121212"/>
|
||||
<path d="M289.313 144.671C289.313 128.745 276.378 115.835 260.42 115.835C244.463 115.835 231.527 128.745 231.527 144.671C231.527 160.596 244.463 173.506 260.42 173.506C276.378 173.506 289.313 160.596 289.313 144.671Z" fill="#121212"/>
|
||||
<g clip-path="url(#clip0_171_1761)">
|
||||
<path d="M562.482 173.247C524.388 173.247 498.363 147.49 498.363 110.468C498.363 73.4455 524.388 47.6885 562.482 47.6885C600.576 47.6885 626.869 73.7135 626.869 110.468C626.869 147.222 600.576 173.247 562.482 173.247ZM562.482 144.007C579.386 144.007 587.703 130.319 587.703 110.468C587.703 90.6168 579.386 76.9289 562.482 76.9289C545.579 76.9289 537.529 90.6168 537.529 110.468C537.529 130.319 545.311 144.007 562.482 144.007Z" fill="#121212"/>
|
||||
<path d="M833.64 141.116C824.217 141.116 819.237 136.684 819.237 126.156V74.8983H851.928V47.7792H819.237V1.15527H791.75L786.1 26.1978C783.343 36.4805 780.82 42.822 773.897 46.0821C773.105 46.4506 771.129 46.9976 769.409 47.3884C768.014 47.701 766.596 47.8573 765.167 47.8573H752.338V47.9243H734.832C723.578 47.9243 714.445 57.0459 714.445 68.3111V111.552C714.445 130.599 707.199 142.668 692.719 142.668C678.238 142.668 672.868 133.279 672.868 116.375V47.9243H634.249V125.765C634.249 151.254 644.442 173.248 676.63 173.248C691.915 173.248 703.895 167.231 711.096 157.182C712.145 155.72 714.445 156.49 714.445 158.276V170.022H753.332V83.8412C753.332 78.8953 757.34 74.8871 762.286 74.8871H779.882V136.952C779.882 164.663 797.89 173.248 817.842 173.248C833.908 173.248 844.436 169.374 853.58 162.441V136.126C846.1 139.453 839.725 141.116 833.629 141.116H833.64Z" fill="#121212"/>
|
||||
<path d="M981.561 130.865C975.387 157.962 954.197 173.258 923.07 173.258C885.243 173.258 858.415 150.18 858.415 112.354C858.415 74.5281 885.779 47.6992 922.266 47.6992C961.699 47.6992 982.365 74.796 982.365 107.263V113.884H896.509C894.555 135.711 909.382 144.017 924.409 144.017C937.829 144.017 946.136 138.915 950.434 127.918L981.561 130.865ZM945.075 94.9372C944.271 83.1361 936.757 75.8567 921.998 75.8567C906.434 75.8567 899.188 82.321 897.045 94.9372H945.064H945.075Z" fill="#121212"/>
|
||||
<path d="M1076.24 85.7486C1070.06 82.2652 1064.17 80.9142 1055.85 80.9142C1039.75 80.9142 1029.02 90.0358 1029.02 110.691V170.02H990.393V47.9225H1029.02V64.3235C1029.02 65.4623 1030.54 65.8195 1031.05 64.8035C1036.68 53.5718 1047.91 44.707 1062.03 44.707C1069.27 44.707 1075.45 46.8507 1078.66 49.5414L1076.25 85.7597L1076.24 85.7486Z" fill="#121212"/>
|
||||
<path d="M547.321 31.5345V23.9983H522.457V31.5345H515.378V2.23828H542.14C553.562 2.23828 554.366 2.95282 554.366 13.1239C554.366 17.4111 553.472 18.5611 551.329 19.6553L549.408 20.6378L551.318 21.6426C553.595 22.8372 554.366 23.2391 554.366 30.0273V31.5345H547.332H547.321ZM522.457 18.3601H547.321V7.88763H522.457V18.349V18.3601Z" fill="#121212"/>
|
||||
<path d="M578.493 2.23828H610.826V7.90996H580.067V14.5083H610.011V19.2868H580.067V25.8963H610.837V31.501L578.504 31.5345C575.344 31.5345 572.787 28.9778 572.787 25.8293V7.95462C572.787 4.80617 575.344 2.24945 578.493 2.24945V2.23828Z" fill="#121212"/>
|
||||
<path d="M655.562 31.5345L653.151 26.3429H633.747L631.335 31.5345H624.58L637.007 4.75034C637.71 3.22078 639.262 2.23828 640.937 2.23828H645.927C647.613 2.23828 649.154 3.22078 649.857 4.75034L662.284 31.5345H655.529H655.562ZM643.46 8.06627C642.712 8.06627 642.053 8.49053 641.729 9.17158L635.968 21.5756H650.94L645.19 9.17158C644.878 8.49053 644.208 8.06627 643.46 8.06627Z" fill="#121212"/>
|
||||
<path d="M694.862 32.4153C676.05 32.4153 675.313 32.4153 675.313 16.8852C675.313 1.35505 676.05 1.36621 694.862 1.36621C711.721 1.36621 713.764 2.06959 714.244 10.5325H707.333V7.01556H682.168V26.766H707.333V23.2714H714.244C713.775 31.7119 711.721 32.4153 694.862 32.4153Z" fill="#121212"/>
|
||||
<path d="M745.282 31.5345V7.02795H729.16V2.23828H768.148V7.02795H752.026V31.5345H745.282Z" fill="#121212"/>
|
||||
<path d="M454.419 169.819C450.935 165.264 448.792 154.814 447.452 137.397C446.112 118.104 437.806 113.817 422.532 113.817H392.254V169.83H347.494V0.986328H432.715C476.391 0.986328 498.106 21.6187 498.106 54.5882C498.106 79.2399 482.833 95.3171 462.201 98.0078C479.618 101.491 489.8 111.405 491.676 130.966C494.087 156.154 494.891 163.656 500.518 169.819H454.419ZM424.676 78.704C443.969 78.704 453.615 73.8808 453.615 58.3395C453.615 44.6739 443.969 37.4392 424.676 37.4392H392.254V78.7152H424.676V78.704Z" fill="#121212"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_171_1761">
|
||||
<rect width="731.156" height="172.261" fill="white" transform="translate(347.494 0.986328)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.0 KiB |
@@ -1,81 +0,0 @@
|
||||
import logoDark from './logo-dark.svg';
|
||||
import logoLight from './logo-light.svg';
|
||||
|
||||
export function Welcome() {
|
||||
return (
|
||||
<main className="flex items-center justify-center pb-4 pt-16">
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center gap-16">
|
||||
<header className="flex flex-col items-center gap-9">
|
||||
<div className="w-[500px] max-w-[100vw] p-4">
|
||||
<img src={logoLight} alt="React Router" className="block w-full dark:hidden" />
|
||||
<img src={logoDark} alt="React Router" className="hidden w-full dark:block" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="w-full max-w-[300px] space-y-6 px-4">
|
||||
<nav className="space-y-4 rounded-3xl border border-gray-200 p-6 dark:border-gray-700">
|
||||
<p className="text-center leading-6 text-gray-700 dark:text-gray-200">
|
||||
What's next?
|
||||
</p>
|
||||
<ul>
|
||||
{resources.map(({ href, text, icon }) => (
|
||||
<li key={href}>
|
||||
<a
|
||||
className="group flex items-center gap-3 self-stretch p-3 leading-normal text-blue-700 hover:underline dark:text-blue-500"
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{icon}
|
||||
{text}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const resources = [
|
||||
{
|
||||
href: 'https://reactrouter.com/docs',
|
||||
text: 'React Router Docs',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
|
||||
>
|
||||
<path
|
||||
d="M9.99981 10.0751V9.99992M17.4688 17.4688C15.889 19.0485 11.2645 16.9853 7.13958 12.8604C3.01467 8.73546 0.951405 4.11091 2.53116 2.53116C4.11091 0.951405 8.73546 3.01467 12.8604 7.13958C16.9853 11.2645 19.0485 15.889 17.4688 17.4688ZM2.53132 17.4688C0.951566 15.8891 3.01483 11.2645 7.13974 7.13963C11.2647 3.01471 15.8892 0.951453 17.469 2.53121C19.0487 4.11096 16.9854 8.73551 12.8605 12.8604C8.73562 16.9853 4.11107 19.0486 2.53132 17.4688Z"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: 'https://rmx.as/discord',
|
||||
text: 'Join Discord',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="20"
|
||||
viewBox="0 0 24 20"
|
||||
fill="none"
|
||||
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
|
||||
>
|
||||
<path
|
||||
d="M15.0686 1.25995L14.5477 1.17423L14.2913 1.63578C14.1754 1.84439 14.0545 2.08275 13.9422 2.31963C12.6461 2.16488 11.3406 2.16505 10.0445 2.32014C9.92822 2.08178 9.80478 1.84975 9.67412 1.62413L9.41449 1.17584L8.90333 1.25995C7.33547 1.51794 5.80717 1.99419 4.37748 2.66939L4.19 2.75793L4.07461 2.93019C1.23864 7.16437 0.46302 11.3053 0.838165 15.3924L0.868838 15.7266L1.13844 15.9264C2.81818 17.1714 4.68053 18.1233 6.68582 18.719L7.18892 18.8684L7.50166 18.4469C7.96179 17.8268 8.36504 17.1824 8.709 16.4944L8.71099 16.4904C10.8645 17.0471 13.128 17.0485 15.2821 16.4947C15.6261 17.1826 16.0293 17.8269 16.4892 18.4469L16.805 18.8725L17.3116 18.717C19.3056 18.105 21.1876 17.1751 22.8559 15.9238L23.1224 15.724L23.1528 15.3923C23.5873 10.6524 22.3579 6.53306 19.8947 2.90714L19.7759 2.73227L19.5833 2.64518C18.1437 1.99439 16.6386 1.51826 15.0686 1.25995ZM16.6074 10.7755L16.6074 10.7756C16.5934 11.6409 16.0212 12.1444 15.4783 12.1444C14.9297 12.1444 14.3493 11.6173 14.3493 10.7877C14.3493 9.94885 14.9378 9.41192 15.4783 9.41192C16.0471 9.41192 16.6209 9.93851 16.6074 10.7755ZM8.49373 12.1444C7.94513 12.1444 7.36471 11.6173 7.36471 10.7877C7.36471 9.94885 7.95323 9.41192 8.49373 9.41192C9.06038 9.41192 9.63892 9.93712 9.6417 10.7815C9.62517 11.6239 9.05462 12.1444 8.49373 12.1444Z"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "@documenso/remix",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production react-router build",
|
||||
"dev": "tsx watch --ignore \"vite.config.{ts,mts,js,mjs}*\" server/main.ts",
|
||||
"start": "cross-env NODE_ENV=production node dist/server/index.js",
|
||||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@epic-web/remember": "^1.1.0",
|
||||
"@hono/node-server": "^1.13.7",
|
||||
"@react-router/node": "^7.1.1",
|
||||
"@react-router/serve": "^7.1.1",
|
||||
"hono": "^4.6.15",
|
||||
"isbot": "^5.1.17",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-router": "^7.1.1",
|
||||
"remix-hono": "^0.0.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "^7.1.1",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"cross-env": "^7.0.3",
|
||||
"tsx": "^4.11.0",
|
||||
"typescript": "5.7.2",
|
||||
"vite": "^5.4.11",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,7 +0,0 @@
|
||||
import type { Config } from '@react-router/dev/config';
|
||||
|
||||
export default {
|
||||
appDirectory: 'app',
|
||||
// Server-side render by default, to enable SPA mode set this to `false`
|
||||
ssr: true,
|
||||
} satisfies Config;
|
||||
@@ -1,53 +0,0 @@
|
||||
import { remember } from '@epic-web/remember';
|
||||
import { type HttpBindings } from '@hono/node-server';
|
||||
import { Hono } from 'hono';
|
||||
import { reactRouter } from 'remix-hono/handler';
|
||||
|
||||
import { IS_APP_WEB } from '@documenso/lib/constants/app';
|
||||
|
||||
console.log({ IS_APP_WEB });
|
||||
|
||||
type Bindings = HttpBindings;
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
const viteDevServer = isProduction
|
||||
? undefined
|
||||
: import('vite').then(async (vite) =>
|
||||
vite.createServer({
|
||||
server: { middlewareMode: true },
|
||||
}),
|
||||
);
|
||||
|
||||
const reactRouterMiddleware = remember('reactRouterMiddleware', async () =>
|
||||
reactRouter({
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
build: isProduction
|
||||
? // @ts-expect-error build/server/index.js is a build artifact
|
||||
await import('../build/server/index.js')
|
||||
: async () => (await viteDevServer)!.ssrLoadModule('virtual:react-router/server-build'),
|
||||
}),
|
||||
);
|
||||
|
||||
export const getApp = async () => {
|
||||
const app = new Hono<{ Bindings: Bindings }>();
|
||||
|
||||
const resolvedDevServer = await viteDevServer;
|
||||
|
||||
// app.get('/', (c) => c.text('Hello, world!'));
|
||||
if (resolvedDevServer) {
|
||||
app.use('*', async (c, next) => {
|
||||
return new Promise((resolve) => {
|
||||
resolvedDevServer.middlewares(c.env.incoming, c.env.outgoing, () => resolve(next()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.use('*', async (c, next) => {
|
||||
const middleware = await reactRouterMiddleware;
|
||||
|
||||
return middleware(c, next);
|
||||
});
|
||||
|
||||
return app;
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import { serve } from '@hono/node-server';
|
||||
|
||||
import { getApp } from './app';
|
||||
|
||||
async function main() {
|
||||
const app = await getApp();
|
||||
|
||||
serve(app, (info) => {
|
||||
console.log(`Server is running on http://localhost:${info.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Failed to start server:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const baseConfig = require('@documenso/tailwind-config');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
content: [
|
||||
...baseConfig.content,
|
||||
'./app/**/*.{ts,tsx}',
|
||||
`${path.join(require.resolve('@documenso/ui'), '..')}/components/**/*.{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}`,
|
||||
],
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"include": [
|
||||
"**/*",
|
||||
"**/.server/**/*",
|
||||
"**/.client/**/*",
|
||||
".react-router/types/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ES2022"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
"vite/client"
|
||||
],
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"rootDirs": [
|
||||
".",
|
||||
"./.react-router/types"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"./app/*"
|
||||
]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"moduleDetection": "force",
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// @ts-expect-error This is just due to our root config, it's a non-issue
|
||||
import { reactRouter } from '@react-router/dev/vite';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [tailwindcss, autoprefixer],
|
||||
},
|
||||
},
|
||||
plugins: [reactRouter(), tsconfigPaths()],
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@documenso/web",
|
||||
"version": "1.9.0-rc.5",
|
||||
"version": "1.9.0-rc.11",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
@@ -26,7 +26,6 @@
|
||||
"@lingui/react": "^4.11.3",
|
||||
"@simplewebauthn/browser": "^9.0.1",
|
||||
"@simplewebauthn/server": "^9.0.3",
|
||||
"@tanstack/react-query": "^4.29.5",
|
||||
"colord": "^2.9.3",
|
||||
"cookie-es": "^1.0.0",
|
||||
"formidable": "^2.1.1",
|
||||
@@ -55,7 +54,7 @@
|
||||
"recharts": "^2.7.2",
|
||||
"remeda": "^2.17.3",
|
||||
"sharp": "0.32.6",
|
||||
"trpc-openapi": "^1.2.0",
|
||||
"trpc-to-openapi": "2.0.4",
|
||||
"ts-pattern": "^5.0.5",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"uqr": "^0.1.2",
|
||||
@@ -68,11 +67,11 @@
|
||||
"@simplewebauthn/types": "^9.0.1",
|
||||
"@types/formidable": "^2.0.6",
|
||||
"@types/luxon": "^3.3.1",
|
||||
"@types/node": "20.1.0",
|
||||
"@types/node": "^20",
|
||||
"@types/papaparse": "^5.3.14",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"typescript": "5.7.2"
|
||||
"typescript": "5.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
apps/web/public/static/icon.svg
Normal file
19
apps/web/public/static/icon.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="58" height="58" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.07 6.25832C23.333 6.92796 22.5176 7.71453 21.5857 8.63408C20.9957 9.09732 20.2682 9.35873 19.5117 9.37328L16.3896 9.43332L17.2992 8.52369C22.815 3.0079 25.5729 0.25 29 0.25C32.4271 0.25 35.185 3.00789 40.7008 8.52368L41.6087 9.43166L38.5937 9.37486C37.7437 9.35885 36.9292 9.03109 36.3051 8.45388L34.4972 6.78198C34.3255 6.6212 34.1581 6.46631 33.9946 6.31712L33.897 6.22687L33.8953 6.22687C33.2778 5.667 32.7153 5.18958 32.1851 4.78508C30.6538 3.6167 29.7624 3.35263 29 3.35263C28.2376 3.35263 27.3462 3.6167 25.8149 4.78508C25.2783 5.19451 24.7085 5.67864 24.0821 6.24737L24.0814 6.24737L24.07 6.25832Z" fill="black"/>
|
||||
<path d="M51.6826 24.0051C51.5337 23.8419 51.3791 23.6748 51.2187 23.5035L49.5459 21.6946C48.9691 21.0709 48.6413 20.2571 48.6249 19.4077L48.5667 16.3896L49.4763 17.2992C54.9921 22.815 57.75 25.5729 57.75 29C57.75 32.4271 54.9921 35.185 49.4763 40.7008L48.5667 41.6104L48.6249 38.5923C48.6413 37.7429 48.9691 36.9291 49.5459 36.3054L51.2185 34.4968C51.379 34.3253 51.5337 34.1581 51.6827 33.9948L51.7731 33.897V33.8953C52.333 33.2778 52.8104 32.7153 53.2149 32.1851C54.3833 30.6538 54.6474 29.7624 54.6474 29C54.6474 28.2376 54.3833 27.3462 53.2149 25.8149C52.8104 25.2847 52.333 24.7222 51.7731 24.1047V24.103L51.6826 24.0051Z" fill="black"/>
|
||||
<path d="M33.9601 51.7143C34.1446 51.5464 34.334 51.3711 34.5289 51.1883L36.3054 49.5457C36.9294 48.9687 37.7435 48.6411 38.5932 48.6249L41.6096 48.5675L40.7008 49.4763C35.185 54.9921 32.4271 57.75 29 57.75C25.5729 57.75 22.815 54.9921 17.2992 49.4763L16.3896 48.5667L19.4141 48.6248C20.2599 48.641 21.0705 48.9659 21.6934 49.5383L22.9131 50.6592C24.0267 51.726 24.9626 52.5647 25.8149 53.2149C27.3462 54.3833 28.2376 54.6474 29 54.6474C29.7624 54.6474 30.6538 54.3833 32.1851 53.2149C32.7217 52.8055 33.2915 52.3214 33.9179 51.7526H33.9187L33.9601 51.7143Z" fill="black"/>
|
||||
<path d="M6.26202 33.9341C6.44675 34.1373 6.64036 34.3465 6.8432 34.5625L8.4547 36.3051C9.03166 36.929 9.35938 37.7431 9.37562 38.5927L9.43332 41.6104L8.52369 40.7008C3.0079 35.185 0.25 32.4271 0.25 29C0.25 25.5729 3.00789 22.815 8.52368 17.2992L9.43249 16.3904L9.37539 19.4067C9.3593 20.2567 9.03143 21.0711 8.45413 21.6952L6.79271 23.4913C6.62762 23.6675 6.46871 23.8392 6.3158 24.0069L6.22687 24.103L6.22687 24.1047C5.66699 24.7222 5.18958 25.2847 4.78508 25.8149C3.6167 27.3462 3.35263 28.2376 3.35263 29C3.35263 29.7624 3.6167 30.6538 4.78508 32.1851C5.1946 32.7219 5.67887 33.2918 6.24777 33.9184L6.24777 33.9187L6.26202 33.9341Z" fill="black"/>
|
||||
<path d="M6.24777 24.0804L8.45413 21.6952C8.96576 21.1421 9.28147 20.4395 9.35788 19.6951C9.36697 18.4688 9.38705 17.3991 9.43129 16.4536L9.43374 16.3242L9.43774 16.3202C9.47846 15.5034 9.53816 14.7805 9.62565 14.1297C9.88231 12.2207 10.3259 11.4037 10.865 10.8646C11.4041 10.3255 12.2211 9.88195 14.1301 9.62529C14.7929 9.53618 15.5306 9.4759 16.3661 9.43513L16.3675 9.43374L16.4131 9.43287C17.3923 9.38614 18.5054 9.36574 19.7886 9.35686C20.5626 9.2798 21.2914 8.94412 21.8545 8.39979L24.0813 6.24742H22.7951C14.9946 6.24742 11.0944 6.24742 8.67108 8.67072C6.24777 11.094 6.24777 14.9943 6.24777 22.7948V24.0804Z" fill="black"/>
|
||||
<path d="M6.24777 33.9187V35.2053C6.24777 43.0058 6.24777 46.9061 8.67108 49.3294C11.0944 51.7527 14.9946 51.7527 22.7951 51.7527H35.2057C43.0062 51.7527 46.9064 51.7527 49.3297 49.3294C51.753 46.9061 51.753 43.0058 51.753 35.2053V33.9187L49.5459 36.3054C49.0356 36.8571 48.7203 37.5577 48.643 38.3C48.6337 39.5529 48.613 40.6424 48.5668 41.603L48.5663 41.6325L48.5654 41.6334C48.5246 42.4693 48.4643 43.2073 48.3752 43.8704C48.1185 45.7794 47.6749 46.5964 47.1358 47.1355C46.5967 47.6746 45.7797 48.1181 43.8707 48.3748C43.2197 48.4623 42.4965 48.522 41.6793 48.5628L41.6758 48.5663L41.5626 48.5684C40.6127 48.6132 39.5373 48.6334 38.3032 48.6426C37.5597 48.7193 36.858 49.0347 36.3054 49.5457L33.9187 51.7526L24.103 51.7526L21.6934 49.5383C21.1424 49.032 20.4445 48.7193 19.7052 48.6426C18.4558 48.6334 17.3688 48.6129 16.4101 48.5671L16.3675 48.5663L16.3662 48.565C15.5307 48.5242 14.7929 48.4639 14.1301 48.3748C12.2211 48.1181 11.4041 47.6746 10.865 47.1355C10.3259 46.5964 9.88231 45.7794 9.62565 43.8704C9.53653 43.2075 9.47625 42.4698 9.43548 41.6342L9.43374 41.6325L9.43265 41.5753C9.38742 40.6221 9.36703 39.5422 9.35786 38.3022C9.281 37.559 8.96554 36.8575 8.4547 36.3051L6.24777 33.9187Z" fill="black"/>
|
||||
<path d="M48.643 19.7C48.7203 20.4423 49.0356 21.1428 49.5459 21.6946L51.753 24.0813V22.7948C51.753 14.9943 51.753 11.094 49.3297 8.67072C46.9064 6.24742 43.0062 6.24742 35.2057 6.24742H33.9192L36.3051 8.45388C36.8586 8.96577 37.5618 9.28147 38.3067 9.35754C39.5257 9.36658 40.5898 9.38648 41.531 9.4302L41.7192 9.43374L41.725 9.43963C42.5235 9.4803 43.2319 9.5394 43.8707 9.62529C45.7797 9.88195 46.5967 10.3255 47.1358 10.8646C47.6749 11.4037 48.1185 12.2207 48.3752 14.1297C48.4643 14.7928 48.5246 15.5307 48.5654 16.3666L48.5663 16.3675L48.5668 16.3971C48.613 17.3577 48.6337 18.4471 48.643 19.7Z" fill="black"/>
|
||||
<path d="M26.6453 15.2538L24.5526 17.0299C24.1792 17.3469 23.7112 17.5312 23.2219 17.554L19.7195 17.7172L21.5458 15.8909C25.071 12.3656 26.8336 10.603 29.0239 10.603C31.2142 10.603 32.9768 12.3656 36.502 15.8909L38.3172 17.706L34.7657 17.5521C34.2678 17.5305 33.7917 17.3417 33.4143 17.0162L31.8345 15.6533C31.2799 15.1314 30.8096 14.7189 30.3805 14.3915C29.5014 13.7207 29.168 13.7055 29.0239 13.7055C28.8799 13.7055 28.5465 13.7207 27.6674 14.3915C27.6533 14.4022 27.6393 14.413 27.6252 14.4239L27.6232 14.4238L27.6024 14.4414C27.3079 14.6698 26.9934 14.9381 26.6453 15.2538Z" fill="black"/>
|
||||
<path d="M43.4935 27.471C42.8957 26.7152 42.0376 25.8229 40.7954 24.5736C40.5923 24.2491 40.4753 23.8751 40.4596 23.4874L40.306 19.6948L42.1106 21.4994C45.6358 25.0247 47.3985 26.7873 47.3985 28.9776C47.3985 31.1678 45.6358 32.9304 42.1106 36.4557L40.2963 38.27L40.4711 34.6452C40.4954 34.1415 40.6908 33.6612 41.025 33.2835L42.352 31.784C42.7709 31.3388 43.1192 30.9479 43.4095 30.589L43.573 30.4043V30.3824C43.5854 30.3662 43.5978 30.3502 43.61 30.3341C44.2808 29.455 44.296 29.1216 44.296 28.9776C44.296 28.8335 44.2808 28.5001 43.61 27.621C43.5978 27.6049 43.5854 27.5889 43.573 27.5727V27.5545L43.4935 27.471Z" fill="black"/>
|
||||
<path d="M14.4826 27.5691L16.902 24.9381C17.2447 24.5655 17.4494 24.0868 17.4821 23.5816L17.616 21.5163C17.6363 20.8624 17.6699 20.31 17.7254 19.8285L17.7349 19.682L17.7445 19.6725C17.7466 19.6559 17.7488 19.6394 17.751 19.6229C17.8983 18.527 18.1233 18.2805 18.2251 18.1786C18.327 18.0767 18.5736 17.8518 19.6695 17.7044C20.0213 17.6571 20.4118 17.6233 20.8544 17.5991L23.8259 17.3059C24.3027 17.2589 24.7513 17.0586 25.1046 16.7352L27.6159 14.4361H25.0583C20.0729 14.4361 17.5802 14.4361 16.0314 15.9848C14.712 17.3043 14.5166 19.3088 14.4877 22.9561C14.4826 23.59 14.4826 24.2735 14.4826 25.0117L14.4826 27.5627V27.5691Z" fill="black"/>
|
||||
<path d="M14.4826 30.386V30.3952L14.4826 32.9434C14.4826 33.6816 14.4826 34.3651 14.4877 34.999C14.5166 38.6463 14.7119 40.6509 16.0314 41.9703C17.3509 43.2898 19.3554 43.4851 23.0028 43.5141C23.6366 43.5191 24.3201 43.5191 25.0583 43.5191H27.6059H30.4384H32.99C33.728 43.5191 34.4114 43.5191 35.0451 43.5141C38.6927 43.4852 40.6974 43.2898 42.0169 41.9703C43.5657 40.4216 43.5657 37.9289 43.5657 32.9434V30.3783L40.9873 33.1741C40.7295 33.4537 40.5498 33.7928 40.462 34.1575C40.457 35.9697 40.4324 37.2297 40.3142 38.1998L40.313 38.2515L40.3071 38.2574C40.3039 38.2825 40.3006 38.3075 40.2973 38.3322C40.15 39.4282 39.925 39.6747 39.8231 39.7766C39.7213 39.8784 39.4747 40.1034 38.3788 40.2507C38.3541 40.2541 38.3291 40.2573 38.304 40.2605L38.2979 40.2666L38.2111 40.2719C37.6594 40.3374 37.0141 40.3733 36.2282 40.3929L34.4602 40.5008C33.9569 40.5316 33.4791 40.7331 33.1058 41.072L30.4107 43.5191H27.5938L24.8997 41.0003C24.5451 40.6688 24.0925 40.4638 23.612 40.4147C23.4039 40.4139 23.2033 40.4127 23.0098 40.4112C21.5576 40.3995 20.5048 40.363 19.6695 40.2507C18.5736 40.1034 18.327 39.8784 18.2251 39.7766C18.1233 39.6747 17.8983 39.4282 17.751 38.3322C17.7488 38.3158 17.7466 38.2993 17.7445 38.2828L17.7349 38.2732L17.734 38.2524L17.7304 38.1697C17.6321 37.3441 17.6002 36.3092 17.5899 34.9063L17.5744 34.5469C17.5703 34.4505 17.5598 34.355 17.5434 34.2608C17.4714 33.8486 17.2836 33.463 16.9993 33.1507L14.4826 30.386Z" fill="black"/>
|
||||
<path d="M40.4361 21.6459L40.5814 23.6165C40.6181 24.1136 40.8212 24.5837 41.158 24.9511L43.5657 27.5768V25.0117C43.5657 20.0263 43.5657 17.5336 42.0169 15.9848C40.4681 14.4361 37.9754 14.4361 32.99 14.4361H30.4104L32.9941 16.7897C33.3562 17.1196 33.8173 17.3201 34.3055 17.3601L37.3018 17.6052C37.5881 17.6223 37.8522 17.6436 38.098 17.6704L38.3195 17.6885L38.3288 17.6978C38.3455 17.7 38.3622 17.7022 38.3788 17.7044C39.4747 17.8518 39.7213 18.0767 39.8231 18.1786C39.925 18.2805 40.15 18.527 40.2973 19.6229C40.3724 20.1817 40.4136 20.8379 40.4361 21.6459Z" fill="black"/>
|
||||
<path d="M17.7437 38.2621L17.734 38.2524L17.7349 38.2732L17.7445 38.2828L17.7437 38.2621Z" fill="black"/>
|
||||
<path d="M19.7418 40.2602L23.0098 40.4112C21.5992 40.3998 20.5654 40.3651 19.7418 40.2602Z" fill="black"/>
|
||||
<path d="M17.6049 34.6945C17.5987 34.5474 17.578 34.4022 17.5434 34.2608C17.5598 34.355 17.5703 34.4505 17.5744 34.5469L17.5899 34.9063C17.6002 36.3092 17.6321 37.3441 17.7304 38.1697L17.734 38.2524L17.7437 38.2621L17.6049 34.6945Z" fill="black"/>
|
||||
<path d="M10.6494 28.9776C10.6494 30.8436 11.9288 32.3993 14.4877 34.999C14.4826 34.3651 14.4826 33.6816 14.4826 32.9434L14.4826 30.3952L14.4772 30.389L14.4772 30.3854C14.464 30.3682 14.4508 30.3511 14.4379 30.3341C13.7671 29.455 13.7518 29.1216 13.7518 28.9776C13.7518 28.8335 13.7671 28.5001 14.4379 27.621C14.4526 27.6016 14.4675 27.5822 14.4826 27.5627L14.4826 25.0117C14.4826 24.2735 14.4826 23.59 14.4877 22.9561C11.9288 25.5558 10.6494 27.1115 10.6494 28.9776Z" fill="black"/>
|
||||
<path d="M27.6674 43.5636C27.6549 43.5541 27.6425 43.5446 27.63 43.5349H27.6232L27.6059 43.5191H25.0583C24.3201 43.5191 23.6366 43.5191 23.0028 43.5141C25.6023 46.0727 27.1579 47.3521 29.0239 47.3521C30.8899 47.3521 32.4455 46.0727 35.0451 43.5141C34.4114 43.5191 33.728 43.5191 32.99 43.5191H30.4384C30.419 43.5341 30.3997 43.5489 30.3805 43.5636C29.5014 44.2344 29.168 44.2496 29.0239 44.2496C28.8799 44.2496 28.5465 44.2344 27.6674 43.5636Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -28,7 +28,7 @@ export const AdminActions = ({ className, document, recipients }: AdminActionsPr
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutate: resealDocument, isLoading: isResealDocumentLoading } =
|
||||
const { mutate: resealDocument, isPending: isResealDocumentLoading } =
|
||||
trpc.admin.resealDocument.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
|
||||
@@ -59,7 +59,7 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument
|
||||
<Trans>Admin Actions</Trans>
|
||||
</h2>
|
||||
|
||||
<AdminActions className="mt-2" document={document} recipients={document.Recipient} />
|
||||
<AdminActions className="mt-2" document={document} recipients={document.recipients} />
|
||||
|
||||
<hr className="my-4" />
|
||||
<h2 className="text-lg font-semibold">
|
||||
@@ -68,7 +68,7 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument
|
||||
|
||||
<div className="mt-4">
|
||||
<Accordion type="multiple" className="space-y-4">
|
||||
{document.Recipient.map((recipient) => (
|
||||
{document.recipients.map((recipient) => (
|
||||
<AccordionItem
|
||||
key={recipient.id}
|
||||
value={recipient.id.toString()}
|
||||
|
||||
@@ -39,9 +39,9 @@ type TAdminUpdateRecipientFormSchema = z.infer<typeof ZAdminUpdateRecipientFormS
|
||||
|
||||
export type RecipientItemProps = {
|
||||
recipient: Recipient & {
|
||||
Field: Array<
|
||||
fields: Array<
|
||||
Field & {
|
||||
Signature: Signature | null;
|
||||
signature: Signature | null;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -89,13 +89,13 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => {
|
||||
accessorKey: 'signature',
|
||||
cell: ({ row }) => (
|
||||
<div>
|
||||
{row.original.Signature?.typedSignature && (
|
||||
<span>{row.original.Signature.typedSignature}</span>
|
||||
{row.original.signature?.typedSignature && (
|
||||
<span>{row.original.signature.typedSignature}</span>
|
||||
)}
|
||||
|
||||
{row.original.Signature?.signatureImageAsBase64 && (
|
||||
{row.original.signature?.signatureImageAsBase64 && (
|
||||
<img
|
||||
src={row.original.Signature.signatureImageAsBase64}
|
||||
src={row.original.signature.signatureImageAsBase64}
|
||||
alt="Signature"
|
||||
className="h-12 w-full dark:invert"
|
||||
/>
|
||||
@@ -103,7 +103,7 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] satisfies DataTableColumnDef<(typeof recipient)['Field'][number]>[];
|
||||
] satisfies DataTableColumnDef<(typeof recipient)['fields'][number]>[];
|
||||
}, []);
|
||||
|
||||
const onUpdateRecipientFormSubmit = async ({ name, email }: TAdminUpdateRecipientFormSchema) => {
|
||||
@@ -190,7 +190,7 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => {
|
||||
<Trans>Fields</Trans>
|
||||
</h2>
|
||||
|
||||
<DataTable columns={columns} data={recipient.Field} />
|
||||
<DataTable columns={columns} data={recipient.fields} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import type { Document } from '@documenso/prisma/client';
|
||||
import { TRPCClientError } from '@documenso/trpc/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -36,7 +35,7 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo
|
||||
|
||||
const [reason, setReason] = useState('');
|
||||
|
||||
const { mutateAsync: deleteDocument, isLoading: isDeletingDocument } =
|
||||
const { mutateAsync: deleteDocument, isPending: isDeletingDocument } =
|
||||
trpc.admin.deleteDocument.useMutation();
|
||||
|
||||
const handleDeleteDocument = async () => {
|
||||
@@ -55,21 +54,12 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo
|
||||
|
||||
router.push('/admin/documents');
|
||||
} catch (err) {
|
||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
||||
toast({
|
||||
title: _(msg`An error occurred`),
|
||||
description: err.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
err.message ??
|
||||
'We encountered an unknown error while attempting to delete your document. Please try again later.',
|
||||
});
|
||||
}
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to delete your document. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,7 +67,7 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo
|
||||
<div>
|
||||
<div>
|
||||
<Alert
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row "
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -37,7 +37,7 @@ export const AdminDocumentResults = () => {
|
||||
const page = searchParams?.get?.('page') ? Number(searchParams.get('page')) : undefined;
|
||||
const perPage = searchParams?.get?.('perPage') ? Number(searchParams.get('perPage')) : undefined;
|
||||
|
||||
const { data: findDocumentsData, isLoading: isFindDocumentsLoading } =
|
||||
const { data: findDocumentsData, isPending: isFindDocumentsLoading } =
|
||||
trpc.admin.findDocuments.useQuery(
|
||||
{
|
||||
query: debouncedTerm,
|
||||
@@ -45,7 +45,7 @@ export const AdminDocumentResults = () => {
|
||||
perPage: perPage || 20,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -86,14 +86,14 @@ export const AdminDocumentResults = () => {
|
||||
header: _(msg`Owner`),
|
||||
accessorKey: 'owner',
|
||||
cell: ({ row }) => {
|
||||
const avatarFallbackText = row.original.User.name
|
||||
? extractInitials(row.original.User.name)
|
||||
: row.original.User.email.slice(0, 1).toUpperCase();
|
||||
const avatarFallbackText = row.original.user.name
|
||||
? extractInitials(row.original.user.name)
|
||||
: row.original.user.email.slice(0, 1).toUpperCase();
|
||||
|
||||
return (
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger>
|
||||
<Link href={`/admin/users/${row.original.User.id}`}>
|
||||
<Link href={`/admin/users/${row.original.user.id}`}>
|
||||
<Avatar className="dark:border-border h-12 w-12 border-2 border-solid border-white">
|
||||
<AvatarFallback className="text-xs text-gray-400">
|
||||
{avatarFallbackText}
|
||||
@@ -110,8 +110,8 @@ export const AdminDocumentResults = () => {
|
||||
</Avatar>
|
||||
|
||||
<div className="text-muted-foreground flex flex-col text-sm">
|
||||
<span>{row.original.User.name}</span>
|
||||
<span>{row.original.User.email}</span>
|
||||
<span>{row.original.user.name}</span>
|
||||
<span>{row.original.user.email}</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useState, useTransition } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronDownIcon as CaretSortIcon, Loader } from 'lucide-react';
|
||||
import { ChevronDownIcon, ChevronUpIcon, ChevronsUpDown, Loader } from 'lucide-react';
|
||||
|
||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
@@ -54,7 +54,15 @@ export const LeaderboardTable = ({
|
||||
onClick={() => handleColumnSort('name')}
|
||||
>
|
||||
{_(msg`Name`)}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4" />
|
||||
{sortBy === 'name' ? (
|
||||
sortOrder === 'asc' ? (
|
||||
<ChevronUpIcon className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" />
|
||||
)
|
||||
) : (
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
accessorKey: 'name',
|
||||
@@ -80,7 +88,15 @@ export const LeaderboardTable = ({
|
||||
onClick={() => handleColumnSort('signingVolume')}
|
||||
>
|
||||
{_(msg`Signing Volume`)}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4" />
|
||||
{sortBy === 'signingVolume' ? (
|
||||
sortOrder === 'asc' ? (
|
||||
<ChevronUpIcon className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" />
|
||||
)
|
||||
) : (
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
accessorKey: 'signingVolume',
|
||||
@@ -94,7 +110,15 @@ export const LeaderboardTable = ({
|
||||
onClick={() => handleColumnSort('createdAt')}
|
||||
>
|
||||
{_(msg`Created`)}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4" />
|
||||
{sortBy === 'createdAt' ? (
|
||||
sortOrder === 'asc' ? (
|
||||
<ChevronUpIcon className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" />
|
||||
)
|
||||
) : (
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -102,7 +126,7 @@ export const LeaderboardTable = ({
|
||||
cell: ({ row }) => i18n.date(row.original.createdAt),
|
||||
},
|
||||
] satisfies DataTableColumnDef<SigningVolume>[];
|
||||
}, [sortOrder]);
|
||||
}, [sortOrder, sortBy]);
|
||||
|
||||
useEffect(() => {
|
||||
startTransition(() => {
|
||||
@@ -133,6 +157,9 @@ export const LeaderboardTable = ({
|
||||
const handleColumnSort = (column: 'name' | 'createdAt' | 'signingVolume') => {
|
||||
startTransition(() => {
|
||||
updateSearchParams({
|
||||
search: debouncedSearchString,
|
||||
page,
|
||||
perPage,
|
||||
sortBy: column,
|
||||
sortOrder: sortBy === column && sortOrder === 'asc' ? 'desc' : 'asc',
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
SITE_SETTINGS_BANNER_ID,
|
||||
ZSiteSettingsBannerSchema,
|
||||
} from '@documenso/lib/server-only/site-settings/schemas/banner';
|
||||
import { TRPCClientError } from '@documenso/trpc/client';
|
||||
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { ColorPicker } from '@documenso/ui/primitives/color-picker';
|
||||
@@ -59,7 +58,7 @@ export function BannerForm({ banner }: BannerFormProps) {
|
||||
|
||||
const enabled = form.watch('enabled');
|
||||
|
||||
const { mutateAsync: updateSiteSetting, isLoading: isUpdateSiteSettingLoading } =
|
||||
const { mutateAsync: updateSiteSetting, isPending: isUpdateSiteSettingLoading } =
|
||||
trpcReact.admin.updateSiteSetting.useMutation();
|
||||
|
||||
const onBannerUpdate = async ({ id, enabled, data }: TBannerFormSchema) => {
|
||||
@@ -78,21 +77,13 @@ export function BannerForm({ banner }: BannerFormProps) {
|
||||
|
||||
router.refresh();
|
||||
} catch (err) {
|
||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
||||
toast({
|
||||
title: _(msg`An error occurred`),
|
||||
description: err.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
variant: 'destructive',
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to update the banner. Please try again later.`,
|
||||
),
|
||||
});
|
||||
}
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
variant: 'destructive',
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to update the banner. Please try again later.`,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { User } from '@documenso/prisma/client';
|
||||
import { TRPCClientError } from '@documenso/trpc/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -37,7 +38,7 @@ export const DeleteUserDialog = ({ className, user }: DeleteUserDialogProps) =>
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
const { mutateAsync: deleteUser, isLoading: isDeletingUser } =
|
||||
const { mutateAsync: deleteUser, isPending: isDeletingUser } =
|
||||
trpc.admin.deleteUser.useMutation();
|
||||
|
||||
const onDeleteAccount = async () => {
|
||||
@@ -54,23 +55,19 @@ export const DeleteUserDialog = ({ className, user }: DeleteUserDialogProps) =>
|
||||
|
||||
router.push('/admin/users');
|
||||
} catch (err) {
|
||||
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
|
||||
toast({
|
||||
title: _(msg`An error occurred`),
|
||||
description: err.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
err.message ??
|
||||
_(
|
||||
msg`We encountered an unknown error while attempting to delete your account. Please try again later.`,
|
||||
),
|
||||
});
|
||||
}
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
const errorMessage = match(error.code)
|
||||
.with(AppErrorCode.NOT_FOUND, () => msg`User not found.`)
|
||||
.with(AppErrorCode.UNAUTHORIZED, () => msg`You are not authorized to delete this user.`)
|
||||
.otherwise(() => msg`An error occurred while deleting the user.`);
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(errorMessage),
|
||||
variant: 'destructive',
|
||||
duration: 7500,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export const DisableUserDialog = ({ className, userToDisable }: DisableUserDialo
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
const { mutateAsync: disableUser, isLoading: isDisablingUser } =
|
||||
const { mutateAsync: disableUser, isPending: isDisablingUser } =
|
||||
trpc.admin.disableUser.useMutation();
|
||||
|
||||
const onDisableAccount = async () => {
|
||||
|
||||
@@ -34,7 +34,7 @@ export const EnableUserDialog = ({ className, userToEnable }: EnableUserDialogPr
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
const { mutateAsync: enableUser, isLoading: isEnablingUser } =
|
||||
const { mutateAsync: enableUser, isPending: isEnablingUser } =
|
||||
trpc.admin.enableUser.useMutation();
|
||||
|
||||
const onEnableAccount = async () => {
|
||||
|
||||
@@ -22,8 +22,8 @@ type UserData = {
|
||||
name: string | null;
|
||||
email: string;
|
||||
roles: Role[];
|
||||
Subscription?: SubscriptionLite[] | null;
|
||||
Document: DocumentLite[];
|
||||
subscriptions?: SubscriptionLite[] | null;
|
||||
documents: DocumentLite[];
|
||||
};
|
||||
|
||||
type SubscriptionLite = Pick<
|
||||
@@ -81,7 +81,7 @@ export const UsersDataTable = ({
|
||||
header: _(msg`Subscription`),
|
||||
accessorKey: 'subscription',
|
||||
cell: ({ row }) => {
|
||||
const foundIndividualSubscription = (row.original.Subscription ?? []).find((sub) =>
|
||||
const foundIndividualSubscription = (row.original.subscriptions ?? []).find((sub) =>
|
||||
individualPriceIds.includes(sub.priceId),
|
||||
);
|
||||
|
||||
@@ -92,7 +92,7 @@ export const UsersDataTable = ({
|
||||
header: _(msg`Documents`),
|
||||
accessorKey: 'documents',
|
||||
cell: ({ row }) => {
|
||||
return <div>{row.original.Document.length}</div>;
|
||||
return <div>{row.original.documents?.length}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,14 +18,14 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export type DocumentPageViewButtonProps = {
|
||||
document: Document & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
Recipient: Recipient[];
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
recipients: Recipient[];
|
||||
team: Pick<Team, 'id' | 'url'> | null;
|
||||
};
|
||||
team?: Pick<Team, 'id' | 'url'>;
|
||||
};
|
||||
|
||||
export const DocumentPageViewButton = ({ document, team }: DocumentPageViewButtonProps) => {
|
||||
export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps) => {
|
||||
const { data: session } = useSession();
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
@@ -34,7 +34,7 @@ export const DocumentPageViewButton = ({ document, team }: DocumentPageViewButto
|
||||
return null;
|
||||
}
|
||||
|
||||
const recipient = document.Recipient.find((recipient) => recipient.email === session.user.email);
|
||||
const recipient = document.recipients.find((recipient) => recipient.email === session.user.email);
|
||||
|
||||
const isRecipient = !!recipient;
|
||||
const isPending = document.status === DocumentStatus.PENDING;
|
||||
@@ -46,10 +46,16 @@ export const DocumentPageViewButton = ({ document, team }: DocumentPageViewButto
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
const documentWithData = await trpcClient.document.getDocumentById.query({
|
||||
documentId: document.id,
|
||||
teamId: team?.id,
|
||||
});
|
||||
const documentWithData = await trpcClient.document.getDocumentById.query(
|
||||
{
|
||||
documentId: document.id,
|
||||
},
|
||||
{
|
||||
context: {
|
||||
teamId: document.team?.id?.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const documentData = documentWithData?.documentData;
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ import { DuplicateDocumentDialog } from '../duplicate-document-dialog';
|
||||
|
||||
export type DocumentPageViewDropdownProps = {
|
||||
document: Document & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
Recipient: Recipient[];
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
recipients: Recipient[];
|
||||
team: Pick<Team, 'id' | 'url'> | null;
|
||||
};
|
||||
team?: Pick<Team, 'id' | 'url'> & { teamEmail: TeamEmail | null };
|
||||
@@ -60,9 +60,9 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
|
||||
return null;
|
||||
}
|
||||
|
||||
const recipient = document.Recipient.find((recipient) => recipient.email === session.user.email);
|
||||
const recipient = document.recipients.find((recipient) => recipient.email === session.user.email);
|
||||
|
||||
const isOwner = document.User.id === session.user.id;
|
||||
const isOwner = document.user.id === session.user.id;
|
||||
const isDraft = document.status === DocumentStatus.DRAFT;
|
||||
const isPending = document.status === DocumentStatus.PENDING;
|
||||
const isDeleted = document.deletedAt !== null;
|
||||
@@ -74,10 +74,16 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
const documentWithData = await trpcClient.document.getDocumentById.query({
|
||||
documentId: document.id,
|
||||
teamId: team?.id,
|
||||
});
|
||||
const documentWithData = await trpcClient.document.getDocumentById.query(
|
||||
{
|
||||
documentId: document.id,
|
||||
},
|
||||
{
|
||||
context: {
|
||||
teamId: team?.id?.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const documentData = documentWithData?.documentData;
|
||||
|
||||
@@ -95,7 +101,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
|
||||
}
|
||||
};
|
||||
|
||||
const nonSignedRecipients = document.Recipient.filter((item) => item.signingStatus !== 'SIGNED');
|
||||
const nonSignedRecipients = document.recipients.filter((item) => item.signingStatus !== 'SIGNED');
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -150,7 +156,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
|
||||
|
||||
{canManageDocument && (
|
||||
<DocumentRecipientLinkCopyDialog
|
||||
recipients={document.Recipient}
|
||||
recipients={document.recipients}
|
||||
trigger={
|
||||
<DropdownMenuItem
|
||||
disabled={!isPending || isDeleted}
|
||||
|
||||
@@ -12,8 +12,8 @@ import type { Document, Recipient, User } from '@documenso/prisma/client';
|
||||
export type DocumentPageViewInformationProps = {
|
||||
userId: number;
|
||||
document: Document & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
Recipient: Recipient[];
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
recipients: Recipient[];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -29,7 +29,8 @@ export const DocumentPageViewInformation = ({
|
||||
return [
|
||||
{
|
||||
description: msg`Uploaded by`,
|
||||
value: userId === document.userId ? _(msg`You`) : document.User.name ?? document.User.email,
|
||||
value:
|
||||
userId === document.userId ? _(msg`You`) : (document.user.name ?? document.user.email),
|
||||
},
|
||||
{
|
||||
description: msg`Created`,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export type DocumentPageViewRecipientsProps = {
|
||||
document: Document & {
|
||||
Recipient: Recipient[];
|
||||
recipients: Recipient[];
|
||||
};
|
||||
documentRootPath: string;
|
||||
};
|
||||
@@ -40,7 +40,7 @@ export const DocumentPageViewRecipients = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const recipients = document.Recipient;
|
||||
const recipients = document.recipients;
|
||||
|
||||
return (
|
||||
<section className="dark:bg-background border-border bg-widget flex flex-col rounded-xl border">
|
||||
|
||||
@@ -71,7 +71,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
||||
|
||||
const documentVisibility = document?.visibility;
|
||||
const currentTeamMemberRole = team?.currentTeamMember?.role;
|
||||
const isRecipient = document?.Recipient.find((recipient) => recipient.email === user.email);
|
||||
const isRecipient = document?.recipients.find((recipient) => recipient.email === user.email);
|
||||
let canAccessDocument = true;
|
||||
|
||||
if (team && !isRecipient && document?.userId !== user.id) {
|
||||
@@ -125,12 +125,13 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
||||
getFieldsForDocument({
|
||||
documentId,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
}),
|
||||
]);
|
||||
|
||||
const documentWithRecipients = {
|
||||
...document,
|
||||
Recipient: recipients,
|
||||
recipients,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
SKIP_QUERY_BATCH_META,
|
||||
} from '@documenso/lib/constants/trpc';
|
||||
import type { TGetDocumentWithDetailsByIdResponse } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import type { TDocument } from '@documenso/lib/types/document';
|
||||
import { DocumentDistributionMethod, DocumentStatus } from '@documenso/prisma/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@@ -35,7 +35,7 @@ import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type EditDocumentFormProps = {
|
||||
className?: string;
|
||||
initialDocument: TGetDocumentWithDetailsByIdResponse;
|
||||
initialDocument: TDocument;
|
||||
documentRootPath: string;
|
||||
isDocumentEnterprise: boolean;
|
||||
};
|
||||
@@ -64,7 +64,6 @@ export const EditDocumentForm = ({
|
||||
trpc.document.getDocumentWithDetailsById.useQuery(
|
||||
{
|
||||
documentId: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
{
|
||||
initialData: initialDocument,
|
||||
@@ -72,15 +71,14 @@ export const EditDocumentForm = ({
|
||||
},
|
||||
);
|
||||
|
||||
const { Recipient: recipients, Field: fields } = document;
|
||||
const { recipients, fields } = document;
|
||||
|
||||
const { mutateAsync: setSettingsForDocument } = trpc.document.setSettingsForDocument.useMutation({
|
||||
const { mutateAsync: updateDocument } = trpc.document.setSettingsForDocument.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
documentId: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
||||
);
|
||||
@@ -94,7 +92,6 @@ export const EditDocumentForm = ({
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
documentId: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialDocument), ...newData, id: Number(newData.id) }),
|
||||
);
|
||||
@@ -107,40 +104,20 @@ export const EditDocumentForm = ({
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
documentId: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialDocument), Field: newFields }),
|
||||
(oldData) => ({ ...(oldData || initialDocument), fields: newFields }),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateTypedSignature } =
|
||||
trpc.document.updateTypedSignatureSettings.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
documentId: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
(oldData) => ({
|
||||
...(oldData || initialDocument),
|
||||
...newData,
|
||||
id: Number(newData.id),
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
|
||||
const { mutateAsync: setRecipients } = trpc.recipient.setDocumentRecipients.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: ({ recipients: newRecipients }) => {
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
documentId: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialDocument), Recipient: newRecipients }),
|
||||
(oldData) => ({ ...(oldData || initialDocument), recipients: newRecipients }),
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -151,7 +128,6 @@ export const EditDocumentForm = ({
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
documentId: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
||||
);
|
||||
@@ -205,9 +181,8 @@ export const EditDocumentForm = ({
|
||||
try {
|
||||
const { timezone, dateFormat, redirectUrl, language } = data.meta;
|
||||
|
||||
await setSettingsForDocument({
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
teamId: team?.id,
|
||||
data: {
|
||||
title: data.title,
|
||||
externalId: data.externalId || null,
|
||||
@@ -246,10 +221,9 @@ export const EditDocumentForm = ({
|
||||
signingOrder: data.signingOrder,
|
||||
}),
|
||||
|
||||
addSigners({
|
||||
setRecipients({
|
||||
documentId: document.id,
|
||||
teamId: team?.id,
|
||||
signers: data.signers.map((signer) => ({
|
||||
recipients: data.signers.map((signer) => ({
|
||||
...signer,
|
||||
// Explicitly set to null to indicate we want to remove auth if required.
|
||||
actionAuth: signer.actionAuth || null,
|
||||
@@ -279,9 +253,12 @@ export const EditDocumentForm = ({
|
||||
fields: data.fields,
|
||||
});
|
||||
|
||||
await updateTypedSignature({
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||
|
||||
meta: {
|
||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
// Clear all field data from localStorage
|
||||
@@ -313,7 +290,6 @@ export const EditDocumentForm = ({
|
||||
try {
|
||||
await sendDocument({
|
||||
documentId: document.id,
|
||||
teamId: team?.id,
|
||||
meta: {
|
||||
subject,
|
||||
message,
|
||||
|
||||
@@ -52,7 +52,7 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
|
||||
|
||||
const documentVisibility = document?.visibility;
|
||||
const currentTeamMemberRole = team?.currentTeamMember?.role;
|
||||
const isRecipient = document?.Recipient.find((recipient) => recipient.email === user.email);
|
||||
const isRecipient = document?.recipients.find((recipient) => recipient.email === user.email);
|
||||
let canAccessDocument = true;
|
||||
|
||||
if (!isRecipient && document?.userId !== user.id) {
|
||||
@@ -78,7 +78,7 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
|
||||
redirect(`${documentRootPath}/${documentId}`);
|
||||
}
|
||||
|
||||
const { documentMeta, Recipient: recipients } = document;
|
||||
const { documentMeta, recipients } = document;
|
||||
|
||||
if (documentMeta?.password) {
|
||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||
|
||||
@@ -37,17 +37,16 @@ export const DocumentLogsDataTable = ({ documentId }: DocumentLogsDataTableProps
|
||||
|
||||
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||
|
||||
const { data, isLoading, isInitialLoading, isLoadingError } =
|
||||
trpc.document.findDocumentAuditLogs.useQuery(
|
||||
{
|
||||
documentId,
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
const { data, isLoading, isLoadingError } = trpc.document.findDocumentAuditLogs.useQuery(
|
||||
{
|
||||
documentId,
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
},
|
||||
{
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
const onPaginationChange = (page: number, perPage: number) => {
|
||||
updateSearchParams({
|
||||
@@ -132,7 +131,7 @@ export const DocumentLogsDataTable = ({ documentId }: DocumentLogsDataTableProps
|
||||
enable: isLoadingError,
|
||||
}}
|
||||
skeleton={{
|
||||
enable: isLoading && isInitialLoading,
|
||||
enable: isLoading,
|
||||
rows: 3,
|
||||
component: (
|
||||
<>
|
||||
|
||||
@@ -77,9 +77,9 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
|
||||
},
|
||||
{
|
||||
description: msg`Created by`,
|
||||
value: document.User.name
|
||||
? `${document.User.name} (${document.User.email})`
|
||||
: document.User.email,
|
||||
value: document.user.name
|
||||
? `${document.user.name} (${document.user.email})`
|
||||
: document.user.email,
|
||||
},
|
||||
{
|
||||
description: msg`Date created`,
|
||||
|
||||
@@ -15,20 +15,16 @@ export type DownloadAuditLogButtonProps = {
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export const DownloadAuditLogButton = ({
|
||||
className,
|
||||
teamId,
|
||||
documentId,
|
||||
}: DownloadAuditLogButtonProps) => {
|
||||
export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditLogButtonProps) => {
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { mutateAsync: downloadAuditLogs, isLoading } =
|
||||
const { mutateAsync: downloadAuditLogs, isPending } =
|
||||
trpc.document.downloadAuditLogs.useMutation();
|
||||
|
||||
const onDownloadAuditLogsClick = async () => {
|
||||
try {
|
||||
const { url } = await downloadAuditLogs({ teamId, documentId });
|
||||
const { url } = await downloadAuditLogs({ documentId });
|
||||
|
||||
const iframe = Object.assign(document.createElement('iframe'), {
|
||||
src: url,
|
||||
@@ -74,10 +70,10 @@ export const DownloadAuditLogButton = ({
|
||||
return (
|
||||
<Button
|
||||
className={cn('w-full sm:w-auto', className)}
|
||||
loading={isLoading}
|
||||
loading={isPending}
|
||||
onClick={() => void onDownloadAuditLogsClick()}
|
||||
>
|
||||
{!isLoading && <DownloadIcon className="mr-1.5 h-4 w-4" />}
|
||||
{!isPending && <DownloadIcon className="mr-1.5 h-4 w-4" />}
|
||||
<Trans>Download Audit Logs</Trans>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -26,12 +26,12 @@ export const DownloadCertificateButton = ({
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { mutateAsync: downloadCertificate, isLoading } =
|
||||
const { mutateAsync: downloadCertificate, isPending } =
|
||||
trpc.document.downloadCertificate.useMutation();
|
||||
|
||||
const onDownloadCertificatesClick = async () => {
|
||||
try {
|
||||
const { url } = await downloadCertificate({ documentId, teamId });
|
||||
const { url } = await downloadCertificate({ documentId });
|
||||
|
||||
const iframe = Object.assign(document.createElement('iframe'), {
|
||||
src: url,
|
||||
@@ -77,12 +77,12 @@ export const DownloadCertificateButton = ({
|
||||
return (
|
||||
<Button
|
||||
className={cn('w-full sm:w-auto', className)}
|
||||
loading={isLoading}
|
||||
loading={isPending}
|
||||
variant="outline"
|
||||
disabled={documentStatus !== DocumentStatus.COMPLETED}
|
||||
onClick={() => void onDownloadCertificatesClick()}
|
||||
>
|
||||
{!isLoading && <DownloadIcon className="mr-1.5 h-4 w-4" />}
|
||||
{!isPending && <DownloadIcon className="mr-1.5 h-4 w-4" />}
|
||||
<Trans>Download Certificate</Trans>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -91,7 +91,7 @@ export const ResendDocumentActionItem = ({
|
||||
|
||||
const onFormSubmit = async ({ recipients }: TResendDocumentFormSchema) => {
|
||||
try {
|
||||
await resendDocument({ documentId: document.id, recipients, teamId: team?.id });
|
||||
await resendDocument({ documentId: document.id, recipients });
|
||||
|
||||
toast({
|
||||
title: _(msg`Document re-sent`),
|
||||
|
||||
@@ -12,15 +12,14 @@ import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
|
||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
||||
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||
import { trpc as trpcClient } from '@documenso/trpc/client';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export type DataTableActionButtonProps = {
|
||||
row: Document & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
Recipient: Recipient[];
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
recipients: Recipient[];
|
||||
team: Pick<Team, 'id' | 'url'> | null;
|
||||
};
|
||||
team?: Pick<Team, 'id' | 'url'>;
|
||||
@@ -35,9 +34,9 @@ export const DataTableActionButton = ({ row, team }: DataTableActionButtonProps)
|
||||
return null;
|
||||
}
|
||||
|
||||
const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email);
|
||||
const recipient = row.recipients.find((recipient) => recipient.email === session.user.email);
|
||||
|
||||
const isOwner = row.User.id === session.user.id;
|
||||
const isOwner = row.user.id === session.user.id;
|
||||
const isRecipient = !!recipient;
|
||||
const isDraft = row.status === DocumentStatus.DRAFT;
|
||||
const isPending = row.status === DocumentStatus.PENDING;
|
||||
@@ -50,18 +49,20 @@ export const DataTableActionButton = ({ row, team }: DataTableActionButtonProps)
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
let document: DocumentWithData | null = null;
|
||||
|
||||
if (!recipient) {
|
||||
document = await trpcClient.document.getDocumentById.query({
|
||||
documentId: row.id,
|
||||
teamId: team?.id,
|
||||
});
|
||||
} else {
|
||||
document = await trpcClient.document.getDocumentByToken.query({
|
||||
token: recipient.token,
|
||||
});
|
||||
}
|
||||
const document = !recipient
|
||||
? await trpcClient.document.getDocumentById.query(
|
||||
{
|
||||
documentId: row.id,
|
||||
},
|
||||
{
|
||||
context: {
|
||||
teamId: team?.id?.toString(),
|
||||
},
|
||||
},
|
||||
)
|
||||
: await trpcClient.document.getDocumentByToken.query({
|
||||
token: recipient.token,
|
||||
});
|
||||
|
||||
const documentData = document?.documentData;
|
||||
|
||||
|
||||
@@ -23,9 +23,8 @@ import { useSession } from 'next-auth/react';
|
||||
|
||||
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import { DocumentStatus, RecipientRole } from '@documenso/prisma/client';
|
||||
import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
|
||||
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||
import { DocumentStatus, RecipientRole } from '@documenso/prisma/client';
|
||||
import { trpc as trpcClient } from '@documenso/trpc/client';
|
||||
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
|
||||
import {
|
||||
@@ -46,8 +45,8 @@ import { MoveDocumentDialog } from './move-document-dialog';
|
||||
|
||||
export type DataTableActionDropdownProps = {
|
||||
row: Document & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
Recipient: Recipient[];
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
recipients: Recipient[];
|
||||
team: Pick<Team, 'id' | 'url'> | null;
|
||||
};
|
||||
team?: Pick<Team, 'id' | 'url'> & { teamEmail?: string };
|
||||
@@ -66,9 +65,9 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
return null;
|
||||
}
|
||||
|
||||
const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email);
|
||||
const recipient = row.recipients.find((recipient) => recipient.email === session.user.email);
|
||||
|
||||
const isOwner = row.User.id === session.user.id;
|
||||
const isOwner = row.user.id === session.user.id;
|
||||
// const isRecipient = !!recipient;
|
||||
const isDraft = row.status === DocumentStatus.DRAFT;
|
||||
const isPending = row.status === DocumentStatus.PENDING;
|
||||
@@ -81,18 +80,13 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
let document: DocumentWithData | null = null;
|
||||
|
||||
if (!recipient) {
|
||||
document = await trpcClient.document.getDocumentById.query({
|
||||
documentId: row.id,
|
||||
teamId: team?.id,
|
||||
});
|
||||
} else {
|
||||
document = await trpcClient.document.getDocumentByToken.query({
|
||||
token: recipient.token,
|
||||
});
|
||||
}
|
||||
const document = !recipient
|
||||
? await trpcClient.document.getDocumentById.query({
|
||||
documentId: row.id,
|
||||
})
|
||||
: await trpcClient.document.getDocumentByToken.query({
|
||||
token: recipient.token,
|
||||
});
|
||||
|
||||
const documentData = document?.documentData;
|
||||
|
||||
@@ -110,7 +104,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
}
|
||||
};
|
||||
|
||||
const nonSignedRecipients = row.Recipient.filter((item) => item.signingStatus !== 'SIGNED');
|
||||
const nonSignedRecipients = row.recipients.filter((item) => item.signingStatus !== 'SIGNED');
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -195,7 +189,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
|
||||
{canManageDocument && (
|
||||
<DocumentRecipientLinkCopyDialog
|
||||
recipients={row.Recipient}
|
||||
recipients={row.recipients}
|
||||
trigger={
|
||||
<DropdownMenuItem disabled={!isPending} asChild onSelect={(e) => e.preventDefault()}>
|
||||
<div>
|
||||
@@ -239,14 +233,12 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
onOpenChange={setMoveDialogOpen}
|
||||
/>
|
||||
|
||||
{isDuplicateDialogOpen && (
|
||||
<DuplicateDocumentDialog
|
||||
id={row.id}
|
||||
open={isDuplicateDialogOpen}
|
||||
onOpenChange={setDuplicateDialogOpen}
|
||||
team={team}
|
||||
/>
|
||||
)}
|
||||
<DuplicateDocumentDialog
|
||||
id={row.id}
|
||||
open={isDuplicateDialogOpen}
|
||||
onOpenChange={setDuplicateDialogOpen}
|
||||
team={team}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export const DataTableSenderFilter = ({ teamId }: DataTableSenderFilterProps) =>
|
||||
|
||||
const senderIds = parseToIntegerArray(searchParams?.get('senderIds') ?? '');
|
||||
|
||||
const { data, isInitialLoading } = trpc.team.getTeamMembers.useQuery({
|
||||
const { data, isLoading } = trpc.team.getTeamMembers.useQuery({
|
||||
teamId,
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ export const DataTableSenderFilter = ({ teamId }: DataTableSenderFilterProps) =>
|
||||
}
|
||||
enableClearAllButton={true}
|
||||
inputPlaceholder={msg`Search`}
|
||||
loading={!isMounted || isInitialLoading}
|
||||
loading={!isMounted || isLoading}
|
||||
options={comboBoxOptions}
|
||||
selectedValues={senderIds}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -10,9 +10,9 @@ import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
|
||||
|
||||
export type DataTableTitleProps = {
|
||||
row: Document & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
team: Pick<Team, 'url'> | null;
|
||||
Recipient: Recipient[];
|
||||
recipients: Recipient[];
|
||||
};
|
||||
teamUrl?: string;
|
||||
};
|
||||
@@ -24,9 +24,9 @@ export const DataTableTitle = ({ row, teamUrl }: DataTableTitleProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email);
|
||||
const recipient = row.recipients.find((recipient) => recipient.email === session.user.email);
|
||||
|
||||
const isOwner = row.User.id === session.user.id;
|
||||
const isOwner = row.user.id === session.user.id;
|
||||
const isRecipient = !!recipient;
|
||||
const isCurrentTeamDocument = teamUrl && row.team?.url === teamUrl;
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import { DateTime } from 'luxon';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import type { TFindDocumentsResponse } from '@documenso/lib/server-only/document/find-documents';
|
||||
import type { Team } from '@documenso/prisma/client';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
import type { TFindDocumentsResponse } from '@documenso/trpc/server/document-router/schema';
|
||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||
import { DataTable } from '@documenso/ui/primitives/data-table';
|
||||
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
|
||||
@@ -57,14 +57,14 @@ export const DocumentsDataTable = ({
|
||||
{
|
||||
id: 'sender',
|
||||
header: _(msg`Sender`),
|
||||
cell: ({ row }) => row.original.User.name ?? row.original.User.email,
|
||||
cell: ({ row }) => row.original.user.name ?? row.original.user.email,
|
||||
},
|
||||
{
|
||||
header: _(msg`Recipient`),
|
||||
accessorKey: 'recipient',
|
||||
cell: ({ row }) => (
|
||||
<StackAvatarsWithTooltip
|
||||
recipients={row.original.Recipient}
|
||||
recipients={row.original.recipients}
|
||||
documentStatus={row.original.status}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -38,7 +38,6 @@ export const DeleteDocumentDialog = ({
|
||||
onOpenChange,
|
||||
status,
|
||||
documentTitle,
|
||||
teamId,
|
||||
canManageDocument,
|
||||
}: DeleteDocumentDialogProps) => {
|
||||
const router = useRouter();
|
||||
@@ -52,7 +51,7 @@ export const DeleteDocumentDialog = ({
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
|
||||
|
||||
const { mutateAsync: deleteDocument, isLoading } = trpcReact.document.deleteDocument.useMutation({
|
||||
const { mutateAsync: deleteDocument, isPending } = trpcReact.document.deleteDocument.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
void refreshLimits();
|
||||
@@ -76,7 +75,7 @@ export const DeleteDocumentDialog = ({
|
||||
|
||||
const onDelete = async () => {
|
||||
try {
|
||||
await deleteDocument({ documentId: id, teamId });
|
||||
await deleteDocument({ documentId: id });
|
||||
} catch {
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
@@ -93,7 +92,7 @@ export const DeleteDocumentDialog = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
|
||||
<Dialog open={open} onOpenChange={(value) => !isPending && onOpenChange(value)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
@@ -194,7 +193,7 @@ export const DeleteDocumentDialog = ({
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
loading={isLoading}
|
||||
loading={isPending}
|
||||
onClick={onDelete}
|
||||
disabled={!isDeleteEnabled && canManageDocument}
|
||||
variant="destructive"
|
||||
|
||||
@@ -37,7 +37,6 @@ export const DuplicateDocumentDialog = ({
|
||||
|
||||
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery({
|
||||
documentId: id,
|
||||
teamId: team?.id,
|
||||
});
|
||||
|
||||
const documentData = document?.documentData
|
||||
@@ -49,10 +48,10 @@ export const DuplicateDocumentDialog = ({
|
||||
|
||||
const documentsPath = formatDocumentsPath(team?.url);
|
||||
|
||||
const { mutateAsync: duplicateDocument, isLoading: isDuplicateLoading } =
|
||||
const { mutateAsync: duplicateDocument, isPending: isDuplicateLoading } =
|
||||
trpcReact.document.duplicateDocument.useMutation({
|
||||
onSuccess: (newId) => {
|
||||
router.push(`${documentsPath}/${newId}/edit`);
|
||||
onSuccess: ({ documentId }) => {
|
||||
router.push(`${documentsPath}/${documentId}/edit`);
|
||||
|
||||
toast({
|
||||
title: _(msg`Document Duplicated`),
|
||||
@@ -66,7 +65,7 @@ export const DuplicateDocumentDialog = ({
|
||||
|
||||
const onDuplicate = async () => {
|
||||
try {
|
||||
await duplicateDocument({ documentId: id, teamId: team?.id });
|
||||
await duplicateDocument({ documentId: id });
|
||||
} catch {
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
|
||||
@@ -42,7 +42,7 @@ export const MoveDocumentDialog = ({ documentId, open, onOpenChange }: MoveDocum
|
||||
|
||||
const { data: teams, isLoading: isLoadingTeams } = trpc.team.getTeams.useQuery();
|
||||
|
||||
const { mutateAsync: moveDocument, isLoading } = trpc.document.moveDocumentToTeam.useMutation({
|
||||
const { mutateAsync: moveDocument, isPending } = trpc.document.moveDocumentToTeam.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
toast({
|
||||
@@ -119,8 +119,8 @@ export const MoveDocumentDialog = ({ documentId, open, onOpenChange }: MoveDocum
|
||||
<Button variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button onClick={onMove} loading={isLoading} disabled={!selectedTeamId || isLoading}>
|
||||
{isLoading ? <Trans>Moving...</Trans> : <Trans>Move</Trans>}
|
||||
<Button onClick={onMove} loading={isPending} disabled={!selectedTeamId || isPending}>
|
||||
{isPending ? <Trans>Moving...</Trans> : <Trans>Move</Trans>}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -8,16 +8,16 @@ import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import { TRPCClientError } from '@documenso/trpc/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone';
|
||||
@@ -76,7 +76,6 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
|
||||
const { id } = await createDocument({
|
||||
title: file.name,
|
||||
documentDataId,
|
||||
teamId: team?.id,
|
||||
timezone: userTimezone,
|
||||
});
|
||||
|
||||
@@ -100,25 +99,20 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
|
||||
|
||||
console.error(err);
|
||||
|
||||
if (error.code === 'INVALID_DOCUMENT_FILE') {
|
||||
toast({
|
||||
title: _(msg`Invalid file`),
|
||||
description: _(msg`You cannot upload encrypted PDFs`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else if (err instanceof TRPCClientError) {
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: err.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while uploading your document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
const errorMessage = match(error.code)
|
||||
.with('INVALID_DOCUMENT_FILE', () => msg`You cannot upload encrypted PDFs`)
|
||||
.with(
|
||||
AppErrorCode.LIMIT_EXCEEDED,
|
||||
() => msg`You have reached your document limit for this month. Please upgrade your plan.`,
|
||||
)
|
||||
.otherwise(() => msg`An error occurred while uploading your document.`);
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(errorMessage),
|
||||
variant: 'destructive',
|
||||
duration: 7500,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
|
||||
|
||||
const [enteredEmail, setEnteredEmail] = useState<string>('');
|
||||
|
||||
const { mutateAsync: deleteAccount, isLoading: isDeletingAccount } =
|
||||
const { mutateAsync: deleteAccount, isPending: isDeletingAccount } =
|
||||
trpc.profile.deleteAccount.useMutation();
|
||||
|
||||
const onDeleteAccount = async () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import type { FindTemplateRow } from '@documenso/lib/server-only/template/find-templates';
|
||||
import type {
|
||||
Team,
|
||||
TeamProfile,
|
||||
@@ -15,6 +14,7 @@ import type {
|
||||
} from '@documenso/prisma/client';
|
||||
import { TemplateType } from '@documenso/prisma/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { FindTemplateRow } from '@documenso/trpc/server/template-router/schema';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Switch } from '@documenso/ui/primitives/switch';
|
||||
@@ -61,13 +61,12 @@ export const PublicProfilePageView = ({ user, team, profile }: PublicProfilePage
|
||||
|
||||
const { data } = trpc.template.findTemplates.useQuery({
|
||||
perPage: 100,
|
||||
teamId: team?.id,
|
||||
});
|
||||
|
||||
const { mutateAsync: updateUserProfile, isLoading: isUpdatingUserProfile } =
|
||||
const { mutateAsync: updateUserProfile, isPending: isUpdatingUserProfile } =
|
||||
trpc.profile.updatePublicProfile.useMutation();
|
||||
|
||||
const { mutateAsync: updateTeamProfile, isLoading: isUpdatingTeamProfile } =
|
||||
const { mutateAsync: updateTeamProfile, isPending: isUpdatingTeamProfile } =
|
||||
trpc.team.updateTeamPublicProfile.useMutation();
|
||||
|
||||
const isUpdating = isUpdatingUserProfile || isUpdatingTeamProfile;
|
||||
|
||||
@@ -7,11 +7,11 @@ import { useLingui } from '@lingui/react';
|
||||
import { EditIcon, FileIcon, LinkIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
|
||||
|
||||
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
|
||||
import type { FindTemplateRow } from '@documenso/lib/server-only/template/find-templates';
|
||||
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
|
||||
import type { TemplateDirectLink } from '@documenso/prisma/client';
|
||||
import { TemplateType } from '@documenso/prisma/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { FindTemplateRow } from '@documenso/trpc/server/template-router/schema';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -23,15 +23,12 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { ManagePublicTemplateDialog } from '~/components/templates/manage-public-template-dialog';
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
type DirectTemplate = FindTemplateRow & {
|
||||
directLink: Pick<TemplateDirectLink, 'token' | 'enabled'>;
|
||||
};
|
||||
|
||||
export const PublicTemplatesDataTable = () => {
|
||||
const team = useOptionalCurrentTeam();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -42,12 +39,10 @@ export const PublicTemplatesDataTable = () => {
|
||||
templateId: number;
|
||||
} | null>(null);
|
||||
|
||||
const { data, isInitialLoading, isLoadingError, refetch } = trpc.template.findTemplates.useQuery(
|
||||
const { data, isLoading, isLoadingError, refetch } = trpc.template.findTemplates.useQuery(
|
||||
{},
|
||||
{
|
||||
teamId: team?.id,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -85,7 +80,7 @@ export const PublicTemplatesDataTable = () => {
|
||||
{/* Loading and error handling states. */}
|
||||
{publicDirectTemplates.length === 0 && (
|
||||
<>
|
||||
{isInitialLoading &&
|
||||
{isLoading &&
|
||||
Array(3)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
@@ -120,7 +115,7 @@ export const PublicTemplatesDataTable = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isInitialLoading && (
|
||||
{!isLoading && (
|
||||
<div className="text-muted-foreground flex h-32 flex-col items-center justify-center text-sm">
|
||||
<Trans>No public profile templates found</Trans>
|
||||
<ManagePublicTemplateDialog
|
||||
|
||||
@@ -35,16 +35,15 @@ export const UserSecurityActivityDataTable = () => {
|
||||
|
||||
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||
|
||||
const { data, isLoading, isInitialLoading, isLoadingError } =
|
||||
trpc.profile.findUserSecurityAuditLogs.useQuery(
|
||||
{
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
const { data, isLoading, isLoadingError } = trpc.profile.findUserSecurityAuditLogs.useQuery(
|
||||
{
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
},
|
||||
{
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
const onPaginationChange = (page: number, perPage: number) => {
|
||||
updateSearchParams({
|
||||
@@ -134,7 +133,7 @@ export const UserSecurityActivityDataTable = () => {
|
||||
enable: isLoadingError,
|
||||
}}
|
||||
skeleton={{
|
||||
enable: isLoading && isInitialLoading,
|
||||
enable: isLoading,
|
||||
rows: 3,
|
||||
component: (
|
||||
<>
|
||||
|
||||
@@ -65,7 +65,7 @@ export const CreatePasskeyDialog = ({ trigger, onSuccess, ...props }: CreatePass
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: createPasskeyRegistrationOptions, isLoading } =
|
||||
const { mutateAsync: createPasskeyRegistrationOptions, isPending } =
|
||||
trpc.auth.createPasskeyRegistrationOptions.useMutation();
|
||||
|
||||
const { mutateAsync: createPasskey } = trpc.auth.createPasskey.useMutation();
|
||||
@@ -141,7 +141,7 @@ export const CreatePasskeyDialog = ({ trigger, onSuccess, ...props }: CreatePass
|
||||
>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||
{trigger ?? (
|
||||
<Button variant="secondary" loading={isLoading}>
|
||||
<Button variant="secondary" loading={isPending}>
|
||||
<KeyRoundIcon className="-ml-1 mr-1 h-5 w-5" />
|
||||
<Trans>Add passkey</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -60,7 +60,7 @@ export const UserPasskeysDataTableActions = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePasskey, isLoading: isUpdatingPasskey } =
|
||||
const { mutateAsync: updatePasskey, isPending: isUpdatingPasskey } =
|
||||
trpc.auth.updatePasskey.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
@@ -80,7 +80,7 @@ export const UserPasskeysDataTableActions = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deletePasskey, isLoading: isDeletingPasskey } =
|
||||
const { mutateAsync: deletePasskey, isPending: isDeletingPasskey } =
|
||||
trpc.auth.deletePasskey.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
|
||||
@@ -29,13 +29,13 @@ export const UserPasskeysDataTable = () => {
|
||||
|
||||
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||
|
||||
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.auth.findPasskeys.useQuery(
|
||||
const { data, isLoading, isLoadingError } = trpc.auth.findPasskeys.useQuery(
|
||||
{
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -100,7 +100,7 @@ export const UserPasskeysDataTable = () => {
|
||||
enable: isLoadingError,
|
||||
}}
|
||||
skeleton={{
|
||||
enable: isLoading && isInitialLoading,
|
||||
enable: isLoading,
|
||||
rows: 3,
|
||||
component: (
|
||||
<>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const AcceptTeamInvitationButton = ({ teamId }: AcceptTeamInvitationButto
|
||||
|
||||
const {
|
||||
mutateAsync: acceptTeamInvitation,
|
||||
isLoading,
|
||||
isPending,
|
||||
isSuccess,
|
||||
} = trpc.team.acceptTeamInvitation.useMutation({
|
||||
onSuccess: () => {
|
||||
@@ -40,8 +40,8 @@ export const AcceptTeamInvitationButton = ({ teamId }: AcceptTeamInvitationButto
|
||||
return (
|
||||
<Button
|
||||
onClick={async () => acceptTeamInvitation({ teamId })}
|
||||
loading={isLoading}
|
||||
disabled={isLoading || isSuccess}
|
||||
loading={isPending}
|
||||
disabled={isPending || isSuccess}
|
||||
>
|
||||
<Trans>Accept</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const DeclineTeamInvitationButton = ({ teamId }: DeclineTeamInvitationBut
|
||||
|
||||
const {
|
||||
mutateAsync: declineTeamInvitation,
|
||||
isLoading,
|
||||
isPending,
|
||||
isSuccess,
|
||||
} = trpc.team.declineTeamInvitation.useMutation({
|
||||
onSuccess: () => {
|
||||
@@ -40,8 +40,8 @@ export const DeclineTeamInvitationButton = ({ teamId }: DeclineTeamInvitationBut
|
||||
return (
|
||||
<Button
|
||||
onClick={async () => declineTeamInvitation({ teamId })}
|
||||
loading={isLoading}
|
||||
disabled={isLoading || isSuccess}
|
||||
loading={isPending}
|
||||
disabled={isPending || isSuccess}
|
||||
variant="ghost"
|
||||
>
|
||||
<Trans>Decline</Trans>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const TeamEmailUsage = ({ teamEmail }: TeamEmailUsageProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: deleteTeamEmail, isLoading: isDeletingTeamEmail } =
|
||||
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } =
|
||||
trpc.team.deleteTeamEmail.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
|
||||
@@ -23,11 +23,11 @@ import { AcceptTeamInvitationButton } from './accept-team-invitation-button';
|
||||
import { DeclineTeamInvitationButton } from './decline-team-invitation-button';
|
||||
|
||||
export const TeamInvitations = () => {
|
||||
const { data, isInitialLoading } = trpc.team.getTeamInvitations.useQuery();
|
||||
const { data, isLoading } = trpc.team.getTeamInvitations.useQuery();
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{data && data.length > 0 && !isInitialLoading && (
|
||||
{data && data.length > 0 && !isLoading && (
|
||||
<AnimateGenericFadeInOut>
|
||||
<Alert variant="secondary">
|
||||
<div className="flex h-full flex-row items-center p-2">
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
SKIP_QUERY_BATCH_META,
|
||||
} from '@documenso/lib/constants/trpc';
|
||||
import type { TemplateWithDetails } from '@documenso/prisma/types/template';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
@@ -32,7 +32,7 @@ import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type EditTemplateFormProps = {
|
||||
className?: string;
|
||||
initialTemplate: TemplateWithDetails;
|
||||
initialTemplate: TTemplate;
|
||||
isEnterprise: boolean;
|
||||
templateRootPath: string;
|
||||
};
|
||||
@@ -62,7 +62,6 @@ export const EditTemplateForm = ({
|
||||
const { data: template, refetch: refetchTemplate } = trpc.template.getTemplateById.useQuery(
|
||||
{
|
||||
templateId: initialTemplate.id,
|
||||
teamId: initialTemplate.teamId || undefined,
|
||||
},
|
||||
{
|
||||
initialData: initialTemplate,
|
||||
@@ -70,7 +69,7 @@ export const EditTemplateForm = ({
|
||||
},
|
||||
);
|
||||
|
||||
const { Recipient: recipients, Field: fields, templateDocumentData } = template;
|
||||
const { recipients, fields, templateDocumentData } = template;
|
||||
|
||||
const documentFlow: Record<EditTemplateStep, DocumentFlowStep> = {
|
||||
settings: {
|
||||
@@ -104,19 +103,6 @@ export const EditTemplateForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: setSigningOrderForTemplate } =
|
||||
trpc.template.setSigningOrderForTemplate.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.template.getTemplateById.setData(
|
||||
{
|
||||
templateId: initialTemplate.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: addTemplateFields } = trpc.field.addTemplateFields.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
@@ -129,7 +115,7 @@ export const EditTemplateForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation({
|
||||
const { mutateAsync: setRecipients } = trpc.recipient.setTemplateRecipients.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.template.getTemplateById.setData(
|
||||
@@ -141,31 +127,14 @@ export const EditTemplateForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateTypedSignature } =
|
||||
trpc.template.updateTemplateTypedSignatureSettings.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.template.getTemplateById.setData(
|
||||
{
|
||||
templateId: initialTemplate.id,
|
||||
},
|
||||
(oldData) => ({
|
||||
...(oldData || initialTemplate),
|
||||
...newData,
|
||||
id: Number(newData.id),
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
|
||||
try {
|
||||
await updateTemplateSettings({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
data: {
|
||||
title: data.title,
|
||||
externalId: data.externalId || null,
|
||||
visibility: data.visibility,
|
||||
globalAccessAuth: data.globalAccessAuth ?? null,
|
||||
globalActionAuth: data.globalActionAuth ?? null,
|
||||
},
|
||||
@@ -195,16 +164,16 @@ export const EditTemplateForm = ({
|
||||
) => {
|
||||
try {
|
||||
await Promise.all([
|
||||
setSigningOrderForTemplate({
|
||||
updateTemplateSettings({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
signingOrder: data.signingOrder,
|
||||
meta: {
|
||||
signingOrder: data.signingOrder,
|
||||
},
|
||||
}),
|
||||
|
||||
addTemplateSigners({
|
||||
setRecipients({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
signers: data.signers,
|
||||
recipients: data.signers,
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -228,10 +197,11 @@ export const EditTemplateForm = ({
|
||||
fields: data.fields,
|
||||
});
|
||||
|
||||
await updateTypedSignature({
|
||||
await updateTemplateSettings({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||
meta: {
|
||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
// Clear all field data from localStorage
|
||||
@@ -296,6 +266,7 @@ export const EditTemplateForm = ({
|
||||
<AddTemplateSettingsFormPartial
|
||||
key={recipients.length}
|
||||
template={template}
|
||||
currentTeamMemberRole={team?.currentTeamMember?.role}
|
||||
documentFlow={documentFlow.settings}
|
||||
recipients={recipients}
|
||||
fields={fields}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Button } from '@documenso/ui/primitives/button';
|
||||
import { TemplateDirectLinkDialog } from '../template-direct-link-dialog';
|
||||
|
||||
export type TemplateDirectLinkDialogWrapperProps = {
|
||||
template: Template & { directLink?: TemplateDirectLink | null; Recipient: Recipient[] };
|
||||
template: Template & { directLink?: TemplateDirectLink | null; recipients: Recipient[] };
|
||||
};
|
||||
|
||||
export const TemplateDirectLinkDialogWrapper = ({
|
||||
|
||||
@@ -69,21 +69,19 @@ export const TemplatePageViewDocumentsTable = ({
|
||||
Object.fromEntries(searchParams ?? []),
|
||||
);
|
||||
|
||||
const { data, isLoading, isInitialLoading, isLoadingError } =
|
||||
trpc.document.findDocuments.useQuery(
|
||||
{
|
||||
templateId,
|
||||
teamId: team?.id,
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
query: parsedSearchParams.query,
|
||||
source: parsedSearchParams.source,
|
||||
status: parsedSearchParams.status,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
const { data, isLoading, isLoadingError } = trpc.document.findDocuments.useQuery(
|
||||
{
|
||||
templateId,
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
query: parsedSearchParams.query,
|
||||
source: parsedSearchParams.source,
|
||||
status: parsedSearchParams.status,
|
||||
},
|
||||
{
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
const onPaginationChange = (page: number, perPage: number) => {
|
||||
updateSearchParams({
|
||||
@@ -117,7 +115,7 @@ export const TemplatePageViewDocumentsTable = ({
|
||||
accessorKey: 'recipient',
|
||||
cell: ({ row }) => (
|
||||
<StackAvatarsWithTooltip
|
||||
recipients={row.original.Recipient}
|
||||
recipients={row.original.recipients}
|
||||
documentStatus={row.original.status}
|
||||
/>
|
||||
),
|
||||
@@ -242,7 +240,7 @@ export const TemplatePageViewDocumentsTable = ({
|
||||
enable: isLoadingError,
|
||||
}}
|
||||
skeleton={{
|
||||
enable: isLoading && isInitialLoading,
|
||||
enable: isLoading,
|
||||
rows: 3,
|
||||
component: (
|
||||
<>
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { Template, User } from '@documenso/prisma/client';
|
||||
export type TemplatePageViewInformationProps = {
|
||||
userId: number;
|
||||
template: Template & {
|
||||
User: Pick<User, 'id' | 'name' | 'email'>;
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,7 +28,8 @@ export const TemplatePageViewInformation = ({
|
||||
return [
|
||||
{
|
||||
description: msg`Uploaded by`,
|
||||
value: userId === template.userId ? _(msg`You`) : template.User.name ?? template.User.email,
|
||||
value:
|
||||
userId === template.userId ? _(msg`You`) : (template.user.name ?? template.user.email),
|
||||
},
|
||||
{
|
||||
description: msg`Created`,
|
||||
|
||||
@@ -20,12 +20,10 @@ export type TemplatePageViewRecentActivityProps = {
|
||||
|
||||
export const TemplatePageViewRecentActivity = ({
|
||||
templateId,
|
||||
teamId,
|
||||
documentRootPath,
|
||||
}: TemplatePageViewRecentActivityProps) => {
|
||||
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
|
||||
templateId,
|
||||
teamId,
|
||||
orderByColumn: 'createdAt',
|
||||
orderByDirection: 'asc',
|
||||
perPage: 5,
|
||||
@@ -125,7 +123,7 @@ export const TemplatePageViewRecentActivity = ({
|
||||
{match(document.source)
|
||||
.with(DocumentSource.DOCUMENT, DocumentSource.TEMPLATE, () => (
|
||||
<Trans>
|
||||
Document created by <span className="font-bold">{document.User.name}</span>
|
||||
Document created by <span className="font-bold">{document.user.name}</span>
|
||||
</Trans>
|
||||
))
|
||||
.with(DocumentSource.TEMPLATE_DIRECT_LINK, () => (
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||
|
||||
export type TemplatePageViewRecipientsProps = {
|
||||
template: Template & {
|
||||
Recipient: Recipient[];
|
||||
recipients: Recipient[];
|
||||
};
|
||||
templateRootPath: string;
|
||||
};
|
||||
@@ -21,7 +21,7 @@ export const TemplatePageViewRecipients = ({
|
||||
}: TemplatePageViewRecipientsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const recipients = template.Recipient;
|
||||
const recipients = template.recipients;
|
||||
|
||||
return (
|
||||
<section className="dark:bg-background border-border bg-widget flex flex-col rounded-xl border">
|
||||
|
||||
@@ -14,6 +14,7 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
||||
|
||||
import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields';
|
||||
import { TemplateType } from '~/components/formatter/template-type';
|
||||
import { TemplateBulkSendDialog } from '~/components/templates/template-bulk-send-dialog';
|
||||
|
||||
import { DataTableActionDropdown } from '../data-table-action-dropdown';
|
||||
import { TemplateDirectLinkBadge } from '../template-direct-link-badge';
|
||||
@@ -54,10 +55,10 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
||||
redirect(templateRootPath);
|
||||
}
|
||||
|
||||
const { templateDocumentData, Field, Recipient: recipients, templateMeta } = template;
|
||||
const { templateDocumentData, fields, recipients, templateMeta } = template;
|
||||
|
||||
// Remap to fit the DocumentReadOnlyFields component.
|
||||
const readOnlyFields = Field.map((field) => {
|
||||
const readOnlyFields = fields.map((field) => {
|
||||
const recipient = recipients.find((recipient) => recipient.id === field.recipientId) || {
|
||||
name: '',
|
||||
email: '',
|
||||
@@ -66,8 +67,8 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
||||
|
||||
return {
|
||||
...field,
|
||||
Recipient: recipient,
|
||||
Signature: null,
|
||||
recipient,
|
||||
signature: null,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -111,6 +112,8 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
||||
<div className="mt-2 flex flex-row space-x-4 sm:mt-0 sm:self-end">
|
||||
<TemplateDirectLinkDialogWrapper template={template} />
|
||||
|
||||
<TemplateBulkSendDialog templateId={template.id} recipients={template.recipients} />
|
||||
|
||||
<Button className="w-full" asChild>
|
||||
<Link href={`${templateRootPath}/${template.id}/edit`}>
|
||||
<LucideEdit className="mr-1.5 h-3.5 w-3.5" />
|
||||
@@ -165,7 +168,7 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
||||
<UseTemplateDialog
|
||||
templateId={template.id}
|
||||
templateSigningOrder={template.templateMeta?.signingOrder}
|
||||
recipients={template.Recipient}
|
||||
recipients={template.recipients}
|
||||
documentRootPath={documentRootPath}
|
||||
trigger={
|
||||
<Button className="w-full">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Copy, Edit, MoreHorizontal, MoveRight, Share2Icon, Trash2 } from 'lucide-react';
|
||||
import { Copy, Edit, MoreHorizontal, MoveRight, Share2Icon, Trash2, Upload } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
import type { Recipient, Template, TemplateDirectLink } from '@documenso/prisma/client';
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@documenso/ui/primitives/dropdown-menu';
|
||||
|
||||
import { TemplateBulkSendDialog } from '~/components/templates/template-bulk-send-dialog';
|
||||
|
||||
import { DeleteTemplateDialog } from './delete-template-dialog';
|
||||
import { DuplicateTemplateDialog } from './duplicate-template-dialog';
|
||||
import { MoveTemplateDialog } from './move-template-dialog';
|
||||
@@ -25,7 +27,7 @@ import { TemplateDirectLinkDialog } from './template-direct-link-dialog';
|
||||
export type DataTableActionDropdownProps = {
|
||||
row: Template & {
|
||||
directLink?: Pick<TemplateDirectLink, 'token' | 'enabled'> | null;
|
||||
Recipient: Recipient[];
|
||||
recipients: Recipient[];
|
||||
};
|
||||
templateRootPath: string;
|
||||
teamId?: number;
|
||||
@@ -86,6 +88,17 @@ export const DataTableActionDropdown = ({
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<TemplateBulkSendDialog
|
||||
templateId={row.id}
|
||||
recipients={row.recipients}
|
||||
trigger={
|
||||
<div className="hover:bg-accent hover:text-accent-foreground relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors">
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
<Trans>Bulk Send via CSV</Trans>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<DropdownMenuItem
|
||||
disabled={!isOwner && !isTeamTemplate}
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AlertTriangle, Globe2Icon, InfoIcon, Link2Icon, Loader, LockIcon } from
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import type { FindTemplateRow } from '@documenso/lib/server-only/template/find-templates';
|
||||
import type { FindTemplateRow } from '@documenso/trpc/server/template-router/schema';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||
import { DataTable } from '@documenso/ui/primitives/data-table';
|
||||
@@ -146,7 +146,7 @@ export const TemplatesDataTable = ({
|
||||
templateId={row.original.id}
|
||||
templateSigningOrder={row.original.templateMeta?.signingOrder}
|
||||
documentDistributionMethod={row.original.templateMeta?.distributionMethod}
|
||||
recipients={row.original.Recipient}
|
||||
recipients={row.original.recipients}
|
||||
documentRootPath={documentRootPath}
|
||||
/>
|
||||
|
||||
|
||||
@@ -22,18 +22,13 @@ type DeleteTemplateDialogProps = {
|
||||
onOpenChange: (_open: boolean) => void;
|
||||
};
|
||||
|
||||
export const DeleteTemplateDialog = ({
|
||||
id,
|
||||
teamId,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: DeleteTemplateDialogProps) => {
|
||||
export const DeleteTemplateDialog = ({ id, open, onOpenChange }: DeleteTemplateDialogProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: deleteTemplate, isLoading } = trpcReact.template.deleteTemplate.useMutation({
|
||||
const { mutateAsync: deleteTemplate, isPending } = trpcReact.template.deleteTemplate.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
|
||||
@@ -56,7 +51,7 @@ export const DeleteTemplateDialog = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
|
||||
<Dialog open={open} onOpenChange={(value) => !isPending && onOpenChange(value)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
@@ -75,7 +70,7 @@ export const DeleteTemplateDialog = ({
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<Trans>Cancel</Trans>
|
||||
@@ -84,8 +79,8 @@ export const DeleteTemplateDialog = ({
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
loading={isLoading}
|
||||
onClick={async () => deleteTemplate({ templateId: id, teamId })}
|
||||
loading={isPending}
|
||||
onClick={async () => deleteTemplate({ templateId: id })}
|
||||
>
|
||||
<Trans>Delete</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -24,7 +24,6 @@ type DuplicateTemplateDialogProps = {
|
||||
|
||||
export const DuplicateTemplateDialog = ({
|
||||
id,
|
||||
teamId,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: DuplicateTemplateDialogProps) => {
|
||||
@@ -33,7 +32,7 @@ export const DuplicateTemplateDialog = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: duplicateTemplate, isLoading } =
|
||||
const { mutateAsync: duplicateTemplate, isPending } =
|
||||
trpcReact.template.duplicateTemplate.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
@@ -56,7 +55,7 @@ export const DuplicateTemplateDialog = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
|
||||
<Dialog open={open} onOpenChange={(value) => !isPending && onOpenChange(value)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
@@ -71,7 +70,7 @@ export const DuplicateTemplateDialog = ({
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
variant="secondary"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
@@ -80,11 +79,10 @@ export const DuplicateTemplateDialog = ({
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
loading={isLoading}
|
||||
loading={isPending}
|
||||
onClick={async () =>
|
||||
duplicateTemplate({
|
||||
templateId: id,
|
||||
teamId,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
||||
@@ -43,7 +43,7 @@ export const MoveTemplateDialog = ({ templateId, open, onOpenChange }: MoveTempl
|
||||
const [selectedTeamId, setSelectedTeamId] = useState<number | null>(null);
|
||||
|
||||
const { data: teams, isLoading: isLoadingTeams } = trpc.team.getTeams.useQuery();
|
||||
const { mutateAsync: moveTemplate, isLoading } = trpc.template.moveTemplateToTeam.useMutation({
|
||||
const { mutateAsync: moveTemplate, isPending } = trpc.template.moveTemplateToTeam.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
toast({
|
||||
@@ -130,8 +130,8 @@ export const MoveTemplateDialog = ({ templateId, open, onOpenChange }: MoveTempl
|
||||
<Button variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button onClick={onMove} loading={isLoading} disabled={!selectedTeamId || isLoading}>
|
||||
{isLoading ? <Trans>Moving...</Trans> : <Trans>Move</Trans>}
|
||||
<Button onClick={onMove} loading={isPending} disabled={!selectedTeamId || isPending}>
|
||||
{isPending ? <Trans>Moving...</Trans> : <Trans>Move</Trans>}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -31,7 +31,7 @@ type NewTemplateDialogProps = {
|
||||
templateRootPath: string;
|
||||
};
|
||||
|
||||
export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialogProps) => {
|
||||
export const NewTemplateDialog = ({ templateRootPath }: NewTemplateDialogProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { data: session } = useSession();
|
||||
@@ -58,7 +58,6 @@ export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialo
|
||||
});
|
||||
|
||||
const { id } = await createTemplate({
|
||||
teamId,
|
||||
title: file.name,
|
||||
templateDocumentDataId,
|
||||
});
|
||||
|
||||
@@ -46,12 +46,10 @@ import {
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
type TemplateDirectLinkDialogProps = {
|
||||
template: Template & {
|
||||
directLink?: Pick<TemplateDirectLink, 'token' | 'enabled'> | null;
|
||||
Recipient: Recipient[];
|
||||
recipients: Recipient[];
|
||||
};
|
||||
open: boolean;
|
||||
onOpenChange: (_open: boolean) => void;
|
||||
@@ -68,8 +66,6 @@ export const TemplateDirectLinkDialog = ({
|
||||
const { quota, remaining } = useLimits();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const team = useOptionalCurrentTeam();
|
||||
|
||||
const [, copy] = useCopyToClipboard();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -81,13 +77,13 @@ export const TemplateDirectLinkDialog = ({
|
||||
);
|
||||
|
||||
const validDirectTemplateRecipients = useMemo(
|
||||
() => template.Recipient.filter((recipient) => recipient.role !== RecipientRole.CC),
|
||||
[template.Recipient],
|
||||
() => template.recipients.filter((recipient) => recipient.role !== RecipientRole.CC),
|
||||
[template.recipients],
|
||||
);
|
||||
|
||||
const {
|
||||
mutateAsync: createTemplateDirectLink,
|
||||
isLoading: isCreatingTemplateDirectLink,
|
||||
isPending: isCreatingTemplateDirectLink,
|
||||
reset: resetCreateTemplateDirectLink,
|
||||
} = trpcReact.template.createTemplateDirectLink.useMutation({
|
||||
onSuccess: (data) => {
|
||||
@@ -108,7 +104,7 @@ export const TemplateDirectLinkDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: toggleTemplateDirectLink, isLoading: isTogglingTemplateAccess } =
|
||||
const { mutateAsync: toggleTemplateDirectLink, isPending: isTogglingTemplateAccess } =
|
||||
trpcReact.template.toggleTemplateDirectLink.useMutation({
|
||||
onSuccess: (data) => {
|
||||
const enabledDescription = msg`Direct link signing has been enabled`;
|
||||
@@ -131,7 +127,7 @@ export const TemplateDirectLinkDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteTemplateDirectLink, isLoading: isDeletingTemplateDirectLink } =
|
||||
const { mutateAsync: deleteTemplateDirectLink, isPending: isDeletingTemplateDirectLink } =
|
||||
trpcReact.template.deleteTemplateDirectLink.useMutation({
|
||||
onSuccess: () => {
|
||||
onOpenChange(false);
|
||||
@@ -174,7 +170,6 @@ export const TemplateDirectLinkDialog = ({
|
||||
|
||||
await createTemplateDirectLink({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
directRecipientId: recipientId,
|
||||
});
|
||||
};
|
||||
@@ -327,7 +322,7 @@ export const TemplateDirectLinkDialog = ({
|
||||
</div>
|
||||
|
||||
{/* Prevent creating placeholder direct template recipient if the email already exists. */}
|
||||
{!template.Recipient.some(
|
||||
{!template.recipients.some(
|
||||
(recipient) => recipient.email === DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
) && (
|
||||
<DialogFooter className="mx-auto">
|
||||
@@ -345,7 +340,6 @@ export const TemplateDirectLinkDialog = ({
|
||||
onClick={async () =>
|
||||
createTemplateDirectLink({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
||||
@@ -7,15 +7,17 @@ import { useRouter } from 'next/navigation';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { InfoIcon, Plus } from 'lucide-react';
|
||||
import { InfoIcon, Plus, Upload, X } from 'lucide-react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import {
|
||||
TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX,
|
||||
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
|
||||
} from '@documenso/lib/constants/template';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentDistributionMethod, DocumentSigningOrder } from '@documenso/prisma/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -45,11 +47,14 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitive
|
||||
import type { Toast } from '@documenso/ui/primitives/use-toast';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
const ZAddRecipientsForNewDocumentSchema = z
|
||||
.object({
|
||||
distributeDocument: z.boolean(),
|
||||
useCustomDocument: z.boolean().default(false),
|
||||
customDocumentData: z
|
||||
.any()
|
||||
.refine((data) => data instanceof File || data === undefined)
|
||||
.optional(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
@@ -113,12 +118,12 @@ export function UseTemplateDialog({
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const team = useOptionalCurrentTeam();
|
||||
|
||||
const form = useForm<TAddRecipientsForNewDocumentSchema>({
|
||||
resolver: zodResolver(ZAddRecipientsForNewDocumentSchema),
|
||||
defaultValues: {
|
||||
distributeDocument: false,
|
||||
useCustomDocument: false,
|
||||
customDocumentData: undefined,
|
||||
recipients: recipients
|
||||
.sort((a, b) => (a.signingOrder || 0) - (b.signingOrder || 0))
|
||||
.map((recipient) => {
|
||||
@@ -145,11 +150,18 @@ export function UseTemplateDialog({
|
||||
|
||||
const onSubmit = async (data: TAddRecipientsForNewDocumentSchema) => {
|
||||
try {
|
||||
let customDocumentDataId: string | undefined = undefined;
|
||||
|
||||
if (data.useCustomDocument && data.customDocumentData) {
|
||||
const customDocumentData = await putPdfFile(data.customDocumentData);
|
||||
customDocumentDataId = customDocumentData.id;
|
||||
}
|
||||
|
||||
const { id } = await createDocumentFromTemplate({
|
||||
templateId,
|
||||
teamId: team?.id,
|
||||
recipients: data.recipients,
|
||||
distributeDocument: data.distributeDocument,
|
||||
customDocumentDataId,
|
||||
});
|
||||
|
||||
toast({
|
||||
@@ -300,89 +312,245 @@ export function UseTemplateDialog({
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{recipients.length > 0 && (
|
||||
<div className="mt-4 flex flex-row items-center">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="distributeDocument"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex flex-row items-center">
|
||||
<Checkbox
|
||||
id="distributeDocument"
|
||||
className="h-5 w-5"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
|
||||
{documentDistributionMethod === DocumentDistributionMethod.EMAIL && (
|
||||
<label
|
||||
className="text-muted-foreground ml-2 flex items-center text-sm"
|
||||
htmlFor="distributeDocument"
|
||||
>
|
||||
<Trans>Send document</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="mx-1 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
|
||||
<p>
|
||||
<Trans>
|
||||
The document will be immediately sent to recipients if this
|
||||
is checked.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
Otherwise, the document will be created as a draft.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{documentDistributionMethod === DocumentDistributionMethod.NONE && (
|
||||
<label
|
||||
className="text-muted-foreground ml-2 flex items-center text-sm"
|
||||
htmlFor="distributeDocument"
|
||||
>
|
||||
<Trans>Create as pending</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="mx-1 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
|
||||
<p>
|
||||
<Trans>
|
||||
Create the document as pending and ready to sign.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Trans>We won't send anything to notify recipients.</Trans>
|
||||
</p>
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
We will generate signing links for you, which you can send
|
||||
to the recipients through your method of choice.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="useCustomDocument"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex flex-row items-center">
|
||||
<Checkbox
|
||||
id="useCustomDocument"
|
||||
className="h-5 w-5"
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked);
|
||||
if (!checked) {
|
||||
form.setValue('customDocumentData', undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="text-muted-foreground ml-2 flex items-center text-sm"
|
||||
htmlFor="useCustomDocument"
|
||||
>
|
||||
<Trans>Upload custom document</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="mx-1 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
|
||||
<p>
|
||||
<Trans>
|
||||
Upload a custom document to use instead of the template's default
|
||||
document
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</label>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{form.watch('useCustomDocument') && (
|
||||
<div className="my-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customDocumentData"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="w-full space-y-4">
|
||||
<label
|
||||
className={cn(
|
||||
'text-muted-foreground hover:border-muted-foreground/50 group relative flex min-h-[150px] cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-gray-300 px-6 py-10 transition-colors',
|
||||
{
|
||||
'border-destructive hover:border-destructive':
|
||||
form.formState.errors.customDocumentData,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="text-center">
|
||||
{!field.value && (
|
||||
<>
|
||||
<Upload className="text-muted-foreground/50 mx-auto h-10 w-10" />
|
||||
<div className="mt-4 flex text-sm leading-6">
|
||||
<span className="text-muted-foreground relative">
|
||||
<Trans>
|
||||
<span className="text-primary font-semibold">
|
||||
Click to upload
|
||||
</span>{' '}
|
||||
or drag and drop
|
||||
</Trans>
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-muted-foreground/80 text-xs">
|
||||
PDF files only
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{field.value && (
|
||||
<div className="text-muted-foreground space-y-1">
|
||||
<p className="text-sm font-medium">{field.value.name}</p>
|
||||
<p className="text-muted-foreground/60 text-xs">
|
||||
{(field.value.size / (1024 * 1024)).toFixed(2)} MB
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
className="absolute h-full w-full opacity-0"
|
||||
accept=".pdf,application/pdf"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
|
||||
if (!file) {
|
||||
field.onChange(undefined);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.type !== 'application/pdf') {
|
||||
form.setError('customDocumentData', {
|
||||
type: 'manual',
|
||||
message: _(msg`Please select a PDF file`),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024) {
|
||||
form.setError('customDocumentData', {
|
||||
type: 'manual',
|
||||
message: _(
|
||||
msg`File size exceeds the limit of ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB`,
|
||||
),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
field.onChange(file);
|
||||
}}
|
||||
/>
|
||||
|
||||
{field.value && (
|
||||
<div className="absolute right-2 top-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
field.onChange(undefined);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<div className="sr-only">
|
||||
<Trans>Clear file</Trans>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{recipients.length > 0 && (
|
||||
<div className="mt-4 flex flex-row items-center">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="distributeDocument"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex flex-row items-center">
|
||||
<Checkbox
|
||||
id="distributeDocument"
|
||||
className="h-5 w-5"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
|
||||
{documentDistributionMethod === DocumentDistributionMethod.EMAIL && (
|
||||
<label
|
||||
className="text-muted-foreground ml-2 flex items-center text-sm"
|
||||
htmlFor="distributeDocument"
|
||||
>
|
||||
<Trans>Send document</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="mx-1 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
|
||||
<p>
|
||||
<Trans>
|
||||
The document will be immediately sent to recipients if this is
|
||||
checked.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
Otherwise, the document will be created as a draft.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{documentDistributionMethod === DocumentDistributionMethod.NONE && (
|
||||
<label
|
||||
className="text-muted-foreground ml-2 flex items-center text-sm"
|
||||
htmlFor="distributeDocument"
|
||||
>
|
||||
<Trans>Create as pending</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="mx-1 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-muted-foreground z-[99999] max-w-md space-y-2 p-4">
|
||||
<p>
|
||||
<Trans>Create the document as pending and ready to sign.</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Trans>We won't send anything to notify recipients.</Trans>
|
||||
</p>
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
We will generate signing links for you, which you can send to
|
||||
the recipients through your method of choice.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<DialogFooter className="mt-4">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="secondary">
|
||||
<Trans>Close</Trans>
|
||||
|
||||
@@ -66,6 +66,7 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
|
||||
const { data: auditLogs } = await findDocumentAuditLogs({
|
||||
documentId: documentId,
|
||||
userId: document.userId,
|
||||
teamId: document.teamId || undefined,
|
||||
perPage: 100_000,
|
||||
});
|
||||
|
||||
@@ -103,7 +104,7 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
|
||||
<span className="font-medium">{_(msg`Owner`)}</span>
|
||||
|
||||
<span className="mt-1 block break-words">
|
||||
{document.User.name} ({document.User.email})
|
||||
{document.user.name} ({document.user.email})
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -139,7 +140,7 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
|
||||
<p className="font-medium">{_(msg`Recipients`)}</p>
|
||||
|
||||
<ul className="mt-1 list-inside list-disc">
|
||||
{document.Recipient.map((recipient) => (
|
||||
{document.recipients.map((recipient) => (
|
||||
<li key={recipient.id}>
|
||||
<span className="text-muted-foreground">
|
||||
[{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
@@ -7,8 +5,10 @@ import { useLingui } from '@lingui/react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { renderSVG } from 'uqr';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { APP_I18N_OPTIONS, ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
@@ -73,6 +73,8 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
||||
id: documentId,
|
||||
}).catch(() => null);
|
||||
|
||||
const documentAccessToken = document?.documentAccessToken?.token;
|
||||
|
||||
if (!document) {
|
||||
return redirect('/');
|
||||
}
|
||||
@@ -86,7 +88,7 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
||||
});
|
||||
|
||||
const isOwner = (email: string) => {
|
||||
return email.toLowerCase() === document.User.email.toLowerCase();
|
||||
return email.toLowerCase() === document.user.email.toLowerCase();
|
||||
};
|
||||
|
||||
const getDevice = (userAgent?: string | null) => {
|
||||
@@ -104,7 +106,7 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
||||
};
|
||||
|
||||
const getAuthenticationLevel = (recipientId: number) => {
|
||||
const recipient = document.Recipient.find((recipient) => recipient.id === recipientId);
|
||||
const recipient = document.recipients.find((recipient) => recipient.id === recipientId);
|
||||
|
||||
if (!recipient) {
|
||||
return 'Unknown';
|
||||
@@ -157,9 +159,11 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
||||
};
|
||||
|
||||
const getRecipientSignatureField = (recipientId: number) => {
|
||||
return document.Recipient.find((recipient) => recipient.id === recipientId)?.Field.find(
|
||||
(field) => field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE,
|
||||
);
|
||||
return document.recipients
|
||||
.find((recipient) => recipient.id === recipientId)
|
||||
?.fields.find(
|
||||
(field) => field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -181,7 +185,7 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
||||
</TableHeader>
|
||||
|
||||
<TableBody className="print:text-xs">
|
||||
{document.Recipient.map((recipient, i) => {
|
||||
{document.recipients.map((recipient, i) => {
|
||||
const logs = getRecipientAuditLogs(recipient.id);
|
||||
const signature = getRecipientSignatureField(recipient.id);
|
||||
|
||||
@@ -209,17 +213,17 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
||||
boxShadow: `0px 0px 0px 4.88px rgba(122, 196, 85, 0.1), 0px 0px 0px 1.22px rgba(122, 196, 85, 0.6), 0px 0px 0px 0.61px rgba(122, 196, 85, 1)`,
|
||||
}}
|
||||
>
|
||||
{signature.Signature?.signatureImageAsBase64 && (
|
||||
{signature.signature?.signatureImageAsBase64 && (
|
||||
<img
|
||||
src={`${signature.Signature?.signatureImageAsBase64}`}
|
||||
src={`${signature.signature?.signatureImageAsBase64}`}
|
||||
alt="Signature"
|
||||
className="max-h-12 max-w-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{signature.Signature?.typedSignature && (
|
||||
{signature.signature?.typedSignature && (
|
||||
<p className="font-signature text-center text-sm">
|
||||
{signature.Signature?.typedSignature}
|
||||
{signature.signature?.typedSignature}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -303,17 +307,27 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="my-8 flex flex-row-reverse items-end justify-between px-8">
|
||||
<div className="flex items-end justify-end gap-x-4">
|
||||
<div
|
||||
className="flex h-24 w-24 justify-center"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderSVG(`${WEBAPP_BASE_URL}/q/${documentAccessToken}`, {
|
||||
ecc: 'Q',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="my-8 flex-row-reverse">
|
||||
<div className="flex items-end justify-end gap-x-4">
|
||||
<p className="flex-shrink-0 text-sm font-medium print:text-xs">
|
||||
{_(msg`Signing certificate provided by`)}:
|
||||
</p>
|
||||
<div>
|
||||
<p className="flex-shrink-0 text-sm print:text-xs">
|
||||
{_(msg`Signing certificate provided by`)}:
|
||||
</p>
|
||||
|
||||
<Logo className="max-h-6 print:max-h-4" />
|
||||
<Logo className="mt-2 max-h-6 print:max-h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import { useSession } from 'next-auth/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import type { TemplateWithDetails } from '@documenso/prisma/types/template';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
import type { Field } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentFlowFormContainerActions,
|
||||
DocumentFlowFormContainerContent,
|
||||
@@ -40,8 +41,8 @@ export type TConfigureDirectTemplateFormSchema = z.infer<typeof ZConfigureDirect
|
||||
export type ConfigureDirectTemplateFormProps = {
|
||||
flowStep: DocumentFlowStep;
|
||||
isDocumentPdfLoaded: boolean;
|
||||
template: Omit<TemplateWithDetails, 'User'>;
|
||||
directTemplateRecipient: Recipient & { Field: Field[] };
|
||||
template: Omit<TTemplate, 'user'>;
|
||||
directTemplateRecipient: Recipient & { fields: Field[] };
|
||||
initialEmail?: string;
|
||||
onSubmit: (_data: TConfigureDirectTemplateFormSchema) => void;
|
||||
};
|
||||
@@ -57,10 +58,10 @@ export const ConfigureDirectTemplateFormPartial = ({
|
||||
const { _ } = useLingui();
|
||||
const { data: session } = useSession();
|
||||
|
||||
const { Recipient } = template;
|
||||
const { recipients } = template;
|
||||
const { derivedRecipientAccessAuth } = useRequiredDocumentAuthContext();
|
||||
|
||||
const recipientsWithBlankDirectRecipientEmail = Recipient.map((recipient) => {
|
||||
const recipientsWithBlankDirectRecipientEmail = recipients.map((recipient) => {
|
||||
if (recipient.id === directTemplateRecipient.id) {
|
||||
return {
|
||||
...recipient,
|
||||
@@ -74,7 +75,7 @@ export const ConfigureDirectTemplateFormPartial = ({
|
||||
const form = useForm<TConfigureDirectTemplateFormSchema>({
|
||||
resolver: zodResolver(
|
||||
ZConfigureDirectTemplateFormSchema.superRefine((items, ctx) => {
|
||||
if (template.Recipient.map((recipient) => recipient.email).includes(items.email)) {
|
||||
if (template.recipients.map((recipient) => recipient.email).includes(items.email)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: _(msg`Email cannot already exist in the template`),
|
||||
@@ -96,7 +97,7 @@ export const ConfigureDirectTemplateFormPartial = ({
|
||||
|
||||
<DocumentFlowFormContainerContent>
|
||||
{isDocumentPdfLoaded &&
|
||||
directTemplateRecipient.Field.map((field, index) => (
|
||||
directTemplateRecipient.fields.map((field, index) => (
|
||||
<ShowFieldItem
|
||||
key={index}
|
||||
field={field}
|
||||
|
||||
@@ -8,9 +8,9 @@ import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import type { Field } from '@documenso/prisma/client';
|
||||
import { type Recipient } from '@documenso/prisma/client';
|
||||
import type { TemplateWithDetails } from '@documenso/prisma/types/template';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { DocumentFlowFormContainer } from '@documenso/ui/primitives/document-flow/document-flow-root';
|
||||
@@ -28,9 +28,9 @@ import type { DirectTemplateLocalField } from './sign-direct-template';
|
||||
import { SignDirectTemplateForm } from './sign-direct-template';
|
||||
|
||||
export type TemplatesDirectPageViewProps = {
|
||||
template: Omit<TemplateWithDetails, 'User'>;
|
||||
template: Omit<TTemplate, 'user'>;
|
||||
directTemplateToken: string;
|
||||
directTemplateRecipient: Recipient & { Field: Field[] };
|
||||
directTemplateRecipient: Recipient & { fields: Field[] };
|
||||
};
|
||||
|
||||
type DirectTemplateStep = 'configure' | 'sign';
|
||||
@@ -164,7 +164,7 @@ export const DirectTemplatePageView = ({
|
||||
<SignDirectTemplateForm
|
||||
flowStep={directTemplateFlow.sign}
|
||||
directRecipient={recipient}
|
||||
directRecipientFields={directTemplateRecipient.Field}
|
||||
directRecipientFields={directTemplateRecipient.fields}
|
||||
template={template}
|
||||
onSubmit={onSignDirectTemplateSubmit}
|
||||
/>
|
||||
|
||||
@@ -41,7 +41,7 @@ export default async function TemplatesDirectPage({ params }: TemplatesDirectPag
|
||||
notFound();
|
||||
}
|
||||
|
||||
const directTemplateRecipient = template.Recipient.find(
|
||||
const directTemplateRecipient = template.recipients.find(
|
||||
(recipient) => recipient.id === template.directLink?.directTemplateRecipientId,
|
||||
);
|
||||
|
||||
@@ -81,7 +81,7 @@ export default async function TemplatesDirectPage({ params }: TemplatesDirectPag
|
||||
<div className="text-muted-foreground mb-8 mt-2.5 flex items-center gap-x-2">
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
<p className="text-muted-foreground/80">
|
||||
<Plural value={template.Recipient.length} one="# recipient" other="# recipients" />
|
||||
<Plural value={template.recipients.length} one="# recipient" other="# recipients" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ import {
|
||||
ZRadioFieldMeta,
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
|
||||
import type { Field, Recipient, Signature } from '@documenso/prisma/client';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
import type { TemplateWithDetails } from '@documenso/prisma/types/template';
|
||||
import type {
|
||||
TRemovedSignedFieldWithTokenMutationSchema,
|
||||
TSignFieldWithTokenMutationSchema,
|
||||
@@ -55,13 +55,13 @@ export type SignDirectTemplateFormProps = {
|
||||
flowStep: DocumentFlowStep;
|
||||
directRecipient: Recipient;
|
||||
directRecipientFields: Field[];
|
||||
template: Omit<TemplateWithDetails, 'User'>;
|
||||
template: Omit<TTemplate, 'user'>;
|
||||
onSubmit: (_data: DirectTemplateLocalField[]) => Promise<void>;
|
||||
};
|
||||
|
||||
export type DirectTemplateLocalField = Field & {
|
||||
signedValue?: TSignFieldWithTokenMutationSchema;
|
||||
Signature?: Signature;
|
||||
signature?: Signature;
|
||||
};
|
||||
|
||||
export const SignDirectTemplateForm = ({
|
||||
@@ -95,7 +95,7 @@ export const SignDirectTemplateForm = ({
|
||||
};
|
||||
|
||||
if (field.type === FieldType.SIGNATURE) {
|
||||
tempField.Signature = {
|
||||
tempField.signature = {
|
||||
id: 1,
|
||||
created: new Date(),
|
||||
recipientId: 1,
|
||||
@@ -127,7 +127,7 @@ export const SignDirectTemplateForm = ({
|
||||
customText: '',
|
||||
inserted: false,
|
||||
signedValue: undefined,
|
||||
Signature: undefined,
|
||||
signature: undefined,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
|
||||
import type { Document, DocumentData } from '@documenso/prisma/client';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export type DocumentDownloadButtonProps = {
|
||||
document: Pick<Document, 'title'> & {
|
||||
documentData: DocumentData;
|
||||
};
|
||||
};
|
||||
|
||||
export const DocumentDownloadButton = ({ document }: DocumentDownloadButtonProps) => {
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
if (!document) {
|
||||
throw new Error('No document available');
|
||||
}
|
||||
|
||||
await downloadPDF({ documentData: document.documentData, fileName: document.title });
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`An error occurred while downloading your document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button className="w-full" onClick={onDownloadClick}>
|
||||
<Download className="-ml-1 mr-2 inline h-4 w-4" />
|
||||
<Trans>Download</Trans>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
35
apps/web/src/app/(share)/q/[token]/layout.tsx
Normal file
35
apps/web/src/app/(share)/q/[token]/layout.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import type { TGetTeamsResponse } 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 { NextAuthProvider } from '~/providers/next-auth';
|
||||
|
||||
export type SigningLayoutProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default async function SigningLayout({ children }: SigningLayoutProps) {
|
||||
await setupI18nSSR();
|
||||
|
||||
const { user, session } = await getServerComponentSession();
|
||||
|
||||
let teams: TGetTeamsResponse = [];
|
||||
|
||||
if (user && session) {
|
||||
teams = await getTeams({ userId: user.id });
|
||||
}
|
||||
|
||||
return (
|
||||
<NextAuthProvider session={session}>
|
||||
<div className="min-h-screen">
|
||||
{user && <AuthenticatedHeader user={user} teams={teams} />}
|
||||
|
||||
<main className="mb-8 mt-8 px-4 md:mb-12 md:mt-12 md:px-8">{children}</main>
|
||||
</div>
|
||||
</NextAuthProvider>
|
||||
);
|
||||
}
|
||||
68
apps/web/src/app/(share)/q/[token]/page.tsx
Normal file
68
apps/web/src/app/(share)/q/[token]/page.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { getDocumentByAccessToken } from '@documenso/lib/server-only/document/get-document-by-access-token';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
||||
|
||||
import { DocumentDownloadButton } from './document-download-button';
|
||||
|
||||
export type DocumentAccessPageProps = {
|
||||
params: {
|
||||
token?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function DocumentAccessPage({ params: { token } }: DocumentAccessPageProps) {
|
||||
await setupI18nSSR();
|
||||
|
||||
if (!token) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { document } = await getDocumentByAccessToken({ token });
|
||||
const { documentData, documentMeta } = document;
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-screen-xl md:px-8">
|
||||
<h1
|
||||
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||
title={document.title}
|
||||
>
|
||||
{document.title}
|
||||
</h1>
|
||||
|
||||
<div className="mt-8 grid grid-cols-12 gap-y-8 lg:gap-x-8 lg:gap-y-0">
|
||||
<Card
|
||||
className="col-span-12 rounded-xl before:rounded-xl lg:col-span-7 xl:col-span-8"
|
||||
gradient
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<LazyPDFViewer
|
||||
key={documentData.id}
|
||||
documentData={documentData}
|
||||
document={document}
|
||||
password={documentMeta?.password}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="col-span-12 lg:col-span-5 xl:col-span-4">
|
||||
<section className="border-border bg-widget flex flex-col rounded-xl border pb-4 pt-6">
|
||||
<div className="flex flex-row items-center justify-between px-4">
|
||||
<h3 className="text-foreground text-2xl font-semibold">Download document</h3>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground mt-2 px-4 text-sm">
|
||||
Download the document as a PDF file.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 border-t px-4 pt-4">
|
||||
<DocumentDownloadButton document={document} />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -52,15 +52,15 @@ export async function GET(_request: Request, { params: { slug } }: SharePageOpen
|
||||
const isRecipient = 'Signature' in recipientOrSender;
|
||||
|
||||
const signatureImage = match(recipientOrSender)
|
||||
.with({ Signature: P.array(P._) }, (recipient) => {
|
||||
return recipient.Signature?.[0]?.signatureImageAsBase64 || null;
|
||||
.with({ signatures: P.array(P._) }, (recipient) => {
|
||||
return recipient.signatures?.[0]?.signatureImageAsBase64 || null;
|
||||
})
|
||||
.otherwise((sender) => {
|
||||
return sender.signature || null;
|
||||
});
|
||||
|
||||
const signatureName = match(recipientOrSender)
|
||||
.with({ Signature: P.array(P._) }, (recipient) => {
|
||||
.with({ signatures: P.array(P._) }, (recipient) => {
|
||||
return recipient.name || recipient.email;
|
||||
})
|
||||
.otherwise((sender) => {
|
||||
|
||||
@@ -81,12 +81,12 @@ export const CheckboxField = ({
|
||||
);
|
||||
}, [checkedValues, validationSign, checkboxValidationLength]);
|
||||
|
||||
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
|
||||
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||
|
||||
const {
|
||||
mutateAsync: removeSignedFieldWithToken,
|
||||
isLoading: isRemoveSignedFieldWithTokenLoading,
|
||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||
|
||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user