Compare commits

..

2 Commits

Author SHA1 Message Date
Mythie 3eebd52dd1 fix: improve types 2023-04-19 23:52:37 +10:00
Saurav Gurhale f8f4c07d11 add toast error for invalid email 2023-04-19 23:48:21 +10:00
15 changed files with 36 additions and 123 deletions
+10 -10
View File
@@ -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:
- [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/)
- [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 Manager NPM - included in Node.js
- Node Package Manger 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 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.
```sh
@@ -138,12 +138,12 @@ Follow these steps to setup documenso on you local machine:
- Rename <code>.env.example</code> to <code>.env</code>
- 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 (recommended)
- Or setup a local postgres sql instance (recommened)
- Create the database scheme by running <code>db-migrate:dev</code>
- Setup your mail provider
- Set <code>SENDGRID_API_KEY</code> 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 <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
- 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
- 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
- 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:
```sh
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:\
<code>openssl genrsa -out private.key 2048</code>
@@ -45,11 +45,10 @@ export default function RecipientSelector(props: any) {
{props?.recipients.map((recipient: any) => (
<Listbox.Option
key={recipient?.id}
disabled={!recipient?.email}
className={({ active }) =>
classNames(
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}>
@@ -67,7 +66,7 @@ export default function RecipientSelector(props: any) {
selected ? "font-semibold" : "font-normal",
"ml-3 block truncate"
)}>
{`${recipient?.name} <${recipient?.email || 'unknown'}>`}
{`${recipient?.name} <${recipient?.email}>`}
</span>
</div>
@@ -77,7 +76,7 @@ export default function RecipientSelector(props: any) {
active ? "text-white" : "text-neon-dark",
"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>
) : null}
</>
+13 -19
View File
@@ -5,7 +5,6 @@ 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 },
@@ -16,9 +15,6 @@ 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<boolean>(props.open, 1);
let signCanvasRef: any | undefined;
useEffect(() => {
@@ -130,21 +126,19 @@ export default function SignatureDialog(props: any) {
""
)}
{isCurrentTab("Draw") ? (
<div className="" key={props.open ? "closed" : "open"}>
{showCanvas && (
<SignatureCanvas
ref={(ref) => {
signCanvasRef = ref;
}}
canvasProps={{
className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
}}
clearOnResize={true}
onEnd={() => {
setSignatureEmpty(signCanvasRef?.isEmpty());
}}
/>
)}
<div className="">
<SignatureCanvas
ref={(ref) => {
signCanvasRef = ref;
}}
canvasProps={{
className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
}}
clearOnResize={true}
onEnd={() => {
setSignatureEmpty(signCanvasRef?.isEmpty());
}}
/>
<IconButton
className="float-left block"
icon={TrashIcon}
-18
View File
@@ -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;
}
+1 -6
View File
@@ -63,7 +63,6 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
},
data: {
signingStatus: SigningStatus.SIGNED,
signedAt: new Date(),
},
});
@@ -87,11 +86,7 @@ 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
@@ -103,7 +98,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
month: "long",
day: "numeric",
year: "numeric",
}).format(field.Recipient?.signedAt ?? new Date())
}).format(new Date())
: field.customText || "",
field.positionX,
field.positionY,
+2 -22
View File
@@ -27,13 +27,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
const [filteredDocuments, setFilteredDocuments] = useState([]);
const [loading, setLoading] = useState(true);
type statusFilterType = {
label: string;
value: DocumentStatus | "ALL";
};
const statusFilters: statusFilterType[] = [
const statusFilters = [
{ label: "All", value: "ALL" },
{ label: "Draft", value: "DRAFT" },
{ label: "Waiting for others", value: "PENDING" },
@@ -89,20 +83,6 @@ 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;
@@ -158,7 +138,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
label="Status"
options={statusFilters}
value={selectedStatusFilter}
onChange={handleStatusFilterChange}
onChange={setSelectedStatusFilter}
/>
</div>
<div className="mt-20 max-w-[1100px]" hidden={!loading}>
+3 -5
View File
@@ -23,11 +23,9 @@ import { FormProvider, useFieldArray, useForm, useWatch } from "react-hook-form"
import { toast } from "react-hot-toast";
export type FormValues = {
signers: Array<Pick<Recipient, 'id' | 'email' | 'name' | 'sendStatus' | 'readStatus' | 'signingStatus'>>;
signers: Array<Pick<Recipient, 'id' | 'email' | 'name' | 'sendStatus'>>;
};
type FormSigner = FormValues["signers"][number];
const RecipientsPage: NextPageWithLayout = (props: any) => {
const title: string = `"` + props?.document?.title + `"` + "Recipients | Documenso";
const breadcrumbItems = [
@@ -66,7 +64,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
});
const formValues = useWatch({ control, name: "signers" });
const cancelButtonRef = useRef(null);
const hasEmailError = (formValue: FormSigner): boolean => {
const hasEmailError = (formValue: any): boolean => {
const index = formValues.findIndex((e) => e.id === formValue.id);
return !!errors?.signers?.[index]?.email;
};
@@ -143,7 +141,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
trigger();
}}>
<ul role="list" className="divide-y divide-gray-200">
{fields.map((item, index) => (
{fields.map((item: any, index: number) => (
<li
key={index}
className="group w-full border-0 px-2 py-3 hover:bg-green-50 sm:py-4">
-11
View File
@@ -1,5 +1,4 @@
import Head from "next/head";
import { getUserFromToken } from "@documenso/lib/server";
import Login from "../components/login";
export default function LoginPage(props: any) {
@@ -14,16 +13,6 @@ 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 {
-11
View File
@@ -1,6 +1,5 @@
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 }) {
@@ -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"];
return {
props: {
-3
View File
@@ -1,8 +1,6 @@
name: documenso
services:
database:
image: postgres:15
container_name: database
environment:
- POSTGRES_USER=documenso
- POSTGRES_PASSWORD=password
@@ -12,7 +10,6 @@ services:
inbucket:
image: inbucket/inbucket
container_name: mailserver
ports:
- 9000:9000
- 2500:2500
Submodule documenso updated: 8039871ab1...0dcab27e65
+1 -8
View File
@@ -1,8 +1 @@
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);
export const NEXT_PUBLIC_WEBAPP_URL = process.env.NEXT_PUBLIC_WEBAPP_URL;
+2 -2
View File
@@ -11,8 +11,8 @@ export const signingRequestTemplate = (
user: any
) => {
const customContent = `
<p style="margin: 30px 0px; text-align: center">
<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;">
<p style="margin: 30px;">
<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}
</a>
</p>
@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "Recipient" ADD COLUMN "signedAt" TIMESTAMP(3);
-1
View File
@@ -92,7 +92,6 @@ 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)