mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 03:01:59 +10:00
Compare commits
1 Commits
fix/improv
...
chore/opti
| Author | SHA1 | Date | |
|---|---|---|---|
| d6437d6533 |
20
README.md
20
README.md
@ -78,7 +78,7 @@ The current project goal is to <b>[release a production ready version](https://g
|
|||||||
Documenso is built using awesome open source tech including:
|
Documenso is built using awesome open source tech including:
|
||||||
|
|
||||||
- [Typescript](https://www.typescriptlang.org/)
|
- [Typescript](https://www.typescriptlang.org/)
|
||||||
- [Javascript (when necessary)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
- [Javascript (when neccessary)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
||||||
- [NextJS (JS Fullstack Framework)](https://nextjs.org/)
|
- [NextJS (JS Fullstack Framework)](https://nextjs.org/)
|
||||||
- [Postgres SQL (Database)](https://www.postgresql.org/)
|
- [Postgres SQL (Database)](https://www.postgresql.org/)
|
||||||
- [Prisma (ORM - Object-relational mapping)](https://www.prisma.io/)
|
- [Prisma (ORM - Object-relational mapping)](https://www.prisma.io/)
|
||||||
@ -96,7 +96,7 @@ Documenso is built using awesome open source tech including:
|
|||||||
To run Documenso locally you need
|
To run Documenso locally you need
|
||||||
|
|
||||||
- [Node.js (Version: >=18.x)](https://nodejs.org/en/download/)
|
- [Node.js (Version: >=18.x)](https://nodejs.org/en/download/)
|
||||||
- Node Package Manager NPM - included in Node.js
|
- Node Package Manger NPM - included in Node.js
|
||||||
- [PostgreSQL (local or remote)](https://www.postgresql.org/download/)
|
- [PostgreSQL (local or remote)](https://www.postgresql.org/download/)
|
||||||
|
|
||||||
## Developer Quickstart
|
## Developer Quickstart
|
||||||
@ -128,7 +128,7 @@ Your database will also be available on port `5432`. You can connect to it using
|
|||||||
|
|
||||||
## Developer Setup
|
## Developer Setup
|
||||||
|
|
||||||
Follow these steps to setup documenso on you local machine:
|
Follow these steps to setup documenso on you local machnine:
|
||||||
|
|
||||||
- [Clone the repository](https://help.github.com/articles/cloning-a-repository/) it to your local device.
|
- [Clone the repository](https://help.github.com/articles/cloning-a-repository/) it to your local device.
|
||||||
```sh
|
```sh
|
||||||
@ -138,12 +138,12 @@ Follow these steps to setup documenso on you local machine:
|
|||||||
- Rename <code>.env.example</code> to <code>.env</code>
|
- Rename <code>.env.example</code> to <code>.env</code>
|
||||||
- Set DATABASE_URL value in .env file
|
- Set DATABASE_URL value in .env file
|
||||||
- You can use the provided test database url (may be wiped at any point)
|
- You can use the provided test database url (may be wiped at any point)
|
||||||
- Or setup a local postgres sql instance (recommended)
|
- Or setup a local postgres sql instance (recommened)
|
||||||
- Create the database scheme by running <code>db-migrate:dev</code>
|
- Create the database scheme by running <code>db-migrate:dev</code>
|
||||||
- Setup your mail provider
|
- Setup your mail provider
|
||||||
- Set <code>SENDGRID_API_KEY</code> value in .env file
|
- Set <code>SENDGRID_API_KEY</code> value in .env file
|
||||||
- You need a SendGrid account, which you can create [here](https://signup.sendgrid.com/).
|
- You need a SendGrid account, which you can create [here](https://signup.sendgrid.com/).
|
||||||
- Documenso uses [Nodemailer](https://nodemailer.com/about/) so you can easily use your own SMTP server by setting the <code>SMTP\_\* variables</code> in your .env
|
- Documenso uses [Nodemailer](https://nodemailer.com/about/) so you can easily use your own SMTP server by setting the <code>SMTP\_\* varibles</code> in your .env
|
||||||
- Run <code>npm run dev</code> root directory to start
|
- Run <code>npm run dev</code> root directory to start
|
||||||
- Register a new user at http://localhost:3000/signup
|
- Register a new user at http://localhost:3000/signup
|
||||||
|
|
||||||
@ -154,20 +154,20 @@ Follow these steps to setup documenso on you local machine:
|
|||||||
|
|
||||||
- Optional: Create your own signing certificate
|
- Optional: Create your own signing certificate
|
||||||
- A demo certificate is provided in /app/web/ressources/certificate.p12
|
- A demo certificate is provided in /app/web/ressources/certificate.p12
|
||||||
- To generate your own using these steps and a linux Terminal or Windows Linux Subsystem see **Create your own signing certificate**.
|
- To generate you own using these steps and a linux Terminal or Windows Linux Subsystem see **Create your own signging certificate**.
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
- If you pull the newest version from main, using <code>git pull</code>, it may be necessary to regenerate your database client
|
- If you pull the newest version from main, using <code>git pull</code>, it may be neccessary to regenerate your database client
|
||||||
- You can do this by running the generate command in /packages/prisma:
|
- You can do this by running the generate command in /packages/prisma:
|
||||||
```sh
|
```sh
|
||||||
npx prisma generate
|
npx prisma generate
|
||||||
```
|
```
|
||||||
- This is not necessary on first clone
|
- This is not neccessary on first clone
|
||||||
|
|
||||||
# Creating your own signing certificate
|
# Creating your own signging certificate
|
||||||
|
|
||||||
For the digital signature of your documents you need a signing certificate in .p12 formate (public and private key). You can buy one (not recommended for dev) or use the steps to create a self-signed one:
|
For the digital signature of you documents you need a signign certificate in .p12 formate (public and private key). You can buy one (not recommended for dev) or use the steps to create a self-signed one:
|
||||||
|
|
||||||
1. Generate a private key using the OpenSSL command. You can run the following command to generate a 2048-bit RSA key:\
|
1. Generate a private key using the OpenSSL command. You can run the following command to generate a 2048-bit RSA key:\
|
||||||
<code>openssl genrsa -out private.key 2048</code>
|
<code>openssl genrsa -out private.key 2048</code>
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": ["next/babel"],
|
|
||||||
"plugins": []
|
|
||||||
}
|
|
||||||
@ -1,3 +1,8 @@
|
|||||||
{
|
{
|
||||||
"extends": ["next/babel", "next/core-web-vitals"]
|
"extends": [
|
||||||
}
|
"next/core-web-vitals"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"react/no-unescaped-entities": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -80,11 +80,12 @@ export default function PDFSigner(props: any) {
|
|||||||
: props.document.User.email}{" "}
|
: props.document.User.email}{" "}
|
||||||
would like you to sign this document.
|
would like you to sign this document.
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-3 text-sm md:mt-0 md:ml-6 text-right md:text-inherit">
|
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||||
<Button
|
<Button
|
||||||
disabled={!signingDone}
|
disabled={!signingDone}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
icon={CheckBadgeIcon}
|
icon={CheckBadgeIcon}
|
||||||
|
className="float-right"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signDocument(props.document, localSignatures, `${router.query.token}`).then(
|
signDocument(props.document, localSignatures, `${router.query.token}`).then(
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@ -45,11 +45,10 @@ export default function RecipientSelector(props: any) {
|
|||||||
{props?.recipients.map((recipient: any) => (
|
{props?.recipients.map((recipient: any) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={recipient?.id}
|
key={recipient?.id}
|
||||||
disabled={!recipient?.email}
|
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
classNames(
|
classNames(
|
||||||
active ? "bg-neon-dark text-white" : "text-gray-900",
|
active ? "bg-neon-dark text-white" : "text-gray-900",
|
||||||
"relative cursor-default select-none py-2 pl-3 pr-9 aria-disabled:opacity-50 aria-disabled:cursor-not-allowed"
|
"relative cursor-default select-none py-2 pl-3 pr-9"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={recipient}>
|
value={recipient}>
|
||||||
@ -67,7 +66,7 @@ export default function RecipientSelector(props: any) {
|
|||||||
selected ? "font-semibold" : "font-normal",
|
selected ? "font-semibold" : "font-normal",
|
||||||
"ml-3 block truncate"
|
"ml-3 block truncate"
|
||||||
)}>
|
)}>
|
||||||
{`${recipient?.name} <${recipient?.email || 'unknown'}>`}
|
{`${recipient?.name} <${recipient?.email}>`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -77,7 +76,7 @@ export default function RecipientSelector(props: any) {
|
|||||||
active ? "text-white" : "text-neon-dark",
|
active ? "text-white" : "text-neon-dark",
|
||||||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||||
)}>
|
)}>
|
||||||
<CheckIcon className="h-5 w-5" strokeWidth={3} aria-hidden="true" />
|
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { Button, IconButton } from "@documenso/ui";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { LanguageIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
import { LanguageIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
import SignatureCanvas from "react-signature-canvas";
|
import SignatureCanvas from "react-signature-canvas";
|
||||||
import { useDebouncedValue } from "../../hooks/use-debounced-value";
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "Type", icon: LanguageIcon, current: true },
|
{ name: "Type", icon: LanguageIcon, current: true },
|
||||||
@ -16,9 +15,6 @@ export default function SignatureDialog(props: any) {
|
|||||||
const [currentTab, setCurrentTab] = useState(tabs[0]);
|
const [currentTab, setCurrentTab] = useState(tabs[0]);
|
||||||
const [typedSignature, setTypedSignature] = useState("");
|
const [typedSignature, setTypedSignature] = useState("");
|
||||||
const [signatureEmpty, setSignatureEmpty] = useState(true);
|
const [signatureEmpty, setSignatureEmpty] = useState(true);
|
||||||
// This is a workaround to prevent the canvas from being rendered when the dialog is closed
|
|
||||||
// we also need the debounce to avoid rendering while transitions are occuring.
|
|
||||||
const showCanvas = useDebouncedValue<boolean>(props.open, 1);
|
|
||||||
let signCanvasRef: any | undefined;
|
let signCanvasRef: any | undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -89,7 +85,7 @@ export default function SignatureDialog(props: any) {
|
|||||||
</div>
|
</div>
|
||||||
{isCurrentTab("Type") ? (
|
{isCurrentTab("Type") ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="my-7 mb-3 border-b border-gray-300">
|
<div className="my-8 mb-3 border-b border-gray-300">
|
||||||
<input
|
<input
|
||||||
value={typedSignature}
|
value={typedSignature}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -102,7 +98,7 @@ export default function SignatureDialog(props: any) {
|
|||||||
placeholder="Kindly type your name"
|
placeholder="Kindly type your name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row-reverse items-center gap-x-3">
|
<div className="float-right">
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -130,55 +126,47 @@ export default function SignatureDialog(props: any) {
|
|||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{isCurrentTab("Draw") ? (
|
{isCurrentTab("Draw") ? (
|
||||||
<div className="" key={props.open ? "closed" : "open"}>
|
<div className="">
|
||||||
{showCanvas && (
|
<SignatureCanvas
|
||||||
<SignatureCanvas
|
ref={(ref) => {
|
||||||
ref={(ref) => {
|
signCanvasRef = ref;
|
||||||
signCanvasRef = ref;
|
}}
|
||||||
}}
|
canvasProps={{
|
||||||
canvasProps={{
|
className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
|
||||||
className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
|
}}
|
||||||
}}
|
clearOnResize={true}
|
||||||
clearOnResize={true}
|
onEnd={() => {
|
||||||
onEnd={() => {
|
setSignatureEmpty(signCanvasRef?.isEmpty());
|
||||||
setSignatureEmpty(signCanvasRef?.isEmpty());
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
<IconButton
|
||||||
)}
|
className="float-left block"
|
||||||
|
icon={TrashIcon}
|
||||||
<div className="flex items-center justify-between">
|
onClick={() => {
|
||||||
<IconButton
|
signCanvasRef?.clear();
|
||||||
className="block"
|
setSignatureEmpty(signCanvasRef?.isEmpty());
|
||||||
icon={TrashIcon}
|
}}></IconButton>
|
||||||
|
<div className="float-right mt-10">
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signCanvasRef?.clear();
|
props.onClose();
|
||||||
setSignatureEmpty(signCanvasRef?.isEmpty());
|
props.setOpen(false);
|
||||||
|
setCurrent(tabs[0]);
|
||||||
|
}}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="ml-3"
|
||||||
|
onClick={() => {
|
||||||
|
props.onClose({
|
||||||
|
type: "draw",
|
||||||
|
signatureImage: signCanvasRef.toDataURL("image/png"),
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
disabled={signatureEmpty}>
|
||||||
|
Sign
|
||||||
<div className="flex flex-row-reverse items-center gap-x-3">
|
</Button>
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
onClick={() => {
|
|
||||||
props.onClose();
|
|
||||||
props.setOpen(false);
|
|
||||||
setCurrent(tabs[0]);
|
|
||||||
}}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className="ml-3"
|
|
||||||
onClick={() => {
|
|
||||||
props.onClose({
|
|
||||||
type: "draw",
|
|
||||||
signatureImage: signCanvasRef.toDataURL("image/png"),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={signatureEmpty}>
|
|
||||||
Sign
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export default function Login(props: any) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<a href="#" className="text-gray-500 hover:text-neon-700 font-medium">
|
<a href="#" className="text-neon hover:text-neon font-medium">
|
||||||
Forgot your password?
|
Forgot your password?
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -123,7 +123,7 @@ export default function Login(props: any) {
|
|||||||
className="group relative flex w-full">
|
className="group relative flex w-full">
|
||||||
<span className="absolute inset-y-0 left-0 flex items-center pl-3">
|
<span className="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
<LockClosedIcon
|
<LockClosedIcon
|
||||||
className="text-neon-700 group-hover:text-neon-dark-700 h-5 w-5 disabled:disabled:bg-gray-600 disabled:group-hover:bg-gray-600 duration-200"
|
className="text-neon-dark group-hover:text-neon h-5 w-5 disabled:disabled:bg-gray-600 disabled:group-hover:bg-gray-600"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -141,7 +141,7 @@ export default function Login(props: any) {
|
|||||||
{props.allowSignup ? (
|
{props.allowSignup ? (
|
||||||
<p className="mt-2 text-center text-sm text-gray-600">
|
<p className="mt-2 text-center text-sm text-gray-600">
|
||||||
Are you new here?{" "}
|
Are you new here?{" "}
|
||||||
<Link href="/signup" className="text-gray-500 hover:text-neon-700 duration-200 font-medium">
|
<Link href="/signup" className="text-neon hover:text-neon font-medium">
|
||||||
Create a new Account
|
Create a new Account
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -187,7 +187,7 @@ export default function Signup(props: { source: string }) {
|
|||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-center text-sm text-gray-600">
|
<p className="mt-2 text-center text-sm text-gray-600">
|
||||||
Already have an account?{" "}
|
Already have an account?{" "}
|
||||||
<Link href="/login" className="text-gray-500 hover:text-neon-700 font-medium">
|
<Link href="/login" className="text-neon hover:text-neon font-medium">
|
||||||
Sign In
|
Sign In
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export function useDebouncedValue<T>(value: T, delay: number) {
|
|
||||||
// State and setters for debounced value
|
|
||||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = setTimeout(() => {
|
|
||||||
setDebouncedValue(value);
|
|
||||||
}, delay);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(handler);
|
|
||||||
};
|
|
||||||
}, [value, delay]);
|
|
||||||
|
|
||||||
return debouncedValue;
|
|
||||||
}
|
|
||||||
@ -16,27 +16,16 @@
|
|||||||
"@headlessui/react": "^1.7.4",
|
"@headlessui/react": "^1.7.4",
|
||||||
"@heroicons/react": "^2.0.13",
|
"@heroicons/react": "^2.0.13",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
|
||||||
"@types/bcryptjs": "^2.4.2",
|
|
||||||
"@types/filesystem": "^0.0.32",
|
|
||||||
"@types/react-dom": "18.0.9",
|
|
||||||
"avatar-from-initials": "^1.0.3",
|
"avatar-from-initials": "^1.0.3",
|
||||||
"base64-arraybuffer": "^1.0.2",
|
"base64-arraybuffer": "^1.0.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"eslint": "8.27.0",
|
|
||||||
"eslint-config-next": "13.0.3",
|
|
||||||
"file-loader": "^6.2.0",
|
|
||||||
"formidable": "^3.2.5",
|
"formidable": "^3.2.5",
|
||||||
"install": "^0.13.0",
|
"next": "13.2.4",
|
||||||
"next": "13.0.3",
|
"next-auth": "^4.22.0",
|
||||||
"next-auth": ">=4.20.1",
|
|
||||||
"next-transpile-modules": "^10.0.0",
|
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"node-signpdf": "^1.5.0",
|
"node-signpdf": "^1.5.0",
|
||||||
"nodemailer": "^6.9.0",
|
"nodemailer": "^6.9.0",
|
||||||
"nodemailer-sendgrid": "^1.0.3",
|
"nodemailer-sendgrid": "^1.0.3",
|
||||||
"npm": "^9.1.3",
|
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"placeholder-loading": "^0.6.0",
|
"placeholder-loading": "^0.6.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@ -46,12 +35,14 @@
|
|||||||
"react-pdf": "^6.2.2",
|
"react-pdf": "^6.2.2",
|
||||||
"react-resizable": "^3.0.4",
|
"react-resizable": "^3.0.4",
|
||||||
"react-tooltip": "^5.7.2",
|
"react-tooltip": "^5.7.2",
|
||||||
"sass": "^1.57.1",
|
|
||||||
"short-uuid": "^4.2.2",
|
"short-uuid": "^4.2.2",
|
||||||
"string-to-color": "^2.2.2",
|
"string-to-color": "^2.2.2"
|
||||||
"typescript": "4.8.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
"@types/bcryptjs": "^2.4.2",
|
||||||
|
"@types/filesystem": "^0.0.32",
|
||||||
|
"@types/react-dom": "18.0.9",
|
||||||
"@types/formidable": "^2.0.5",
|
"@types/formidable": "^2.0.5",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@types/nodemailer": "^6.4.7",
|
"@types/nodemailer": "^6.4.7",
|
||||||
@ -59,7 +50,14 @@
|
|||||||
"@types/react-pdf": "^6.2.0",
|
"@types/react-pdf": "^6.2.0",
|
||||||
"@types/react-resizable": "^3.0.3",
|
"@types/react-resizable": "^3.0.3",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"eslint": "8.27.0",
|
||||||
|
"eslint-config-next": "13.0.3",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"next-transpile-modules": "^10.0.0",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.19",
|
||||||
"tailwindcss": "^3.2.4"
|
"sass": "^1.57.1",
|
||||||
|
"tailwindcss": "^3.2.4",
|
||||||
|
"typescript": "4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +63,6 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
signingStatus: SigningStatus.SIGNED,
|
signingStatus: SigningStatus.SIGNED,
|
||||||
signedAt: new Date(),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -87,11 +86,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
where: {
|
where: {
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
type: { in: [FieldType.DATE, FieldType.TEXT] },
|
type: { in: [FieldType.DATE, FieldType.TEXT] },
|
||||||
recipientId: { in: signedRecipients.map((r) => r.id) },
|
|
||||||
},
|
},
|
||||||
include: {
|
|
||||||
Recipient: true,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Insert fields other than signatures
|
// Insert fields other than signatures
|
||||||
@ -103,7 +98,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}).format(field.Recipient?.signedAt ?? new Date())
|
}).format(new Date())
|
||||||
: field.customText || "",
|
: field.customText || "",
|
||||||
field.positionX,
|
field.positionX,
|
||||||
field.positionY,
|
field.positionY,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChangeEvent, ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { uploadDocument } from "@documenso/features";
|
import { uploadDocument } from "@documenso/features";
|
||||||
@ -62,27 +62,26 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
|
|||||||
<dl className="mt-8 grid gap-5 md:grid-cols-3 ">
|
<dl className="mt-8 grid gap-5 md:grid-cols-3 ">
|
||||||
{stats.map((item) => (
|
{stats.map((item) => (
|
||||||
<Link href={item.link} key={item.name}>
|
<Link href={item.link} key={item.name}>
|
||||||
<div className="overflow-hidden rounded-lg bg-white px-4 py-3 shadow hover:shadow-lg duration-300 sm:py-5 md:p-6">
|
<div className="overflow-hidden rounded-lg bg-white px-4 py-3 shadow sm:py-5 md:p-6">
|
||||||
<dt className="truncate text-sm font-medium text-gray-700 ">
|
<dt className="truncate text-sm font-medium text-gray-500 ">
|
||||||
<item.icon
|
<item.icon
|
||||||
className="text-neon-600 mr-3 inline h-5 w-5 flex-shrink-0 sm:h-6 sm:w-6"
|
className="text-neon mr-3 inline h-5 w-5 flex-shrink-0 sm:h-6 sm:w-6"
|
||||||
aria-hidden="true"></item.icon>
|
aria-hidden="true"></item.icon>
|
||||||
{item.name}
|
{item.name}
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-3 text-2xl font-semibold tracking-tight text-gray-900 sm:text-3xl">
|
<dd className="mt-1 text-2xl font-semibold tracking-tight text-gray-900 sm:text-3xl">
|
||||||
{getStat(item.name, props)}
|
{getStat(item.name, props)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<div className="mt-12">
|
<div className="mt-12">
|
||||||
<input
|
<input
|
||||||
id="fileUploadHelper"
|
id="fileUploadHelper"
|
||||||
type="file"
|
type="file"
|
||||||
accept="application/pdf"
|
accept="application/pdf"
|
||||||
onChange={(event: ChangeEvent) => {
|
onChange={(event: any) => {
|
||||||
uploadDocument(event);
|
uploadDocument(event);
|
||||||
}}
|
}}
|
||||||
hidden
|
hidden
|
||||||
@ -92,10 +91,9 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
document?.getElementById("fileUploadHelper")?.click();
|
document?.getElementById("fileUploadHelper")?.click();
|
||||||
}}
|
}}
|
||||||
className="group hover:border-neon-600 duration-200 relative block w-full cursor-pointer rounded-lg border-2 border-dashed border-gray-300 p-12 text-center focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
className="hover:border-neon relative block w-full cursor-pointer rounded-lg border-2 border-dashed border-gray-300 p-12 text-center focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
className="mx-auto h-12 w-12 text-gray-400 group-hover:text-gray-700 duration-200"
|
className="mx-auto h-12 w-12 text-gray-400"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 00 20 25"
|
viewBox="0 00 20 25"
|
||||||
@ -106,8 +104,7 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
|
|||||||
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m3.75 9v6m3-3H9m1.5-12H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m3.75 9v6m3-3H9m1.5-12H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
<span id="add_document" className="text-neon mt-2 block text-sm font-medium">
|
||||||
<span id="add_document" className="text-gray-500 group-hover:text-neon-700 mt-2 block text-sm font-medium duration-200">
|
|
||||||
Add a new PDF document.
|
Add a new PDF document.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -27,13 +27,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
const [filteredDocuments, setFilteredDocuments] = useState([]);
|
const [filteredDocuments, setFilteredDocuments] = useState([]);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const statusFilters = [
|
||||||
type statusFilterType = {
|
|
||||||
label: string;
|
|
||||||
value: DocumentStatus | "ALL";
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusFilters: statusFilterType[] = [
|
|
||||||
{ label: "All", value: "ALL" },
|
{ label: "All", value: "ALL" },
|
||||||
{ label: "Draft", value: "DRAFT" },
|
{ label: "Draft", value: "DRAFT" },
|
||||||
{ label: "Waiting for others", value: "PENDING" },
|
{ label: "Waiting for others", value: "PENDING" },
|
||||||
@ -89,20 +83,6 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
return filteredDocuments;
|
return filteredDocuments;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStatusFilterChange(status: statusFilterType) {
|
|
||||||
router.replace(
|
|
||||||
{
|
|
||||||
pathname: router.pathname,
|
|
||||||
query: { filter: status.value },
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
shallow: true, // Perform a shallow update, without reloading the page
|
|
||||||
}
|
|
||||||
);
|
|
||||||
setSelectedStatusFilter(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
function wasXDaysAgoOrLess(documentDate: Date, lastXDays: number): boolean {
|
function wasXDaysAgoOrLess(documentDate: Date, lastXDays: number): boolean {
|
||||||
if (lastXDays < 0) return true;
|
if (lastXDays < 0) return true;
|
||||||
|
|
||||||
@ -142,26 +122,26 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 mb-12 flex flex-row-reverse items-center gap-x-4">
|
<div className="mt-3 mb-12">
|
||||||
<div className="pt-5 block w-fit">
|
<div className="float-right ml-3 mt-7 block w-fit">
|
||||||
{filteredDocuments.length != 1 ? filteredDocuments.length + " Documents" : "1 Document"}
|
{filteredDocuments.length != 1 ? filteredDocuments.length + " Documents" : "1 Document"}
|
||||||
</div>
|
</div>
|
||||||
<SelectBox
|
<SelectBox
|
||||||
className="block w-1/4"
|
className="float-right block w-1/4"
|
||||||
label="Created"
|
label="Created"
|
||||||
options={createdFilter}
|
options={createdFilter}
|
||||||
value={selectedCreatedFilter}
|
value={selectedCreatedFilter}
|
||||||
onChange={setSelectedCreatedFilter}
|
onChange={setSelectedCreatedFilter}
|
||||||
/>
|
/>
|
||||||
<SelectBox
|
<SelectBox
|
||||||
className="block w-1/4"
|
className="float-right ml-3 block w-1/4"
|
||||||
label="Status"
|
label="Status"
|
||||||
options={statusFilters}
|
options={statusFilters}
|
||||||
value={selectedStatusFilter}
|
value={selectedStatusFilter}
|
||||||
onChange={handleStatusFilterChange}
|
onChange={setSelectedStatusFilter}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 max-w-[1100px]" hidden={!loading}>
|
<div className="mt-20 max-w-[1100px]" hidden={!loading}>
|
||||||
<div className="ph-item">
|
<div className="ph-item">
|
||||||
<div className="ph-col-12">
|
<div className="ph-col-12">
|
||||||
<div className="ph-picture"></div>
|
<div className="ph-picture"></div>
|
||||||
@ -178,7 +158,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 flex flex-col" hidden={!documents.length || loading}>
|
<div className="mt-28 flex flex-col" hidden={!documents.length || loading}>
|
||||||
<div
|
<div
|
||||||
className="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8"
|
className="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8"
|
||||||
hidden={!documents.length || loading}>
|
hidden={!documents.length || loading}>
|
||||||
@ -221,13 +201,13 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||||
{document.title || "#" + document.id}
|
{document.title || "#" + document.id}
|
||||||
</td>
|
</td>
|
||||||
<td className="whitespace-nowrap inline-flex py-3 gap-x-2 gap-y-1 flex-wrap max-w-[250px] text-sm text-gray-500">
|
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||||
{document.Recipient.map((item: any) => (
|
{document.Recipient.map((item: any) => (
|
||||||
<div key={item.id}>
|
<div key={item.id}>
|
||||||
{item.sendStatus === "NOT_SENT" ? (
|
{item.sendStatus === "NOT_SENT" ? (
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="flex-shrink-0 h-6 inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
|
className="inline-block flex-shrink-0 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
|
||||||
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@ -237,8 +217,8 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<span id="sent_icon">
|
<span id="sent_icon">
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="flex-shrink-0 h-6 inline-flex items-center rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-yellow-800">
|
className="inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-green-800">
|
||||||
<EnvelopeIcon className="mr-1 inline h-4"></EnvelopeIcon>
|
<EnvelopeIcon className="mr-1 inline h-5"></EnvelopeIcon>
|
||||||
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -250,9 +230,9 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<span id="read_icon">
|
<span id="read_icon">
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="flex-shrink-0 h-6 inline-flex items-center rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-yellow-800">
|
className="inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-green-800">
|
||||||
<CheckIcon className="-mr-2 inline h-4"></CheckIcon>
|
<CheckIcon className="-mr-2 inline h-5"></CheckIcon>
|
||||||
<CheckIcon className="mr-1 inline h-4"></CheckIcon>
|
<CheckIcon className="mr-1 inline h-5"></CheckIcon>
|
||||||
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -261,7 +241,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
)}
|
)}
|
||||||
{item.signingStatus === "SIGNED" ? (
|
{item.signingStatus === "SIGNED" ? (
|
||||||
<span id="signed_icon">
|
<span id="signed_icon">
|
||||||
<span className="flex-shrink-0 h-6 inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
|
<span className="inline-block flex-shrink-0 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
|
||||||
<CheckBadgeIcon className="mr-1 inline h-5"></CheckBadgeIcon>{" "}
|
<CheckBadgeIcon className="mr-1 inline h-5"></CheckBadgeIcon>{" "}
|
||||||
{item.email}
|
{item.email}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -18,16 +18,13 @@ import {
|
|||||||
UserPlusIcon,
|
UserPlusIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { DocumentStatus, Document as PrismaDocument, Recipient } from "@prisma/client";
|
import { DocumentStatus, Document as PrismaDocument } from "@prisma/client";
|
||||||
import { FormProvider, useFieldArray, useForm, useWatch } from "react-hook-form";
|
import { FormProvider, useFieldArray, useForm, useWatch } from "react-hook-form";
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
|
|
||||||
export type FormValues = {
|
export type FormValues = {
|
||||||
signers: Array<Pick<Recipient, 'id' | 'email' | 'name' | 'sendStatus' | 'readStatus' | 'signingStatus'>>;
|
signers: { id: number; email: string; name: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormSigner = FormValues["signers"][number];
|
|
||||||
|
|
||||||
const RecipientsPage: NextPageWithLayout = (props: any) => {
|
const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||||
const title: string = `"` + props?.document?.title + `"` + "Recipients | Documenso";
|
const title: string = `"` + props?.document?.title + `"` + "Recipients | Documenso";
|
||||||
const breadcrumbItems = [
|
const breadcrumbItems = [
|
||||||
@ -66,7 +63,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
});
|
});
|
||||||
const formValues = useWatch({ control, name: "signers" });
|
const formValues = useWatch({ control, name: "signers" });
|
||||||
const cancelButtonRef = useRef(null);
|
const cancelButtonRef = useRef(null);
|
||||||
const hasEmailError = (formValue: FormSigner): boolean => {
|
const hasEmailError = (formValue: any): boolean => {
|
||||||
const index = formValues.findIndex((e) => e.id === formValue.id);
|
const index = formValues.findIndex((e) => e.id === formValue.id);
|
||||||
return !!errors?.signers?.[index]?.email;
|
return !!errors?.signers?.[index]?.email;
|
||||||
};
|
};
|
||||||
@ -111,14 +108,12 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
icon={PaperAirplaneIcon}
|
icon={PaperAirplaneIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
formValues.some((r) => r.email && hasEmailError(r))
|
setOpen(true);
|
||||||
? toast.error("Please enter a valid email address.", { id: "invalid email" })
|
|
||||||
: setOpen(true);
|
|
||||||
}}
|
}}
|
||||||
disabled={
|
disabled={
|
||||||
(formValues.length || 0) === 0 ||
|
(formValues.length || 0) === 0 ||
|
||||||
!formValues.some(
|
!formValues.some(
|
||||||
(r) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
(r: any) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
||||||
) ||
|
) ||
|
||||||
loading
|
loading
|
||||||
}>
|
}>
|
||||||
@ -143,7 +138,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
trigger();
|
trigger();
|
||||||
}}>
|
}}>
|
||||||
<ul role="list" className="divide-y divide-gray-200">
|
<ul role="list" className="divide-y divide-gray-200">
|
||||||
{fields.map((item, index) => (
|
{fields.map((item: any, index: number) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className="group w-full border-0 px-2 py-3 hover:bg-green-50 sm:py-4">
|
className="group w-full border-0 px-2 py-3 hover:bg-green-50 sm:py-4">
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { getUserFromToken } from "@documenso/lib/server";
|
|
||||||
import Login from "../components/login";
|
import Login from "../components/login";
|
||||||
|
|
||||||
export default function LoginPage(props: any) {
|
export default function LoginPage(props: any) {
|
||||||
@ -14,16 +13,6 @@ export default function LoginPage(props: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getServerSideProps(context: any) {
|
export async function getServerSideProps(context: any) {
|
||||||
const user = await getUserFromToken(context.req, context.res);
|
|
||||||
if (user)
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
source: "/login",
|
|
||||||
destination: "/dashboard",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const ALLOW_SIGNUP = process.env.ALLOW_SIGNUP === "true";
|
const ALLOW_SIGNUP = process.env.ALLOW_SIGNUP === "true";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { NextPageContext } from "next";
|
import { NextPageContext } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { getUserFromToken } from "@documenso/lib/server";
|
|
||||||
import Signup from "../components/signup";
|
import Signup from "../components/signup";
|
||||||
|
|
||||||
export default function SignupPage(props: { source: string }) {
|
export default function SignupPage(props: { source: string }) {
|
||||||
@ -23,16 +22,6 @@ export async function getServerSideProps(context: any) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = await getUserFromToken(context.req, context.res);
|
|
||||||
if (user)
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
source: "/signup",
|
|
||||||
destination: "/dashboard",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const signupSource: string = context.query["source"];
|
const signupSource: string = context.query["source"];
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@ -16,48 +16,9 @@ module.exports = {
|
|||||||
qwigley: ["Qwigley", "serif"],
|
qwigley: ["Qwigley", "serif"],
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
neon: {
|
neon: "#37f095",
|
||||||
DEFAULT: "#37F095",
|
"neon-dark": "#2CC077",
|
||||||
50: "#E2FDF0",
|
brown: "#353434",
|
||||||
100: "#CFFBE5",
|
|
||||||
200: "#A9F9D1",
|
|
||||||
300: "#83F6BD",
|
|
||||||
400: "#5DF3A9",
|
|
||||||
500: "#37F095",
|
|
||||||
600: "#11DE79",
|
|
||||||
700: "#0DAA5D",
|
|
||||||
800: "#097640",
|
|
||||||
900: "#054224",
|
|
||||||
950: "#032816",
|
|
||||||
},
|
|
||||||
"neon-dark": {
|
|
||||||
DEFAULT: "#2CC077",
|
|
||||||
50: "#B5EED2",
|
|
||||||
100: "#A5EAC8",
|
|
||||||
200: "#84E3B4",
|
|
||||||
300: "#62DBA0",
|
|
||||||
400: "#41D48B",
|
|
||||||
500: "#2CC077",
|
|
||||||
600: "#22925B",
|
|
||||||
700: "#17653E",
|
|
||||||
800: "#0D3722",
|
|
||||||
900: "#020906",
|
|
||||||
950: "#000000",
|
|
||||||
},
|
|
||||||
brown: {
|
|
||||||
DEFAULT: "#353434",
|
|
||||||
50: "#918F8F",
|
|
||||||
100: "#878585",
|
|
||||||
200: "#737171",
|
|
||||||
300: "#5E5C5C",
|
|
||||||
400: "#4A4848",
|
|
||||||
500: "#353434",
|
|
||||||
600: "#191818",
|
|
||||||
700: "#000000",
|
|
||||||
800: "#000000",
|
|
||||||
900: "#000000",
|
|
||||||
950: "#000000",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
"4xl": "2rem",
|
"4xl": "2rem",
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
name: documenso
|
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: postgres:15
|
image: postgres:15
|
||||||
container_name: database
|
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=documenso
|
- POSTGRES_USER=documenso
|
||||||
- POSTGRES_PASSWORD=password
|
- POSTGRES_PASSWORD=password
|
||||||
@ -12,7 +10,6 @@ services:
|
|||||||
|
|
||||||
inbucket:
|
inbucket:
|
||||||
image: inbucket/inbucket
|
image: inbucket/inbucket
|
||||||
container_name: mailserver
|
|
||||||
ports:
|
ports:
|
||||||
- 9000:9000
|
- 9000:9000
|
||||||
- 2500:2500
|
- 2500:2500
|
||||||
|
|||||||
Submodule documenso updated: 8039871ab1...0dcab27e65
5930
package-lock.json
generated
5930
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -26,33 +26,31 @@
|
|||||||
"@documenso/prisma": "*",
|
"@documenso/prisma": "*",
|
||||||
"@headlessui/react": "^1.7.4",
|
"@headlessui/react": "^1.7.4",
|
||||||
"@heroicons/react": "^2.0.13",
|
"@heroicons/react": "^2.0.13",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
|
||||||
"@types/bcryptjs": "^2.4.2",
|
|
||||||
"@types/node": "18.11.9",
|
|
||||||
"@types/react-dom": "18.0.9",
|
|
||||||
"@types/react-signature-canvas": "^1.0.2",
|
|
||||||
"avatar-from-initials": "^1.0.3",
|
"avatar-from-initials": "^1.0.3",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"dotenv": "^16.0.3",
|
"next": "13.2.4",
|
||||||
"eslint": "8.27.0",
|
|
||||||
"eslint-config-next": "13.0.3",
|
|
||||||
"install": "^0.13.0",
|
|
||||||
"next": "13.0.3",
|
|
||||||
"next-auth": ">=4.20.1",
|
"next-auth": ">=4.20.1",
|
||||||
"next-transpile-modules": "^10.0.0",
|
|
||||||
"npm": "^9.1.3",
|
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.41.5",
|
"react-hook-form": "^7.41.5",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.0",
|
||||||
"react-signature-canvas": "^1.0.6",
|
"react-signature-canvas": "^1.0.6"
|
||||||
"typescript": "4.8.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"@types/bcryptjs": "^2.4.2",
|
||||||
|
"@types/node": "18.11.9",
|
||||||
|
"@types/react-dom": "18.0.9",
|
||||||
|
"@types/react-signature-canvas": "^1.0.2",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"eslint": "8.27.0",
|
||||||
|
"eslint-config-next": "13.0.3",
|
||||||
|
"next-transpile-modules": "^10.0.0",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.5"
|
"prettier-plugin-tailwindcss": "^0.2.5",
|
||||||
|
"typescript": "4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,10 +1,9 @@
|
|||||||
import router from "next/router";
|
import router from "next/router";
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "../lib/constants";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "../lib/constants";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { ChangeEvent } from "react";
|
|
||||||
|
|
||||||
export const uploadDocument = async (event: ChangeEvent) => {
|
export const uploadDocument = async (event: any) => {
|
||||||
if (event.target instanceof HTMLInputElement && event.target?.files && event.target.files[0]) {
|
if (event.target.files && event.target.files[0]) {
|
||||||
const body = new FormData();
|
const body = new FormData();
|
||||||
const document = event.target.files[0];
|
const document = event.target.files[0];
|
||||||
const fileName: string = event.target.files[0].name;
|
const fileName: string = event.target.files[0].name;
|
||||||
@ -13,10 +12,8 @@ export const uploadDocument = async (event: ChangeEvent) => {
|
|||||||
toast.error("Non-PDF documents are not supported yet.");
|
toast.error("Non-PDF documents are not supported yet.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.append("document", document || "");
|
body.append("document", document || "");
|
||||||
|
const response: any = await toast
|
||||||
await toast
|
|
||||||
.promise(
|
.promise(
|
||||||
fetch("/api/documents", {
|
fetch("/api/documents", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@ -1,8 +1 @@
|
|||||||
export const NEXT_PUBLIC_WEBAPP_URL =
|
export const NEXT_PUBLIC_WEBAPP_URL = process.env.NEXT_PUBLIC_WEBAPP_URL;
|
||||||
process.env.IS_PULL_REQUEST === "true"
|
|
||||||
? process.env.RENDER_EXTERNAL_URL
|
|
||||||
: process.env.NEXT_PUBLIC_WEBAPP_URL;
|
|
||||||
|
|
||||||
console.log("IS_PULL_REQUEST:" + process.env.IS_PULL_REQUEST);
|
|
||||||
console.log("RENDER_EXTERNAL_URL:" + process.env.RENDER_EXTERNAL_URL);
|
|
||||||
console.log("NEXT_PUBLIC_WEBAPP_URL:" + process.env.NEXT_PUBLIC_WEBAPP_URL);
|
|
||||||
|
|||||||
@ -11,8 +11,8 @@ export const signingRequestTemplate = (
|
|||||||
user: any
|
user: any
|
||||||
) => {
|
) => {
|
||||||
const customContent = `
|
const customContent = `
|
||||||
<p style="margin: 30px 0px; text-align: center">
|
<p style="margin: 30px;">
|
||||||
<a href="${ctaLink}" style="background-color: #37f095; white-space: nowrap; color: white; border-color: transparent; border-width: 1px; border-radius: 0.375rem; font-size: 18px; padding-left: 16px; padding-right: 16px; padding-top: 10px; padding-bottom: 10px; text-decoration: none; margin-top: 4px; margin-bottom: 4px;">
|
<a href="${ctaLink}" style="background-color: #37f095; color: white; border-color: transparent; border-width: 1px; border-radius: 0.375rem; font-size: 18px; padding-left: 16px; padding-right: 16px; padding-top: 10px; padding-bottom: 10px; text-decoration: none; margin-top: 4px; margin-bottom: 4px;">
|
||||||
${ctaLabel}
|
${ctaLabel}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Recipient" ADD COLUMN "signedAt" TIMESTAMP(3);
|
|
||||||
@ -3,20 +3,20 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"devDependencies": {
|
"scripts": {
|
||||||
"@types/node": "^18.11.18",
|
"db-studio": "prisma studio",
|
||||||
"ts-node": "^10.9.1",
|
"db-seed": "prisma db seed"
|
||||||
"typescript": "4.8.4"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^4.8.1",
|
"@prisma/client": "^4.8.1",
|
||||||
"prisma": "^4.8.1"
|
"prisma": "^4.8.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"devDependencies": {
|
||||||
"db-studio": "prisma studio",
|
"@types/node": "^18.11.18",
|
||||||
"db-seed": "prisma db seed"
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "4.8.4"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "ts-node --transpile-only ./seed.ts"
|
"seed": "ts-node --transpile-only ./seed.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +92,6 @@ model Recipient {
|
|||||||
name String @default("") @db.VarChar(255)
|
name String @default("") @db.VarChar(255)
|
||||||
token String
|
token String
|
||||||
expired DateTime?
|
expired DateTime?
|
||||||
signedAt DateTime?
|
|
||||||
readStatus ReadStatus @default(NOT_OPENED)
|
readStatus ReadStatus @default(NOT_OPENED)
|
||||||
signingStatus SigningStatus @default(NOT_SIGNED)
|
signingStatus SigningStatus @default(NOT_SIGNED)
|
||||||
sendStatus SendStatus @default(NOT_SENT)
|
sendStatus SendStatus @default(NOT_SENT)
|
||||||
|
|||||||
@ -6,8 +6,8 @@ export function Button(props: any) {
|
|||||||
const isLink = typeof props.href !== "undefined" && !props.disabled;
|
const isLink = typeof props.href !== "undefined" && !props.disabled;
|
||||||
const { color = "primary", icon, disabled, onClick } = props;
|
const { color = "primary", icon, disabled, onClick } = props;
|
||||||
const baseStyles =
|
const baseStyles =
|
||||||
"inline-flex gap-x-2 items-center justify-center min-w-[80px] rounded-md border border-transparent px-4 py-2 text-sm font-medium shadow-sm disabled:bg-gray-300 duration-200";
|
"inline-flex items-center justify-center min-w-[80px] rounded-md border border-transparent px-4 py-2 text-sm font-medium shadow-sm disabled:bg-gray-300";
|
||||||
const primaryStyles = "text-gray-900 bg-neon hover:bg-neon-dark";
|
const primaryStyles = "text-white bg-neon hover:bg-neon-dark";
|
||||||
const secondaryStyles = "border-gray-300 bg-white text-gray-700 hover:bg-gray-50";
|
const secondaryStyles = "border-gray-300 bg-white text-gray-700 hover:bg-gray-50";
|
||||||
|
|
||||||
return isLink ? (
|
return isLink ? (
|
||||||
@ -21,7 +21,7 @@ export function Button(props: any) {
|
|||||||
)}
|
)}
|
||||||
hidden={props.hidden}>
|
hidden={props.hidden}>
|
||||||
{props.icon ? (
|
{props.icon ? (
|
||||||
<props.icon className="inline-block h-5 text-inherit" aria-hidden="true"></props.icon>
|
<props.icon className="mr-1 inline h-5 text-inherit" aria-hidden="true"></props.icon>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
@ -40,7 +40,7 @@ export function Button(props: any) {
|
|||||||
disabled={props.disabled || props.loading}
|
disabled={props.disabled || props.loading}
|
||||||
hidden={props.hidden}>
|
hidden={props.hidden}>
|
||||||
{props.icon ? (
|
{props.icon ? (
|
||||||
<props.icon className="inline h-5 text-inherit" aria-hidden="true"></props.icon>
|
<props.icon className="mr-1 inline h-5 text-inherit" aria-hidden="true"></props.icon>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user