diff --git a/README.md b/README.md
index 436e5150d..a6c0ab8ad 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ The current project goal is to [release a production ready version](https://g
Documenso is built using awesome open source tech including:
- [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/)
- [Postgres SQL (Database)](https://www.postgresql.org/)
- [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
- [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/)
## Developer Quickstart
@@ -128,7 +128,7 @@ Your database will also be available on port `5432`. You can connect to it using
## 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.
```sh
@@ -138,12 +138,12 @@ Follow these steps to setup documenso on you local machnine:
- Rename .env.example to .env
- Set DATABASE_URL value in .env file
- 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 db-migrate:dev
- Setup your mail provider
- Set SENDGRID_API_KEY value in .env file
- 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 SMTP\_\* varibles in your .env
+ - Documenso uses [Nodemailer](https://nodemailer.com/about/) so you can easily use your own SMTP server by setting the SMTP\_\* variables in your .env
- Run npm run dev root directory to start
- 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
- 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
-- If you pull the newest version from main, using git pull, it may be neccessary to regenerate your database client
+- If you pull the newest version from main, using git pull, it may be necessary to regenerate your database client
- You can do this by running the generate command in /packages/prisma:
```sh
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:\
openssl genrsa -out private.key 2048
diff --git a/apps/web/components/editor/pdf-signer.tsx b/apps/web/components/editor/pdf-signer.tsx
index 797745a4c..a4a4cd2c6 100644
--- a/apps/web/components/editor/pdf-signer.tsx
+++ b/apps/web/components/editor/pdf-signer.tsx
@@ -80,12 +80,11 @@ export default function PDFSigner(props: any) {
: props.document.User.email}{" "}
would like you to sign this document.
-
+
{
signDocument(props.document, localSignatures, `${router.query.token}`).then(
() => {
diff --git a/apps/web/components/editor/recipient-selector.tsx b/apps/web/components/editor/recipient-selector.tsx
index b5a0a494b..30c90025d 100644
--- a/apps/web/components/editor/recipient-selector.tsx
+++ b/apps/web/components/editor/recipient-selector.tsx
@@ -45,10 +45,11 @@ export default function RecipientSelector(props: any) {
{props?.recipients.map((recipient: any) => (
classNames(
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}>
@@ -66,7 +67,7 @@ export default function RecipientSelector(props: any) {
selected ? "font-semibold" : "font-normal",
"ml-3 block truncate"
)}>
- {`${recipient?.name} <${recipient?.email}>`}
+ {`${recipient?.name} <${recipient?.email || 'unknown'}>`}
@@ -76,7 +77,7 @@ export default function RecipientSelector(props: any) {
active ? "text-white" : "text-neon-dark",
"absolute inset-y-0 right-0 flex items-center pr-4"
)}>
-
+
) : null}
>
diff --git a/apps/web/components/editor/signature-dialog.tsx b/apps/web/components/editor/signature-dialog.tsx
index 0dd442b83..3869eecef 100644
--- a/apps/web/components/editor/signature-dialog.tsx
+++ b/apps/web/components/editor/signature-dialog.tsx
@@ -5,6 +5,7 @@ import { Button, IconButton } from "@documenso/ui";
import { Dialog, Transition } from "@headlessui/react";
import { LanguageIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
import SignatureCanvas from "react-signature-canvas";
+import { useDebouncedValue } from "../../hooks/use-debounced-value";
const tabs = [
{ name: "Type", icon: LanguageIcon, current: true },
@@ -15,6 +16,9 @@ export default function SignatureDialog(props: any) {
const [currentTab, setCurrentTab] = useState(tabs[0]);
const [typedSignature, setTypedSignature] = useState("");
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(props.open, 1);
let signCanvasRef: any | undefined;
useEffect(() => {
@@ -85,7 +89,7 @@ export default function SignatureDialog(props: any) {
{isCurrentTab("Type") ? (
-
+
{
@@ -98,7 +102,7 @@ export default function SignatureDialog(props: any) {
placeholder="Kindly type your name"
/>
-
+
{
@@ -126,47 +130,55 @@ export default function SignatureDialog(props: any) {
""
)}
{isCurrentTab("Draw") ? (
-
-
{
- signCanvasRef = ref;
- }}
- canvasProps={{
- className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
- }}
- clearOnResize={true}
- onEnd={() => {
- setSignatureEmpty(signCanvasRef?.isEmpty());
- }}
- />
- {
- signCanvasRef?.clear();
- setSignatureEmpty(signCanvasRef?.isEmpty());
- }}>
-
-
{
- props.onClose();
- props.setOpen(false);
- setCurrent(tabs[0]);
- }}>
- Cancel
-
-
{
- props.onClose({
- type: "draw",
- signatureImage: signCanvasRef.toDataURL("image/png"),
- });
+
+ {showCanvas && (
+
{
+ signCanvasRef = ref;
}}
- disabled={signatureEmpty}>
- Sign
-
+ canvasProps={{
+ className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
+ }}
+ clearOnResize={true}
+ onEnd={() => {
+ setSignatureEmpty(signCanvasRef?.isEmpty());
+ }}
+ />
+ )}
+
+
+
{
+ signCanvasRef?.clear();
+ setSignatureEmpty(signCanvasRef?.isEmpty());
+ }}
+ />
+
+
+ {
+ props.onClose();
+ props.setOpen(false);
+ setCurrent(tabs[0]);
+ }}>
+ Cancel
+
+
+ {
+ props.onClose({
+ type: "draw",
+ signatureImage: signCanvasRef.toDataURL("image/png"),
+ });
+ }}
+ disabled={signatureEmpty}>
+ Sign
+
+
) : (
diff --git a/apps/web/components/login.tsx b/apps/web/components/login.tsx
index 4bbb784a1..bd968ddc6 100644
--- a/apps/web/components/login.tsx
+++ b/apps/web/components/login.tsx
@@ -111,7 +111,7 @@ export default function Login(props: any) {
@@ -123,7 +123,7 @@ export default function Login(props: any) {
className="group relative flex w-full">
@@ -141,7 +141,7 @@ export default function Login(props: any) {
{props.allowSignup ? (
Are you new here?{" "}
-
+
Create a new Account
diff --git a/apps/web/components/signup.tsx b/apps/web/components/signup.tsx
index d7b27ba7b..8092c0198 100644
--- a/apps/web/components/signup.tsx
+++ b/apps/web/components/signup.tsx
@@ -187,7 +187,7 @@ export default function Signup(props: { source: string }) {
Already have an account?{" "}
-
+
Sign In
diff --git a/apps/web/hooks/use-debounced-value.ts b/apps/web/hooks/use-debounced-value.ts
new file mode 100644
index 000000000..eebbec716
--- /dev/null
+++ b/apps/web/hooks/use-debounced-value.ts
@@ -0,0 +1,18 @@
+import { useEffect, useState } from "react";
+
+export function useDebouncedValue(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;
+}
diff --git a/apps/web/pages/api/documents/[id]/sign.ts b/apps/web/pages/api/documents/[id]/sign.ts
index 537d7ee72..853dd3500 100644
--- a/apps/web/pages/api/documents/[id]/sign.ts
+++ b/apps/web/pages/api/documents/[id]/sign.ts
@@ -63,6 +63,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
},
data: {
signingStatus: SigningStatus.SIGNED,
+ signedAt: new Date(),
},
});
@@ -86,7 +87,11 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
where: {
documentId: document.id,
type: { in: [FieldType.DATE, FieldType.TEXT] },
+ recipientId: { in: signedRecipients.map((r) => r.id) },
},
+ include: {
+ Recipient: true,
+ }
});
// Insert fields other than signatures
@@ -98,7 +103,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
month: "long",
day: "numeric",
year: "numeric",
- }).format(new Date())
+ }).format(field.Recipient?.signedAt ?? new Date())
: field.customText || "",
field.positionX,
field.positionY,
diff --git a/apps/web/pages/dashboard.tsx b/apps/web/pages/dashboard.tsx
index 324560f5a..5d3d93d94 100644
--- a/apps/web/pages/dashboard.tsx
+++ b/apps/web/pages/dashboard.tsx
@@ -1,4 +1,4 @@
-import { ReactElement } from "react";
+import { ChangeEvent, ReactElement } from "react";
import Head from "next/head";
import Link from "next/link";
import { uploadDocument } from "@documenso/features";
@@ -62,26 +62,27 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
{stats.map((item) => (
-
-
+
+
{item.name}
-
+
{getStat(item.name, props)}
))}
+
{
+ onChange={(event: ChangeEvent) => {
uploadDocument(event);
}}
hidden
@@ -91,9 +92,10 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
onClick={() => {
document?.getElementById("fileUploadHelper")?.click();
}}
- 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">
+ 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">
+
{
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"
/>
-
+
+
Add a new PDF document.
diff --git a/apps/web/pages/documents.tsx b/apps/web/pages/documents.tsx
index eaad896a5..af6752d47 100644
--- a/apps/web/pages/documents.tsx
+++ b/apps/web/pages/documents.tsx
@@ -27,7 +27,13 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
const [filteredDocuments, setFilteredDocuments] = useState([]);
const [loading, setLoading] = useState(true);
- const statusFilters = [
+
+ type statusFilterType = {
+ label: string;
+ value: DocumentStatus | "ALL";
+ };
+
+ const statusFilters: statusFilterType[] = [
{ label: "All", value: "ALL" },
{ label: "Draft", value: "DRAFT" },
{ label: "Waiting for others", value: "PENDING" },
@@ -83,6 +89,20 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
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 {
if (lastXDays < 0) return true;
@@ -122,26 +142,26 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
-
-
+
+
{filteredDocuments.length != 1 ? filteredDocuments.length + " Documents" : "1 Document"}
-
+
@@ -158,7 +178,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
-
+
@@ -201,13 +221,13 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
{document.title || "#" + document.id}
-
+
{document.Recipient.map((item: any) => (
{item.sendStatus === "NOT_SENT" ? (
+ 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">
{item.name ? item.name + " <" + item.email + ">" : item.email}
) : (
@@ -217,8 +237,8 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
-
+ 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">
+
{item.name ? item.name + " <" + item.email + ">" : item.email}
@@ -230,9 +250,9 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
-
-
+ 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">
+
+
{item.name ? item.name + " <" + item.email + ">" : item.email}
@@ -241,7 +261,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
)}
{item.signingStatus === "SIGNED" ? (
-
+
{" "}
{item.email}
diff --git a/apps/web/pages/documents/[id]/recipients.tsx b/apps/web/pages/documents/[id]/recipients.tsx
index de20e22e1..8dc9e7f64 100644
--- a/apps/web/pages/documents/[id]/recipients.tsx
+++ b/apps/web/pages/documents/[id]/recipients.tsx
@@ -18,13 +18,16 @@ import {
UserPlusIcon,
XMarkIcon,
} 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 { toast } from "react-hot-toast";
export type FormValues = {
- signers: { id: number; email: string; name: string }[];
+ signers: Array>;
};
+type FormSigner = FormValues["signers"][number];
+
const RecipientsPage: NextPageWithLayout = (props: any) => {
const title: string = `"` + props?.document?.title + `"` + "Recipients | Documenso";
const breadcrumbItems = [
@@ -63,7 +66,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
});
const formValues = useWatch({ control, name: "signers" });
const cancelButtonRef = useRef(null);
- const hasEmailError = (formValue: any): boolean => {
+ const hasEmailError = (formValue: FormSigner): boolean => {
const index = formValues.findIndex((e) => e.id === formValue.id);
return !!errors?.signers?.[index]?.email;
};
@@ -108,12 +111,14 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
color="primary"
icon={PaperAirplaneIcon}
onClick={() => {
- setOpen(true);
+ formValues.some((r) => r.email && hasEmailError(r))
+ ? toast.error("Please enter a valid email address.", { id: "invalid email" })
+ : setOpen(true);
}}
disabled={
(formValues.length || 0) === 0 ||
!formValues.some(
- (r: any) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
+ (r) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
) ||
loading
}>
@@ -138,7 +143,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
trigger();
}}>
- {fields.map((item: any, index: number) => (
+ {fields.map((item, index) => (
diff --git a/apps/web/pages/login.tsx b/apps/web/pages/login.tsx
index ca384369a..6abdd6aaf 100644
--- a/apps/web/pages/login.tsx
+++ b/apps/web/pages/login.tsx
@@ -1,4 +1,5 @@
import Head from "next/head";
+import { getUserFromToken } from "@documenso/lib/server";
import Login from "../components/login";
export default function LoginPage(props: any) {
@@ -13,6 +14,16 @@ export default function LoginPage(props: 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";
return {
diff --git a/apps/web/pages/signup.tsx b/apps/web/pages/signup.tsx
index b59fd6e79..689444de9 100644
--- a/apps/web/pages/signup.tsx
+++ b/apps/web/pages/signup.tsx
@@ -1,5 +1,6 @@
import { NextPageContext } from "next";
import Head from "next/head";
+import { getUserFromToken } from "@documenso/lib/server";
import Signup from "../components/signup";
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"];
return {
props: {
diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js
index a6a344329..89d211331 100644
--- a/apps/web/tailwind.config.js
+++ b/apps/web/tailwind.config.js
@@ -16,9 +16,48 @@ module.exports = {
qwigley: ["Qwigley", "serif"],
},
colors: {
- neon: "#37f095",
- "neon-dark": "#2CC077",
- brown: "#353434",
+ neon: {
+ DEFAULT: "#37F095",
+ 50: "#E2FDF0",
+ 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: {
"4xl": "2rem",
diff --git a/docker/compose-without-app.yml b/docker/compose-without-app.yml
index 8f10c2a5b..e0b566e96 100644
--- a/docker/compose-without-app.yml
+++ b/docker/compose-without-app.yml
@@ -1,6 +1,8 @@
+name: documenso
services:
database:
image: postgres:15
+ container_name: database
environment:
- POSTGRES_USER=documenso
- POSTGRES_PASSWORD=password
@@ -10,6 +12,7 @@ services:
inbucket:
image: inbucket/inbucket
+ container_name: mailserver
ports:
- 9000:9000
- 2500:2500
diff --git a/documenso b/documenso
index 0dcab27e6..8039871ab 160000
--- a/documenso
+++ b/documenso
@@ -1 +1 @@
-Subproject commit 0dcab27e65d97a1aa721b52151693325cf6f595f
+Subproject commit 8039871ab15788312715737c3d4fd0f993fea6c1
diff --git a/packages/features/uploadDocument.ts b/packages/features/uploadDocument.ts
index ef57d795f..9a652672e 100644
--- a/packages/features/uploadDocument.ts
+++ b/packages/features/uploadDocument.ts
@@ -1,9 +1,10 @@
import router from "next/router";
import { NEXT_PUBLIC_WEBAPP_URL } from "../lib/constants";
import toast from "react-hot-toast";
+import { ChangeEvent } from "react";
-export const uploadDocument = async (event: any) => {
- if (event.target.files && event.target.files[0]) {
+export const uploadDocument = async (event: ChangeEvent) => {
+ if (event.target instanceof HTMLInputElement && event.target?.files && event.target.files[0]) {
const body = new FormData();
const document = event.target.files[0];
const fileName: string = event.target.files[0].name;
@@ -12,8 +13,10 @@ export const uploadDocument = async (event: any) => {
toast.error("Non-PDF documents are not supported yet.");
return;
}
+
body.append("document", document || "");
- const response: any = await toast
+
+ await toast
.promise(
fetch("/api/documents", {
method: "POST",
diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts
index 2e69ab309..d43db0f2c 100644
--- a/packages/lib/constants.ts
+++ b/packages/lib/constants.ts
@@ -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);
diff --git a/packages/lib/mail/signingRequestTemplate.ts b/packages/lib/mail/signingRequestTemplate.ts
index a99ff2be4..5be3ad735 100644
--- a/packages/lib/mail/signingRequestTemplate.ts
+++ b/packages/lib/mail/signingRequestTemplate.ts
@@ -11,8 +11,8 @@ export const signingRequestTemplate = (
user: any
) => {
const customContent = `
-
-
+
+
${ctaLabel}
diff --git a/packages/prisma/migrations/20230421134018_doc_214_add_signed_at_field/migration.sql b/packages/prisma/migrations/20230421134018_doc_214_add_signed_at_field/migration.sql
new file mode 100644
index 000000000..619e059aa
--- /dev/null
+++ b/packages/prisma/migrations/20230421134018_doc_214_add_signed_at_field/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Recipient" ADD COLUMN "signedAt" TIMESTAMP(3);
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index e5fa887c3..d326bf5a2 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -92,6 +92,7 @@ model Recipient {
name String @default("") @db.VarChar(255)
token String
expired DateTime?
+ signedAt DateTime?
readStatus ReadStatus @default(NOT_OPENED)
signingStatus SigningStatus @default(NOT_SIGNED)
sendStatus SendStatus @default(NOT_SENT)
diff --git a/packages/ui/components/button/Button.tsx b/packages/ui/components/button/Button.tsx
index fae2fd716..2d2502833 100644
--- a/packages/ui/components/button/Button.tsx
+++ b/packages/ui/components/button/Button.tsx
@@ -6,8 +6,8 @@ export function Button(props: any) {
const isLink = typeof props.href !== "undefined" && !props.disabled;
const { color = "primary", icon, disabled, onClick } = props;
const baseStyles =
- "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-white bg-neon hover:bg-neon-dark";
+ "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";
+ const primaryStyles = "text-gray-900 bg-neon hover:bg-neon-dark";
const secondaryStyles = "border-gray-300 bg-white text-gray-700 hover:bg-gray-50";
return isLink ? (
@@ -21,7 +21,7 @@ export function Button(props: any) {
)}
hidden={props.hidden}>
{props.icon ? (
-
+
) : (
""
)}
@@ -40,7 +40,7 @@ export function Button(props: any) {
disabled={props.disabled || props.loading}
hidden={props.hidden}>
{props.icon ? (
-
+
) : (
""
)}