Compare commits
32 Commits
bugfix-#71
...
feat/DOC-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
108614bf46 | ||
|
|
adf69edd54 | ||
|
|
82139f6b2d | ||
|
|
270c82759c | ||
|
|
01c7903efa | ||
|
|
64b755d5ba | ||
|
|
8788b64585 | ||
|
|
c9547057f6 | ||
|
|
17e688c222 | ||
|
|
f5a42e694d | ||
|
|
b2d09216c8 | ||
|
|
6d30a486ab | ||
|
|
dc6217b14e | ||
|
|
a6171ec4f3 | ||
|
|
d0f962598c | ||
|
|
81fd9ff749 | ||
|
|
4dcb0a684d | ||
|
|
ab96990d43 | ||
|
|
ad5b2bcf82 | ||
|
|
6f18be6b5b | ||
|
|
8039871ab1 | ||
|
|
4b9840d7e0 | ||
|
|
544a16caff | ||
|
|
989d036e54 | ||
|
|
894f8720b8 | ||
|
|
70ea3ceaf3 | ||
|
|
80d26adf9c | ||
|
|
b4e21f97e3 | ||
|
|
849885b5b3 | ||
|
|
d863f89232 | ||
|
|
84e3d29589 | ||
|
|
ba3ffe68ea |
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 neccessary)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
- [Javascript (when necessary)](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 Manger NPM - included in Node.js
|
- Node Package Manager 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 machnine:
|
Follow these steps to setup documenso on you local machine:
|
||||||
|
|
||||||
- [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 machnine:
|
|||||||
- 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 (recommened)
|
- Or setup a local postgres sql instance (recommended)
|
||||||
- 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\_\* varibles</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\_\* variables</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 machnine:
|
|||||||
|
|
||||||
- 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 you own using these steps and a linux Terminal or Windows Linux Subsystem see **Create your own signging certificate**.
|
- To generate your own using these steps and a linux Terminal or Windows Linux Subsystem see **Create your own signing certificate**.
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
- If you pull the newest version from main, using <code>git pull</code>, it may be neccessary to regenerate your database client
|
- If you pull the newest version from main, using <code>git pull</code>, it may be necessary 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 neccessary on first clone
|
- This is not necessary on first clone
|
||||||
|
|
||||||
# Creating your own signging certificate
|
# Creating your own signing certificate
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
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>
|
||||||
|
|||||||
@@ -45,10 +45,11 @@ 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"
|
"relative cursor-default select-none py-2 pl-3 pr-9 aria-disabled:opacity-50 aria-disabled:cursor-not-allowed"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={recipient}>
|
value={recipient}>
|
||||||
@@ -66,7 +67,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}>`}
|
{`${recipient?.name} <${recipient?.email || 'unknown'}>`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -76,7 +77,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" aria-hidden="true" />
|
<CheckIcon className="h-5 w-5" strokeWidth={3} aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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 },
|
||||||
@@ -15,6 +16,9 @@ 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(() => {
|
||||||
@@ -126,19 +130,21 @@ export default function SignatureDialog(props: any) {
|
|||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{isCurrentTab("Draw") ? (
|
{isCurrentTab("Draw") ? (
|
||||||
<div className="">
|
<div className="" key={props.open ? "closed" : "open"}>
|
||||||
<SignatureCanvas
|
{showCanvas && (
|
||||||
ref={(ref) => {
|
<SignatureCanvas
|
||||||
signCanvasRef = ref;
|
ref={(ref) => {
|
||||||
}}
|
signCanvasRef = ref;
|
||||||
canvasProps={{
|
}}
|
||||||
className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
|
canvasProps={{
|
||||||
}}
|
className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
|
||||||
clearOnResize={true}
|
}}
|
||||||
onEnd={() => {
|
clearOnResize={true}
|
||||||
setSignatureEmpty(signCanvasRef?.isEmpty());
|
onEnd={() => {
|
||||||
}}
|
setSignatureEmpty(signCanvasRef?.isEmpty());
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<IconButton
|
<IconButton
|
||||||
className="float-left block"
|
className="float-left block"
|
||||||
icon={TrashIcon}
|
icon={TrashIcon}
|
||||||
|
|||||||
18
apps/web/hooks/use-debounced-value.ts
Normal file
18
apps/web/hooks/use-debounced-value.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
signingStatus: SigningStatus.SIGNED,
|
signingStatus: SigningStatus.SIGNED,
|
||||||
|
signedAt: new Date(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,7 +87,11 @@ 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
|
||||||
@@ -98,7 +103,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}).format(new Date())
|
}).format(field.Recipient?.signedAt ?? new Date())
|
||||||
: field.customText || "",
|
: field.customText || "",
|
||||||
field.positionX,
|
field.positionX,
|
||||||
field.positionY,
|
field.positionY,
|
||||||
|
|||||||
@@ -27,7 +27,13 @@ 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" },
|
||||||
@@ -83,6 +89,20 @@ 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;
|
||||||
|
|
||||||
@@ -138,7 +158,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
label="Status"
|
label="Status"
|
||||||
options={statusFilters}
|
options={statusFilters}
|
||||||
value={selectedStatusFilter}
|
value={selectedStatusFilter}
|
||||||
onChange={setSelectedStatusFilter}
|
onChange={handleStatusFilterChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-20 max-w-[1100px]" hidden={!loading}>
|
<div className="mt-20 max-w-[1100px]" hidden={!loading}>
|
||||||
|
|||||||
@@ -18,13 +18,16 @@ import {
|
|||||||
UserPlusIcon,
|
UserPlusIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { DocumentStatus, Document as PrismaDocument } from "@prisma/client";
|
import { DocumentStatus, Document as PrismaDocument, Recipient } 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: { id: number; email: string; name: string }[];
|
signers: Array<Pick<Recipient, 'id' | 'email' | 'name' | 'sendStatus' | 'readStatus' | 'signingStatus'>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = [
|
||||||
@@ -63,7 +66,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: any): boolean => {
|
const hasEmailError = (formValue: FormSigner): 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;
|
||||||
};
|
};
|
||||||
@@ -108,12 +111,14 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
icon={PaperAirplaneIcon}
|
icon={PaperAirplaneIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(true);
|
formValues.some((r) => r.email && hasEmailError(r))
|
||||||
|
? 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: any) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
(r) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
||||||
) ||
|
) ||
|
||||||
loading
|
loading
|
||||||
}>
|
}>
|
||||||
@@ -138,7 +143,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: any, index: number) => (
|
{fields.map((item, index) => (
|
||||||
<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,4 +1,5 @@
|
|||||||
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) {
|
||||||
@@ -13,6 +14,16 @@ 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,5 +1,6 @@
|
|||||||
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 }) {
|
||||||
@@ -22,6 +23,16 @@ 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: {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
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
|
||||||
@@ -10,6 +12,7 @@ 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: 0dcab27e65...8039871ab1
@@ -1 +1,8 @@
|
|||||||
export const NEXT_PUBLIC_WEBAPP_URL = process.env.NEXT_PUBLIC_WEBAPP_URL;
|
export const 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;">
|
<p style="margin: 30px 0px; text-align: center">
|
||||||
<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;">
|
<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;">
|
||||||
${ctaLabel}
|
${ctaLabel}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Recipient" ADD COLUMN "signedAt" TIMESTAMP(3);
|
||||||
@@ -92,6 +92,7 @@ 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)
|
||||||
|
|||||||
Reference in New Issue
Block a user