mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
Apply prettier config to all files
This commit is contained in:
@ -121,6 +121,7 @@ Follow these steps to setup documenso on you local machnine:
|
|||||||
- Register a new user at http://localhost:3000/signup
|
- Register a new user at http://localhost:3000/signup
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- Optional: Seed the database using <code>npm run db-seed</code> to create a test user and document
|
- Optional: Seed the database using <code>npm run db-seed</code> to create a test user and document
|
||||||
- Optional: Upload and sign <code>apps\web\ressources\example.pdf</code> manually to test your setup
|
- Optional: Upload and sign <code>apps\web\ressources\example.pdf</code> manually to test your setup
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import Draggable from "react-draggable";
|
|
||||||
import Logo from "../logo";
|
|
||||||
import { IconButton } from "@documenso/ui";
|
import { IconButton } from "@documenso/ui";
|
||||||
|
import Logo from "../logo";
|
||||||
import { XCircleIcon } from "@heroicons/react/20/solid";
|
import { XCircleIcon } from "@heroicons/react/20/solid";
|
||||||
|
import Draggable from "react-draggable";
|
||||||
|
|
||||||
const stc = require("string-to-color");
|
const stc = require("string-to-color");
|
||||||
|
|
||||||
type FieldPropsType = {
|
type FieldPropsType = {
|
||||||
@ -51,21 +52,19 @@ export default function EditableField(props: FieldPropsType) {
|
|||||||
onMouseDown={(e: any) => {
|
onMouseDown={(e: any) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{/* width: 192 height 96 */}
|
{/* width: 192 height 96 */}
|
||||||
<div
|
<div
|
||||||
hidden={props.hidden}
|
hidden={props.hidden}
|
||||||
ref={nodeRef}
|
ref={nodeRef}
|
||||||
className="cursor-move opacity-80 p-2 m-auto w-48 h-16 flex-row-reverse text-lg font-bold text-center absolute top-0 left-0 select-none"
|
className="absolute top-0 left-0 m-auto h-16 w-48 cursor-move select-none flex-row-reverse p-2 text-center text-lg font-bold opacity-80"
|
||||||
style={{
|
style={{
|
||||||
background: stc(props.field.Recipient.email),
|
background: stc(props.field.Recipient.email),
|
||||||
}}
|
}}>
|
||||||
>
|
<div className="m-auto flex-row-reverse overflow-hidden text-center text-lg font-bold">
|
||||||
<div className="m-auto overflow-hidden flex-row-reverse text-lg font-bold text-center">
|
|
||||||
{field.type}
|
{field.type}
|
||||||
{field.type === "SIGNATURE" ? (
|
{field.type === "SIGNATURE" ? (
|
||||||
<div className="text-xs text-center">
|
<div className="text-center text-xs">
|
||||||
{`${props.field.Recipient?.name} <${props.field.Recipient?.email}>`}
|
{`${props.field.Recipient?.name} <${props.field.Recipient?.email}>`}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@ -79,8 +78,7 @@ export default function EditableField(props: FieldPropsType) {
|
|||||||
icon={XCircleIcon}
|
icon={XCircleIcon}
|
||||||
onClick={(event: any) => {
|
onClick={(event: any) => {
|
||||||
props.onDelete(props.field.id);
|
props.onDelete(props.field.id);
|
||||||
}}
|
}}></IconButton>
|
||||||
></IconButton>
|
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { RadioGroup } from "@headlessui/react";
|
|
||||||
import { classNames } from "@documenso/lib";
|
import { classNames } from "@documenso/lib";
|
||||||
|
import { RadioGroup } from "@headlessui/react";
|
||||||
import { FieldType } from "@prisma/client";
|
import { FieldType } from "@prisma/client";
|
||||||
|
|
||||||
const stc = require("string-to-color");
|
const stc = require("string-to-color");
|
||||||
|
|
||||||
export default function FieldTypeSelector(props: any) {
|
export default function FieldTypeSelector(props: any) {
|
||||||
@ -24,8 +25,7 @@ export default function FieldTypeSelector(props: any) {
|
|||||||
value={selectedFieldType}
|
value={selectedFieldType}
|
||||||
onChange={(e: any) => {
|
onChange={(e: any) => {
|
||||||
setSelectedFieldType(e);
|
setSelectedFieldType(e);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{fieldTypes.map((fieldType) => (
|
{fieldTypes.map((fieldType) => (
|
||||||
<RadioGroup.Option
|
<RadioGroup.Option
|
||||||
@ -37,30 +37,23 @@ export default function FieldTypeSelector(props: any) {
|
|||||||
className={({ checked, active }) =>
|
className={({ checked, active }) =>
|
||||||
classNames(
|
classNames(
|
||||||
checked ? "border-neon border-2" : "border-transparent",
|
checked ? "border-neon border-2" : "border-transparent",
|
||||||
"hover:bg-slate-100 select-none relative block cursor-pointer rounded-lg border bg-white px-3 py-2 focus:outline-none sm:flex sm:justify-between"
|
"relative block cursor-pointer select-none rounded-lg border bg-white px-3 py-2 hover:bg-slate-100 focus:outline-none sm:flex sm:justify-between"
|
||||||
)
|
)
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
{({ active, checked }) => (
|
{({ active, checked }) => (
|
||||||
<>
|
<>
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
<span className="flex flex-col text-sm">
|
<span className="flex flex-col text-sm">
|
||||||
<RadioGroup.Label
|
<RadioGroup.Label as="span" className="font-medium text-gray-900">
|
||||||
as="span"
|
|
||||||
className="font-medium text-gray-900"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
className="inline-block h-4 w-4 flex-shrink-0 rounded-full mr-3 align-middle"
|
className="mr-3 inline-block h-4 w-4 flex-shrink-0 rounded-full align-middle"
|
||||||
style={{
|
style={{
|
||||||
background: stc(props.selectedRecipient?.email),
|
background: stc(props.selectedRecipient?.email),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="align-middle">
|
<span className="align-middle">
|
||||||
{" "}
|
{" "}
|
||||||
{
|
{fieldTypes.filter((e) => e.id === fieldType.id)[0].name}
|
||||||
fieldTypes.filter((e) => e.id === fieldType.id)[0]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
</RadioGroup.Label>
|
</RadioGroup.Label>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { createOrUpdateField, deleteField } from "@documenso/lib/api";
|
import dynamic from "next/dynamic";
|
||||||
import { createField } from "@documenso/features/editor";
|
|
||||||
import RecipientSelector from "./recipient-selector";
|
|
||||||
import FieldTypeSelector from "./field-type-selector";
|
|
||||||
import { InformationCircleIcon } from "@heroicons/react/24/outline";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { createField } from "@documenso/features/editor";
|
||||||
|
import { createOrUpdateField, deleteField } from "@documenso/lib/api";
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
||||||
|
import FieldTypeSelector from "./field-type-selector";
|
||||||
|
import RecipientSelector from "./recipient-selector";
|
||||||
|
import { InformationCircleIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
const stc = require("string-to-color");
|
const stc = require("string-to-color");
|
||||||
|
|
||||||
const PDFViewer = dynamic(() => import("./pdf-viewer"), {
|
const PDFViewer = dynamic(() => import("./pdf-viewer"), {
|
||||||
@ -20,8 +21,7 @@ export default function PDFEditor(props: any) {
|
|||||||
const [selectedRecipient, setSelectedRecipient]: any = useState();
|
const [selectedRecipient, setSelectedRecipient]: any = useState();
|
||||||
const [selectedFieldType, setSelectedFieldType] = useState();
|
const [selectedFieldType, setSelectedFieldType] = useState();
|
||||||
const noRecipients =
|
const noRecipients =
|
||||||
props?.document.Recipient.length === 0 ||
|
props?.document.Recipient.length === 0 || props?.document.Recipient.every((e: any) => !e.email);
|
||||||
props?.document.Recipient.every((e: any) => !e.email);
|
|
||||||
|
|
||||||
function onPositionChangedHandler(position: any, id: any) {
|
function onPositionChangedHandler(position: any, id: any) {
|
||||||
if (!position) return;
|
if (!position) return;
|
||||||
@ -53,26 +53,16 @@ export default function PDFEditor(props: any) {
|
|||||||
<div hidden={!noRecipients} className="rounded-md bg-yellow-50 p-4">
|
<div hidden={!noRecipients} className="rounded-md bg-yellow-50 p-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<InformationCircleIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-yellow-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||||
<p className="text-sm text-yellow-700">
|
<p className="text-sm text-yellow-700">
|
||||||
This document does not have any recipients. Add recipients to
|
This document does not have any recipients. Add recipients to create fields.
|
||||||
create fields.
|
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id + "/recipients"}
|
||||||
NEXT_PUBLIC_WEBAPP_URL +
|
className="whitespace-nowrap font-medium text-yellow-700 hover:text-yellow-600">
|
||||||
"/documents/" +
|
|
||||||
props.document.id +
|
|
||||||
"/recipients"
|
|
||||||
}
|
|
||||||
className="whitespace-nowrap font-medium text-yellow-700 hover:text-yellow-600"
|
|
||||||
>
|
|
||||||
Add Recipients
|
Add Recipients
|
||||||
<span aria-hidden="true"> →</span>
|
<span aria-hidden="true"> →</span>
|
||||||
</Link>
|
</Link>
|
||||||
@ -98,21 +88,13 @@ export default function PDFEditor(props: any) {
|
|||||||
}}
|
}}
|
||||||
onMouseDown={(e: any, page: number) => {
|
onMouseDown={(e: any, page: number) => {
|
||||||
if (e.button === 0) addField(e, page);
|
if (e.button === 0) addField(e, page);
|
||||||
}}
|
}}></PDFViewer>
|
||||||
></PDFViewer>
|
|
||||||
<div
|
<div
|
||||||
hidden={noRecipients}
|
hidden={noRecipients}
|
||||||
className="fixed left-0 top-1/3 max-w-xs border border-slate-300 bg-white py-4 pr-5 rounded-md"
|
className="fixed left-0 top-1/3 max-w-xs rounded-md border border-slate-300 bg-white py-4 pr-5">
|
||||||
>
|
<RecipientSelector recipients={props?.document?.Recipient} onChange={setSelectedRecipient} />
|
||||||
<RecipientSelector
|
|
||||||
recipients={props?.document?.Recipient}
|
|
||||||
onChange={setSelectedRecipient}
|
|
||||||
/>
|
|
||||||
<hr className="m-3 border-slate-300"></hr>
|
<hr className="m-3 border-slate-300"></hr>
|
||||||
<FieldTypeSelector
|
<FieldTypeSelector selectedRecipient={selectedRecipient} onChange={setSelectedFieldType} />
|
||||||
selectedRecipient={selectedRecipient}
|
|
||||||
onChange={setSelectedFieldType}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -123,12 +105,7 @@ export default function PDFEditor(props: any) {
|
|||||||
if (!selectedFieldType) return;
|
if (!selectedFieldType) return;
|
||||||
if (noRecipients) return;
|
if (noRecipients) return;
|
||||||
|
|
||||||
const signatureField = createField(
|
const signatureField = createField(e, page, selectedRecipient, selectedFieldType);
|
||||||
e,
|
|
||||||
page,
|
|
||||||
selectedRecipient,
|
|
||||||
selectedFieldType
|
|
||||||
);
|
|
||||||
|
|
||||||
createOrUpdateField(props?.document, signatureField).then((res) => {
|
createOrUpdateField(props?.document, signatureField).then((res) => {
|
||||||
setFields((prevState) => [...prevState, res]);
|
setFields((prevState) => [...prevState, res]);
|
||||||
|
|||||||
@ -1,21 +1,14 @@
|
|||||||
import Logo from "../logo";
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import SignatureDialog from "./signature-dialog";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button } from "@documenso/ui";
|
import dynamic from "next/dynamic";
|
||||||
import {
|
import { useRouter } from "next/router";
|
||||||
CheckBadgeIcon,
|
|
||||||
InformationCircleIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { FieldType } from "@prisma/client";
|
|
||||||
import {
|
|
||||||
createOrUpdateField,
|
|
||||||
deleteField,
|
|
||||||
signDocument,
|
|
||||||
} from "@documenso/lib/api";
|
|
||||||
import { createField } from "@documenso/features/editor";
|
import { createField } from "@documenso/features/editor";
|
||||||
|
import { createOrUpdateField, deleteField, signDocument } from "@documenso/lib/api";
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
||||||
|
import { Button } from "@documenso/ui";
|
||||||
|
import Logo from "../logo";
|
||||||
|
import SignatureDialog from "./signature-dialog";
|
||||||
|
import { CheckBadgeIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { FieldType } from "@prisma/client";
|
||||||
|
|
||||||
const PDFViewer = dynamic(() => import("./pdf-viewer"), {
|
const PDFViewer = dynamic(() => import("./pdf-viewer"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@ -27,9 +20,7 @@ export default function PDFSigner(props: any) {
|
|||||||
const [signingDone, setSigningDone] = useState(false);
|
const [signingDone, setSigningDone] = useState(false);
|
||||||
const [localSignatures, setLocalSignatures] = useState<any[]>([]);
|
const [localSignatures, setLocalSignatures] = useState<any[]>([]);
|
||||||
const [fields, setFields] = useState<any[]>(props.fields);
|
const [fields, setFields] = useState<any[]>(props.fields);
|
||||||
const signatureFields = fields.filter(
|
const signatureFields = fields.filter((field) => field.type === FieldType.SIGNATURE);
|
||||||
(field) => field.type === FieldType.SIGNATURE
|
|
||||||
);
|
|
||||||
const [dialogField, setDialogField] = useState<any>();
|
const [dialogField, setDialogField] = useState<any>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -80,9 +71,9 @@ export default function PDFSigner(props: any) {
|
|||||||
<div className="bg-neon p-4">
|
<div className="bg-neon p-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<Logo className="h-12 w-12 -mt-2.5"></Logo>
|
<Logo className="-mt-2.5 h-12 w-12"></Logo>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between text-center justify-start items-center">
|
<div className="ml-3 flex-1 items-center justify-start text-center md:flex md:justify-between">
|
||||||
<p className="text-lg text-slate-700">
|
<p className="text-lg text-slate-700">
|
||||||
{props.document.User.name
|
{props.document.User.name
|
||||||
? `${props.document.User.name} (${props.document.User.email})`
|
? `${props.document.User.name} (${props.document.User.email})`
|
||||||
@ -96,17 +87,10 @@ export default function PDFSigner(props: any) {
|
|||||||
icon={CheckBadgeIcon}
|
icon={CheckBadgeIcon}
|
||||||
className="float-right"
|
className="float-right"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signDocument(
|
signDocument(props.document, localSignatures, `${router.query.token}`).then(() => {
|
||||||
props.document,
|
router.push(`/documents/${props.document.id}/signed?token=${router.query.token}`);
|
||||||
localSignatures,
|
|
||||||
`${router.query.token}`
|
|
||||||
).then(() => {
|
|
||||||
router.push(
|
|
||||||
`/documents/${props.document.id}/signed?token=${router.query.token}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Done
|
Done
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
@ -117,15 +101,11 @@ export default function PDFSigner(props: any) {
|
|||||||
<div className="bg-yellow-50 p-4">
|
<div className="bg-yellow-50 p-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<InformationCircleIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-yellow-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||||
<p className="text-sm text-yellow-700">
|
<p className="text-sm text-yellow-700">
|
||||||
You can sign this document anywhere you like, but maybe look for
|
You can sign this document anywhere you like, but maybe look for a signature line.
|
||||||
a signature line.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -144,12 +124,10 @@ export default function PDFSigner(props: any) {
|
|||||||
pdfUrl={`${NEXT_PUBLIC_WEBAPP_URL}/api/documents/${router.query.id}?token=${router.query.token}`}
|
pdfUrl={`${NEXT_PUBLIC_WEBAPP_URL}/api/documents/${router.query.id}?token=${router.query.token}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onMouseDown={function onMouseDown(e: any, page: number) {
|
onMouseDown={function onMouseDown(e: any, page: number) {
|
||||||
if (signatureFields.length === 0)
|
if (signatureFields.length === 0) addFreeSignature(e, page, props.recipient);
|
||||||
addFreeSignature(e, page, props.recipient);
|
|
||||||
}}
|
}}
|
||||||
onMouseUp={() => {}}
|
onMouseUp={() => {}}
|
||||||
onDelete={onDeleteHandler}
|
onDelete={onDeleteHandler}></PDFViewer>
|
||||||
></PDFViewer>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -157,27 +135,16 @@ export default function PDFSigner(props: any) {
|
|||||||
// Check if all fields are signed..
|
// Check if all fields are signed..
|
||||||
if (signatureFields.length > 0) {
|
if (signatureFields.length > 0) {
|
||||||
// If there are no fields to sign at least one signature is enough
|
// If there are no fields to sign at least one signature is enough
|
||||||
return fields
|
return fields.filter((field) => field.type === FieldType.SIGNATURE).every((field) => field.signature);
|
||||||
.filter((field) => field.type === FieldType.SIGNATURE)
|
|
||||||
.every((field) => field.signature);
|
|
||||||
} else {
|
} else {
|
||||||
return localSignatures.length > 0;
|
return localSignatures.length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFreeSignature(e: any, page: number, recipient: any): any {
|
function addFreeSignature(e: any, page: number, recipient: any): any {
|
||||||
const freeSignatureField = createField(
|
const freeSignatureField = createField(e, page, recipient, FieldType.FREE_SIGNATURE);
|
||||||
e,
|
|
||||||
page,
|
|
||||||
recipient,
|
|
||||||
FieldType.FREE_SIGNATURE
|
|
||||||
);
|
|
||||||
|
|
||||||
createOrUpdateField(
|
createOrUpdateField(props.document, freeSignatureField, recipient.token).then((res) => {
|
||||||
props.document,
|
|
||||||
freeSignatureField,
|
|
||||||
recipient.token
|
|
||||||
).then((res) => {
|
|
||||||
setFields((prevState) => [...prevState, res]);
|
setFields((prevState) => [...prevState, res]);
|
||||||
setDialogField(res);
|
setDialogField(res);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import { Document, Page } from "react-pdf/dist/esm/entry.webpack5";
|
|
||||||
import EditableField from "./editable-field";
|
import EditableField from "./editable-field";
|
||||||
import SignableField from "./signable-field";
|
import SignableField from "./signable-field";
|
||||||
import short from "short-uuid";
|
|
||||||
import { FieldType } from "@prisma/client";
|
import { FieldType } from "@prisma/client";
|
||||||
|
import { Document, Page } from "react-pdf/dist/esm/entry.webpack5";
|
||||||
|
import short from "short-uuid";
|
||||||
|
|
||||||
export default function PDFViewer(props) {
|
export default function PDFViewer(props) {
|
||||||
const [numPages, setNumPages] = useState(null);
|
const [numPages, setNumPages] = useState(null);
|
||||||
@ -30,19 +30,14 @@ export default function PDFViewer(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div hidden={loading} onMouseUp={props.onMouseUp} style={{ height: numPages * pageHeight + 1000 }}>
|
||||||
hidden={loading}
|
<div className="mt-6 max-w-xs"></div>
|
||||||
onMouseUp={props.onMouseUp}
|
|
||||||
style={{ height: numPages * pageHeight + 1000 }}
|
|
||||||
>
|
|
||||||
<div className="max-w-xs mt-6"></div>
|
|
||||||
<Document
|
<Document
|
||||||
file={props.pdfUrl}
|
file={props.pdfUrl}
|
||||||
onLoadSuccess={onDocumentLoadSuccess}
|
onLoadSuccess={onDocumentLoadSuccess}
|
||||||
options={options}
|
options={options}
|
||||||
renderMode="canvas"
|
renderMode="canvas"
|
||||||
className="absolute w-auto mx-auto left-0 right-0"
|
className="absolute left-0 right-0 mx-auto w-auto">
|
||||||
>
|
|
||||||
{Array.from({ length: numPages }, (_, index) => (
|
{Array.from({ length: numPages }, (_, index) => (
|
||||||
<Fragment key={short.generate().toString()}>
|
<Fragment key={short.generate().toString()}>
|
||||||
<div
|
<div
|
||||||
@ -57,8 +52,7 @@ export default function PDFViewer(props) {
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
...props.style,
|
...props.style,
|
||||||
}}
|
}}
|
||||||
className="mx-auto w-fit"
|
className="mx-auto w-fit">
|
||||||
>
|
|
||||||
<Page
|
<Page
|
||||||
className="mt-5"
|
className="mt-5"
|
||||||
key={`page_${index + 1}`}
|
key={`page_${index + 1}`}
|
||||||
@ -69,8 +63,7 @@ export default function PDFViewer(props) {
|
|||||||
if (e.height) setPageHeight(e.height);
|
if (e.height) setPageHeight(e.height);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}}
|
}}
|
||||||
onRenderError={() => setLoading(false)}
|
onRenderError={() => setLoading(false)}></Page>
|
||||||
></Page>
|
|
||||||
{props?.fields
|
{props?.fields
|
||||||
.filter((field) => field.page === index)
|
.filter((field) => field.page === index)
|
||||||
.map((field) =>
|
.map((field) =>
|
||||||
@ -80,21 +73,15 @@ export default function PDFViewer(props) {
|
|||||||
key={field.id}
|
key={field.id}
|
||||||
field={field}
|
field={field}
|
||||||
className="absolute"
|
className="absolute"
|
||||||
onDelete={onDeleteHandler}
|
onDelete={onDeleteHandler}></SignableField>
|
||||||
></SignableField>
|
|
||||||
) : (
|
) : (
|
||||||
<EditableField
|
<EditableField
|
||||||
hidden={
|
hidden={field.Signature || field.inserted || field.type === FieldType.FREE_SIGNATURE}
|
||||||
field.Signature ||
|
|
||||||
field.inserted ||
|
|
||||||
field.type === FieldType.FREE_SIGNATURE
|
|
||||||
}
|
|
||||||
key={field.id}
|
key={field.id}
|
||||||
field={field}
|
field={field}
|
||||||
className="absolute"
|
className="absolute"
|
||||||
onPositionChanged={onPositionChangedHandler}
|
onPositionChanged={onPositionChangedHandler}
|
||||||
onDelete={onDeleteHandler}
|
onDelete={onDeleteHandler}></EditableField>
|
||||||
></EditableField>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
|
import { classNames } from "@documenso/lib";
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
|
||||||
import { classNames } from "@documenso/lib";
|
|
||||||
const stc = require("string-to-color");
|
const stc = require("string-to-color");
|
||||||
|
|
||||||
export default function RecipientSelector(props: any) {
|
export default function RecipientSelector(props: any) {
|
||||||
const [selectedRecipient, setSelectedRecipient]: any = useState(
|
const [selectedRecipient, setSelectedRecipient]: any = useState(props?.recipients[0]);
|
||||||
props?.recipients[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.onChange(selectedRecipient);
|
props.onChange(selectedRecipient);
|
||||||
@ -18,11 +17,10 @@ export default function RecipientSelector(props: any) {
|
|||||||
value={selectedRecipient}
|
value={selectedRecipient}
|
||||||
onChange={(e: any) => {
|
onChange={(e: any) => {
|
||||||
setSelectedRecipient(e);
|
setSelectedRecipient(e);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<div className="relative mt-1 mb-2">
|
<div className="relative mt-1 mb-2">
|
||||||
<Listbox.Button className="select-none relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left focus:border-neon focus:outline-none focus:ring-1 focus:ring-neon sm:text-sm">
|
<Listbox.Button className="focus:border-neon focus:ring-neon relative w-full cursor-default select-none rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-1 sm:text-sm">
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
<span
|
<span
|
||||||
className="inline-block h-4 w-4 flex-shrink-0 rounded-full"
|
className="inline-block h-4 w-4 flex-shrink-0 rounded-full"
|
||||||
@ -33,10 +31,7 @@ export default function RecipientSelector(props: any) {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<ChevronUpDownIcon
|
<ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
|
|
||||||
@ -45,20 +40,18 @@ export default function RecipientSelector(props: any) {
|
|||||||
as={Fragment}
|
as={Fragment}
|
||||||
leave="transition ease-in duration-100"
|
leave="transition ease-in duration-100"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0">
|
||||||
>
|
|
||||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||||
{props?.recipients.map((recipient: any) => (
|
{props?.recipients.map((recipient: any) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={recipient?.id}
|
key={recipient?.id}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
classNames(
|
classNames(
|
||||||
active ? "text-white bg-neon-dark" : "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"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={recipient}
|
value={recipient}>
|
||||||
>
|
|
||||||
{({ selected, active }) => (
|
{({ selected, active }) => (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
@ -72,8 +65,7 @@ export default function RecipientSelector(props: any) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
selected ? "font-semibold" : "font-normal",
|
selected ? "font-semibold" : "font-normal",
|
||||||
"ml-3 block truncate"
|
"ml-3 block truncate"
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{`${selectedRecipient?.name} <${selectedRecipient?.email}>`}
|
{`${selectedRecipient?.name} <${selectedRecipient?.email}>`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -83,8 +75,7 @@ export default function RecipientSelector(props: any) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
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" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import Draggable from "react-draggable";
|
import { classNames } from "@documenso/lib";
|
||||||
import { IconButton } from "@documenso/ui";
|
import { IconButton } from "@documenso/ui";
|
||||||
import { XCircleIcon } from "@heroicons/react/20/solid";
|
import { XCircleIcon } from "@heroicons/react/20/solid";
|
||||||
import { classNames } from "@documenso/lib";
|
import Draggable from "react-draggable";
|
||||||
|
|
||||||
const stc = require("string-to-color");
|
const stc = require("string-to-color");
|
||||||
|
|
||||||
type FieldPropsType = {
|
type FieldPropsType = {
|
||||||
@ -37,31 +38,26 @@ export default function SignableField(props: FieldPropsType) {
|
|||||||
onMouseDown={(e: any) => {
|
onMouseDown={(e: any) => {
|
||||||
// e.preventDefault();
|
// e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
onClick={(e: any) => {
|
onClick={(e: any) => {
|
||||||
if (!field?.signature) props.onClick(props.field);
|
if (!field?.signature) props.onClick(props.field);
|
||||||
}}
|
}}
|
||||||
ref={nodeRef}
|
ref={nodeRef}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"opacity-80 m-auto w-48 h-16 flex-row-reverse text-lg font-bold text-center absolute top-0 left-0 select-none",
|
"absolute top-0 left-0 m-auto h-16 w-48 select-none flex-row-reverse text-center text-lg font-bold opacity-80",
|
||||||
field.type === "SIGNATURE"
|
field.type === "SIGNATURE" ? "cursor-pointer hover:brightness-50" : "cursor-not-allowed"
|
||||||
? "cursor-pointer hover:brightness-50"
|
|
||||||
: "cursor-not-allowed"
|
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
background: stc(props.field.Recipient.email),
|
background: stc(props.field.Recipient.email),
|
||||||
}}
|
}}>
|
||||||
>
|
<div hidden={field?.signature} className="my-4 font-medium">
|
||||||
<div hidden={field?.signature} className="font-medium my-4">
|
|
||||||
{field.type === "SIGNATURE" ? "SIGN HERE" : ""}
|
{field.type === "SIGNATURE" ? "SIGN HERE" : ""}
|
||||||
{field.type === "DATE" ? <small>Date (filled on sign)</small> : ""}
|
{field.type === "DATE" ? <small>Date (filled on sign)</small> : ""}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
hidden={!field?.signature}
|
hidden={!field?.signature}
|
||||||
className="font-qwigley text-5xl m-auto w-auto flex-row-reverse font-medium text-center"
|
className="font-qwigley m-auto w-auto flex-row-reverse text-center text-5xl font-medium">
|
||||||
>
|
|
||||||
{field?.signature?.type === "type" ? (
|
{field?.signature?.type === "type" ? (
|
||||||
<div className="my-4">{field?.signature.typedSignature}</div>
|
<div className="my-4">{field?.signature.typedSignature}</div>
|
||||||
) : (
|
) : (
|
||||||
@ -69,7 +65,7 @@ export default function SignableField(props: FieldPropsType) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{field?.signature?.type === "draw" ? (
|
{field?.signature?.type === "draw" ? (
|
||||||
<img className="w-48 h-16" src={field?.signature?.signatureImage} />
|
<img className="h-16 w-48" src={field?.signature?.signatureImage} />
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { classNames } from "@documenso/lib";
|
import { classNames } from "@documenso/lib";
|
||||||
|
import { localStorage } from "@documenso/lib";
|
||||||
import { Button, IconButton } from "@documenso/ui";
|
import { Button, IconButton } from "@documenso/ui";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {
|
import { LanguageIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
LanguageIcon,
|
|
||||||
PencilIcon,
|
|
||||||
TrashIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { Fragment, useEffect, useState } from "react";
|
|
||||||
import SignatureCanvas from "react-signature-canvas";
|
import SignatureCanvas from "react-signature-canvas";
|
||||||
import { localStorage } from "@documenso/lib";
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "Type", icon: LanguageIcon, current: true },
|
{ name: "Type", icon: LanguageIcon, current: true },
|
||||||
@ -34,8 +30,7 @@ export default function SignatureDialog(props: any) {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
props.setOpen(false);
|
props.setOpen(false);
|
||||||
setCurrent(tabs[0]);
|
setCurrent(tabs[0]);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
@ -43,8 +38,7 @@ export default function SignatureDialog(props: any) {
|
|||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0">
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
@ -57,11 +51,10 @@ export default function SignatureDialog(props: any) {
|
|||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
||||||
>
|
<Dialog.Panel className="relative min-h-[350px] transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
|
||||||
<Dialog.Panel className="min-h-[350px] relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="border-b border-gray-200 mb-3">
|
<div className="mb-3 border-b border-gray-200">
|
||||||
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
|
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<a
|
<a
|
||||||
@ -72,16 +65,13 @@ export default function SignatureDialog(props: any) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
tab.current
|
tab.current
|
||||||
? "border-neon text-neon"
|
? "border-neon text-neon"
|
||||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
|
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
|
||||||
"group inline-flex items-center py-4 px-1 border-b-2 font-medium text-sm cursor-pointer"
|
"group inline-flex cursor-pointer items-center border-b-2 py-4 px-1 text-sm font-medium"
|
||||||
)}
|
)}
|
||||||
aria-current={tab.current ? "page" : undefined}
|
aria-current={tab.current ? "page" : undefined}>
|
||||||
>
|
|
||||||
<tab.icon
|
<tab.icon
|
||||||
className={classNames(
|
className={classNames(
|
||||||
tab.current
|
tab.current ? "text-neon" : "text-gray-400 group-hover:text-gray-500",
|
||||||
? "text-neon"
|
|
||||||
: "text-gray-400 group-hover:text-gray-500",
|
|
||||||
"-ml-0.5 mr-2 h-5 w-5"
|
"-ml-0.5 mr-2 h-5 w-5"
|
||||||
)}
|
)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@ -93,7 +83,7 @@ export default function SignatureDialog(props: any) {
|
|||||||
</div>
|
</div>
|
||||||
{isCurrentTab("Type") ? (
|
{isCurrentTab("Type") ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="my-8 border-b border-gray-300 mb-3">
|
<div className="my-8 mb-3 border-b border-gray-300">
|
||||||
<input
|
<input
|
||||||
value={typedSignature}
|
value={typedSignature}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -101,7 +91,7 @@ export default function SignatureDialog(props: any) {
|
|||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
typedSignature ? "font-qwigley text-4xl" : "",
|
typedSignature ? "font-qwigley text-4xl" : "",
|
||||||
"leading-none h-10 align-bottom mt-14 text-center block w-full focus:border-neon focus:ring-neon text-2xl"
|
"focus:border-neon focus:ring-neon mt-14 block h-10 w-full text-center align-bottom text-2xl leading-none"
|
||||||
)}
|
)}
|
||||||
placeholder="Kindly type your name"
|
placeholder="Kindly type your name"
|
||||||
/>
|
/>
|
||||||
@ -113,24 +103,19 @@ export default function SignatureDialog(props: any) {
|
|||||||
props.onClose();
|
props.onClose();
|
||||||
props.setOpen(false);
|
props.setOpen(false);
|
||||||
setCurrent(tabs[0]);
|
setCurrent(tabs[0]);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="ml-3"
|
className="ml-3"
|
||||||
disabled={!typedSignature}
|
disabled={!typedSignature}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
localStorage.setItem(
|
localStorage.setItem("typedSignature", typedSignature);
|
||||||
"typedSignature",
|
|
||||||
typedSignature
|
|
||||||
);
|
|
||||||
props.onClose({
|
props.onClose({
|
||||||
type: "type",
|
type: "type",
|
||||||
typedSignature: typedSignature,
|
typedSignature: typedSignature,
|
||||||
});
|
});
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Sign
|
Sign
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -145,8 +130,7 @@ export default function SignatureDialog(props: any) {
|
|||||||
signCanvasRef = ref;
|
signCanvasRef = ref;
|
||||||
}}
|
}}
|
||||||
canvasProps={{
|
canvasProps={{
|
||||||
className:
|
className: "sigCanvas border-b b-2 border-slate w-full h-full mb-3",
|
||||||
"sigCanvas border-b b-2 border-slate w-full h-full mb-3",
|
|
||||||
}}
|
}}
|
||||||
clearOnResize={true}
|
clearOnResize={true}
|
||||||
onEnd={() => {
|
onEnd={() => {
|
||||||
@ -154,22 +138,20 @@ export default function SignatureDialog(props: any) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="block float-left"
|
className="float-left block"
|
||||||
icon={TrashIcon}
|
icon={TrashIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signCanvasRef?.clear();
|
signCanvasRef?.clear();
|
||||||
setSignatureEmpty(signCanvasRef?.isEmpty());
|
setSignatureEmpty(signCanvasRef?.isEmpty());
|
||||||
}}
|
}}></IconButton>
|
||||||
></IconButton>
|
<div className="float-right mt-10">
|
||||||
<div className="mt-10 float-right">
|
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onClose();
|
props.onClose();
|
||||||
props.setOpen(false);
|
props.setOpen(false);
|
||||||
setCurrent(tabs[0]);
|
setCurrent(tabs[0]);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -177,12 +159,10 @@ export default function SignatureDialog(props: any) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onClose({
|
props.onClose({
|
||||||
type: "draw",
|
type: "draw",
|
||||||
signatureImage:
|
signatureImage: signCanvasRef.toDataURL("image/png"),
|
||||||
signCanvasRef.toDataURL("image/png"),
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={signatureEmpty}
|
disabled={signatureEmpty}>
|
||||||
>
|
|
||||||
Sign
|
Sign
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -200,11 +180,11 @@ export default function SignatureDialog(props: any) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
function isCurrentTab(tabName: string): boolean {
|
function isCurrentTab(tabName: string): boolean {
|
||||||
return currentTab.name === tabName;
|
return currentTab.name === tabName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCurrent(t: any) {
|
function setCurrent(t: any) {
|
||||||
tabs.forEach((tab) => {
|
tabs.forEach((tab) => {
|
||||||
tab.current = tab.name === t.name;
|
tab.current = tab.name === t.name;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
||||||
|
|
||||||
import Navigation from "./navigation";
|
import Navigation from "./navigation";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
function useRedirectToLoginIfUnauthenticated() {
|
function useRedirectToLoginIfUnauthenticated() {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { LockClosedIcon } from "@heroicons/react/20/solid";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
|
||||||
|
|
||||||
import Logo from "./logo";
|
|
||||||
import { signIn } from "next-auth/react";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
||||||
import { Button } from "@documenso/ui";
|
import { Button } from "@documenso/ui";
|
||||||
|
import Logo from "./logo";
|
||||||
|
import { LockClosedIcon } from "@heroicons/react/20/solid";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
interface LoginValues {
|
interface LoginValues {
|
||||||
email: string;
|
email: string;
|
||||||
@ -22,10 +21,7 @@ export default function Login(props: any) {
|
|||||||
const methods = useForm<LoginValues>();
|
const methods = useForm<LoginValues>();
|
||||||
const { register, formState } = methods;
|
const { register, formState } = methods;
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
let callbackUrl =
|
let callbackUrl = typeof router.query?.callbackUrl === "string" ? router.query.callbackUrl : "";
|
||||||
typeof router.query?.callbackUrl === "string"
|
|
||||||
? router.query.callbackUrl
|
|
||||||
: "";
|
|
||||||
|
|
||||||
// If not absolute URL, make it absolute
|
// If not absolute URL, make it absolute
|
||||||
if (!/^https?:\/\//.test(callbackUrl)) {
|
if (!/^https?:\/\//.test(callbackUrl)) {
|
||||||
@ -79,10 +75,7 @@ export default function Login(props: any) {
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form
|
<form className="mt-8 space-y-6" onSubmit={methods.handleSubmit(onSubmit)}>
|
||||||
className="mt-8 space-y-6"
|
|
||||||
onSubmit={methods.handleSubmit(onSubmit)}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="remember" defaultValue="true" />
|
<input type="hidden" name="remember" defaultValue="true" />
|
||||||
<div className="-space-y-px rounded-md shadow-sm">
|
<div className="-space-y-px rounded-md shadow-sm">
|
||||||
<div>
|
<div>
|
||||||
@ -96,7 +89,7 @@ export default function Login(props: any) {
|
|||||||
type="email"
|
type="email"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
required
|
required
|
||||||
className="relative block w-full appearance-none rounded-none rounded-t-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-neon focus:outline-none focus:ring-neon sm:text-sm"
|
className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-none rounded-t-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -111,14 +104,14 @@ export default function Login(props: any) {
|
|||||||
type="password"
|
type="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
required
|
required
|
||||||
className="relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-neon focus:outline-none focus:ring-neon sm:text-sm"
|
className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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="font-medium text-neon hover:text-neon">
|
<a href="#" className="text-neon hover:text-neon font-medium">
|
||||||
Forgot your password?
|
Forgot your password?
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -127,11 +120,10 @@ export default function Login(props: any) {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={formState.isSubmitting}
|
disabled={formState.isSubmitting}
|
||||||
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="h-5 w-5 text-neon-dark group-hover:text-neon disabled:group-hover:bg-gray-600 disabled:disabled:bg-gray-600"
|
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>
|
||||||
@ -140,10 +132,7 @@ export default function Login(props: any) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div
|
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||||
className="absolute inset-0 flex items-center"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<div className="w-full border-t border-gray-300" />
|
<div className="w-full border-t border-gray-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex justify-center"></div>
|
<div className="relative flex justify-center"></div>
|
||||||
@ -152,20 +141,14 @@ 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
|
<Link href="/signup" className="text-neon hover:text-neon font-medium">
|
||||||
href="/signup"
|
|
||||||
className="font-medium text-neon hover:text-neon"
|
|
||||||
>
|
|
||||||
Create a new Account
|
Create a new Account
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="mt-2 text-center text-sm text-gray-600">
|
<p className="mt-2 text-center text-sm text-gray-600">
|
||||||
Like Documenso{" "}
|
Like Documenso{" "}
|
||||||
<Link
|
<Link href="https://documenso.com" className="text-neon hover:text-neon font-medium">
|
||||||
href="https://documenso.com"
|
|
||||||
className="font-medium text-neon hover:text-neon"
|
|
||||||
>
|
|
||||||
Hosted Documenso will be availible soon™
|
Hosted Documenso will be availible soon™
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,25 +1,16 @@
|
|||||||
import { classNames } from "@documenso/lib";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { classNames } from "@documenso/lib";
|
||||||
|
|
||||||
export default function Logo(props: any) {
|
export default function Logo(props: any) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link href="/dashboard">
|
<Link href="/dashboard">
|
||||||
<svg
|
<svg className="w-12" viewBox="0 0 88.6758041381836 32.18000030517578" {...props}>
|
||||||
className="w-12"
|
<rect width="88.6758041381836" height="32.18000030517578" fill="transparent"></rect>
|
||||||
viewBox="0 0 88.6758041381836 32.18000030517578"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
width="88.6758041381836"
|
|
||||||
height="32.18000030517578"
|
|
||||||
fill="transparent"
|
|
||||||
></rect>
|
|
||||||
<g transform="matrix(1,0,0,1,-25.98720359802246,-66.41200256347656)">
|
<g transform="matrix(1,0,0,1,-25.98720359802246,-66.41200256347656)">
|
||||||
<path
|
<path
|
||||||
d="M99.004,68.26l-.845-1.848-23.226,2.299c-1.437,.142-2.876,.089-4.3-.156-2.949-.51-6.32-1.717-9.073-2.099-1.477-.206-2.934,.448-3.789,1.67-1.174,1.678-2.308,3.888-3.501,5.622-1.518,2.207-3.032,4.418-4.531,6.638-1.693,2.499-3.365,5.013-5.061,7.51-.103,.153-.333,.221-.503,.329-.079-.279-.306-.636-.206-.824,1.006-1.928,1.845-4,3.165-5.694,1.908-2.449,2.914-5.445,5.007-7.745,.342-.777,.845-1.466,1.151-2.244,.915-2.341-1.295-5.305-2.935-5.305h-.613c-1.196,0-2.526,.357-4.092,1.716-.986,.856-2.391,2.432-2.97,3.326-3.424,5.286-7.177,10.382-10.15,15.932-.365,.682-1.719,3.722-2.013,3.606-.214-.077-.159-.458-.041-.823,1.312-4.065,4.163-9.851,5.843-13.777,.41-.962,.635-1.516-.305-2.361-1.016-.913-2.669,.084-3.084,1.052-1.784,4.174-2.631,8.08-4.038,12.396-.466,1.43-.916,2.865-1.386,4.294-.273,.831-.548,1.661-.836,2.488-.112,.321-.226,.642-.345,.962-.032,.082-.064,.165-.095,.248-.006,.011-.003,.002-.009,.017-.005,.008-.003,.015-.006,.023-.011,.026-.021,.05-.03,.076-.024,.067-.012,.044,.009-.002-.691,1.577,.417,3.002,2.091,3.002,.808,0,1.552-1.431,1.943-2.055,1.224-1.957,3.28-5.125,4.36-7.163,.894-1.69,2.078-3.14,3.209-4.615,1.857-2.42,3.065-5.242,5.025-7.575,.33-.394,.694-.772,1.093-1.093,.121-.098,.473-.05,.618,.062,.124,.098,.2,.432,.127,.577-.397,.788-.863,1.542-1.278,2.322-1.481,2.79-2.953,5.582-4.425,8.377-1.16,2.204-2.659,5.055-3.551,6.755-.438,.833-.176,1.857,.604,2.382,0,0-.383,2.028,1.908,2.028,1.896,0,2.711-1.145,3.053-1.624,.348-.486,.748-.951,1.202-1.334,.151-.13,.563-.018,.821,.079,.076,.029,.085,.406,.03,.582-.398,1.272-.323,2.283,1.879,2.283,.784,0,2.218-1.283,2.904-2.213,.794-1.075,.731-1.1,1.415-2.244,1.678-2.821,3.132-5.055,4.751-7.91,1.083-1.91,2.175-3.854,3.294-5.516,.685-1.015,1.446-1.252,2.188-1.252s.409,.686,.336,1.025c-.071,.341-.677,1.545-2.531,4.35-1.115,1.687-2.244,3.367-3.308,5.084-1.075,1.736-2.141,3.48-3.205,5.225-.88,1.445,.162,3.467,1.854,3.467h2.765c2.653,0,5.302-.397,7.916-.851l7.41-1.287c1.421-.245,2.868-.298,4.303-.158l23.161,2.296,.845-1.849c4.61-9.858,15.211-11.322,15.66-11.38v-5.717c-.448-.058-11.05-1.522-15.66-11.381Zm-40.143,6.165c-.164,.465-.491,.922-.86,1.255-.918,.828-1.451,1.842-1.911,2.977-.512,1.269-1.829,2.119-1.966,3.65-.027,.3-.627,.577-1.006,.797-.109,.062-.357-.114-.524-.174,.015-.229-.024-.401,.039-.515,.848-1.495,1.678-3.002,2.584-4.462,.775-1.254,1.642-2.453,2.469-3.677,.085-.126,.158-.273,.27-.365,.457-.368,.93-.719,1.399-1.075,.497,.721-.312,1.071-.494,1.589Zm30.047,12.011c1.736,0,3.162-1.137,3.686-2.693h10.547c-3.02,1.916-6.1,4.684-8.402,8.716l-21.902-2.17v-15.584l21.902-2.167c2.302,4.032,5.382,6.802,8.402,8.714h-10.547c-.524-1.554-1.951-2.691-3.686-2.691-2.172,0-3.938,1.763-3.938,3.938s1.766,3.938,3.938,3.938Z"
|
d="M99.004,68.26l-.845-1.848-23.226,2.299c-1.437,.142-2.876,.089-4.3-.156-2.949-.51-6.32-1.717-9.073-2.099-1.477-.206-2.934,.448-3.789,1.67-1.174,1.678-2.308,3.888-3.501,5.622-1.518,2.207-3.032,4.418-4.531,6.638-1.693,2.499-3.365,5.013-5.061,7.51-.103,.153-.333,.221-.503,.329-.079-.279-.306-.636-.206-.824,1.006-1.928,1.845-4,3.165-5.694,1.908-2.449,2.914-5.445,5.007-7.745,.342-.777,.845-1.466,1.151-2.244,.915-2.341-1.295-5.305-2.935-5.305h-.613c-1.196,0-2.526,.357-4.092,1.716-.986,.856-2.391,2.432-2.97,3.326-3.424,5.286-7.177,10.382-10.15,15.932-.365,.682-1.719,3.722-2.013,3.606-.214-.077-.159-.458-.041-.823,1.312-4.065,4.163-9.851,5.843-13.777,.41-.962,.635-1.516-.305-2.361-1.016-.913-2.669,.084-3.084,1.052-1.784,4.174-2.631,8.08-4.038,12.396-.466,1.43-.916,2.865-1.386,4.294-.273,.831-.548,1.661-.836,2.488-.112,.321-.226,.642-.345,.962-.032,.082-.064,.165-.095,.248-.006,.011-.003,.002-.009,.017-.005,.008-.003,.015-.006,.023-.011,.026-.021,.05-.03,.076-.024,.067-.012,.044,.009-.002-.691,1.577,.417,3.002,2.091,3.002,.808,0,1.552-1.431,1.943-2.055,1.224-1.957,3.28-5.125,4.36-7.163,.894-1.69,2.078-3.14,3.209-4.615,1.857-2.42,3.065-5.242,5.025-7.575,.33-.394,.694-.772,1.093-1.093,.121-.098,.473-.05,.618,.062,.124,.098,.2,.432,.127,.577-.397,.788-.863,1.542-1.278,2.322-1.481,2.79-2.953,5.582-4.425,8.377-1.16,2.204-2.659,5.055-3.551,6.755-.438,.833-.176,1.857,.604,2.382,0,0-.383,2.028,1.908,2.028,1.896,0,2.711-1.145,3.053-1.624,.348-.486,.748-.951,1.202-1.334,.151-.13,.563-.018,.821,.079,.076,.029,.085,.406,.03,.582-.398,1.272-.323,2.283,1.879,2.283,.784,0,2.218-1.283,2.904-2.213,.794-1.075,.731-1.1,1.415-2.244,1.678-2.821,3.132-5.055,4.751-7.91,1.083-1.91,2.175-3.854,3.294-5.516,.685-1.015,1.446-1.252,2.188-1.252s.409,.686,.336,1.025c-.071,.341-.677,1.545-2.531,4.35-1.115,1.687-2.244,3.367-3.308,5.084-1.075,1.736-2.141,3.48-3.205,5.225-.88,1.445,.162,3.467,1.854,3.467h2.765c2.653,0,5.302-.397,7.916-.851l7.41-1.287c1.421-.245,2.868-.298,4.303-.158l23.161,2.296,.845-1.849c4.61-9.858,15.211-11.322,15.66-11.38v-5.717c-.448-.058-11.05-1.522-15.66-11.381Zm-40.143,6.165c-.164,.465-.491,.922-.86,1.255-.918,.828-1.451,1.842-1.911,2.977-.512,1.269-1.829,2.119-1.966,3.65-.027,.3-.627,.577-1.006,.797-.109,.062-.357-.114-.524-.174,.015-.229-.024-.401,.039-.515,.848-1.495,1.678-3.002,2.584-4.462,.775-1.254,1.642-2.453,2.469-3.677,.085-.126,.158-.273,.27-.365,.457-.368,.93-.719,1.399-1.075,.497,.721-.312,1.071-.494,1.589Zm30.047,12.011c1.736,0,3.162-1.137,3.686-2.693h10.547c-3.02,1.916-6.1,4.684-8.402,8.716l-21.902-2.17v-15.584l21.902-2.167c2.302,4.032,5.382,6.802,8.402,8.714h-10.547c-.524-1.554-1.951-2.691-3.686-2.691-2.172,0-3.938,1.763-3.938,3.938s1.766,3.938,3.938,3.938Z"
|
||||||
className={classNames(props.dark ? "fill-white" : "fill-brown")}
|
className={classNames(props.dark ? "fill-white" : "fill-brown")}></path>
|
||||||
></path>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@ -1,23 +1,22 @@
|
|||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { signOut, useSession } from "next-auth/react";
|
import { getUser } from "@documenso/lib/api";
|
||||||
import avatarFromInitials from "avatar-from-initials";
|
import Logo from "./logo";
|
||||||
import { toast } from "react-hot-toast";
|
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ArrowRightOnRectangleIcon,
|
||||||
Bars3Icon,
|
Bars3Icon,
|
||||||
BellIcon,
|
BellIcon,
|
||||||
XMarkIcon,
|
|
||||||
UserCircleIcon,
|
|
||||||
ArrowRightOnRectangleIcon,
|
|
||||||
DocumentTextIcon,
|
|
||||||
ChartBarIcon,
|
ChartBarIcon,
|
||||||
|
DocumentTextIcon,
|
||||||
|
UserCircleIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import Logo from "./logo";
|
import avatarFromInitials from "avatar-from-initials";
|
||||||
import { getUser } from "@documenso/lib/api";
|
import { signOut, useSession } from "next-auth/react";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{
|
{
|
||||||
@ -125,14 +124,12 @@ export default function TopNavigation() {
|
|||||||
item.current
|
item.current
|
||||||
? "border-neon text-brown"
|
? "border-neon text-brown"
|
||||||
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
|
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
|
||||||
"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
|
"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium"
|
||||||
)}
|
)}
|
||||||
aria-current={item.current ? "page" : undefined}
|
aria-current={item.current ? "page" : undefined}>
|
||||||
>
|
|
||||||
<item.icon
|
<item.icon
|
||||||
className="flex-shrink-0 -ml-1 mr-3 h-6 w-6 inline"
|
className="-ml-1 mr-3 inline h-6 w-6 flex-shrink-0"
|
||||||
aria-hidden="true"
|
aria-hidden="true"></item.icon>
|
||||||
></item.icon>
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
@ -142,8 +139,7 @@ export default function TopNavigation() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
document?.getElementById("mb")?.click();
|
document?.getElementById("mb")?.click();
|
||||||
}}
|
}}
|
||||||
className="hidden sm:ml-6 sm:flex sm:items-center hover:bg-gray-200 px-3 cursor-pointer"
|
className="hidden cursor-pointer px-3 hover:bg-gray-200 sm:ml-6 sm:flex sm:items-center">
|
||||||
>
|
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<p className="font-bold">{user?.name || ""}</p>
|
<p className="font-bold">{user?.name || ""}</p>
|
||||||
<p>{user?.email}</p>
|
<p>{user?.email}</p>
|
||||||
@ -152,8 +148,7 @@ export default function TopNavigation() {
|
|||||||
<div>
|
<div>
|
||||||
<Menu.Button
|
<Menu.Button
|
||||||
id="mb"
|
id="mb"
|
||||||
className="flex max-w-xs items-center rounded-full bg-white text-sm"
|
className="flex max-w-xs items-center rounded-full bg-white text-sm">
|
||||||
>
|
|
||||||
<span className="sr-only">Open user menu</span>
|
<span className="sr-only">Open user menu</span>
|
||||||
<div
|
<div
|
||||||
key={user?.email}
|
key={user?.email}
|
||||||
@ -170,8 +165,7 @@ export default function TopNavigation() {
|
|||||||
enterTo="transform opacity-100 scale-100"
|
enterTo="transform opacity-100 scale-100"
|
||||||
leave="transition ease-in duration-75"
|
leave="transition ease-in duration-75"
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95">
|
||||||
>
|
|
||||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||||
{userNavigation.map((item) => (
|
{userNavigation.map((item) => (
|
||||||
<Menu.Item key={item.name}>
|
<Menu.Item key={item.name}>
|
||||||
@ -182,12 +176,10 @@ export default function TopNavigation() {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
active ? "bg-gray-100" : "",
|
active ? "bg-gray-100" : "",
|
||||||
"block px-4 py-2 text-sm text-gray-700"
|
"block px-4 py-2 text-sm text-gray-700"
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<item.icon
|
<item.icon
|
||||||
className="flex-shrink-0 -ml-1 mr-3 h-6 w-6 inline"
|
className="-ml-1 mr-3 inline h-6 w-6 flex-shrink-0"
|
||||||
aria-hidden="true"
|
aria-hidden="true"></item.icon>
|
||||||
></item.icon>
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@ -219,15 +211,14 @@ export default function TopNavigation() {
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.current
|
item.current
|
||||||
? "bg-teal-50 border-teal-500 text-teal-700"
|
? "border-teal-500 bg-teal-50 text-teal-700"
|
||||||
: "border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800",
|
: "border-transparent text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800",
|
||||||
"block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
|
"block border-l-4 py-2 pl-3 pr-4 text-base font-medium"
|
||||||
)}
|
)}
|
||||||
aria-current={item.current ? "page" : undefined}
|
aria-current={item.current ? "page" : undefined}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
close();
|
close();
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
@ -259,8 +250,7 @@ export default function TopNavigation() {
|
|||||||
: item.click
|
: item.click
|
||||||
}
|
}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800"
|
className="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800">
|
||||||
>
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { ChangeEvent, useEffect, useState } from "react";
|
import { ChangeEvent, useEffect, useState } from "react";
|
||||||
import { KeyIcon, UserCircleIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import Link from "next/link";
|
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useSession } from "next-auth/react";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { updateUser } from "@documenso/features";
|
import { updateUser } from "@documenso/features";
|
||||||
import { Button } from "@documenso/ui";
|
|
||||||
import { getUser } from "@documenso/lib/api";
|
import { getUser } from "@documenso/lib/api";
|
||||||
|
import { Button } from "@documenso/ui";
|
||||||
|
import { KeyIcon, UserCircleIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
const subNavigation = [
|
const subNavigation = [
|
||||||
{
|
{
|
||||||
@ -74,15 +74,10 @@ export default function Setttings() {
|
|||||||
</Head>
|
</Head>
|
||||||
<header className="py-10">
|
<header className="py-10">
|
||||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-brown">
|
<h1 className="text-brown text-3xl font-bold leading-tight tracking-tight">Settings</h1>
|
||||||
Settings
|
|
||||||
</h1>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div
|
<div className="mx-auto max-w-screen-xl px-4 pb-6 sm:px-6 lg:px-8 lg:pb-16" hidden={!user.email}>
|
||||||
className="mx-auto max-w-screen-xl px-4 pb-6 sm:px-6 lg:px-8 lg:pb-16"
|
|
||||||
hidden={!user.email}
|
|
||||||
>
|
|
||||||
<div className="overflow-hidden rounded-lg bg-white shadow">
|
<div className="overflow-hidden rounded-lg bg-white shadow">
|
||||||
<div className="divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
<div className="divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
||||||
<aside className="py-6 lg:col-span-3">
|
<aside className="py-6 lg:col-span-3">
|
||||||
@ -93,18 +88,17 @@ export default function Setttings() {
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.current
|
item.current
|
||||||
? "bg-teal-50 border-neon-dark text-teal-700 hover:bg-teal-50 hover:text-teal-700"
|
? "border-neon-dark bg-teal-50 text-teal-700 hover:bg-teal-50 hover:text-teal-700"
|
||||||
: "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900",
|
: "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900",
|
||||||
"group border-l-4 px-3 py-2 flex items-center text-sm font-medium"
|
"group flex items-center border-l-4 px-3 py-2 text-sm font-medium"
|
||||||
)}
|
)}
|
||||||
aria-current={item.current ? "page" : undefined}
|
aria-current={item.current ? "page" : undefined}>
|
||||||
>
|
|
||||||
<item.icon
|
<item.icon
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.current
|
item.current
|
||||||
? "text-teal-500 group-hover:text-teal-500"
|
? "text-teal-500 group-hover:text-teal-500"
|
||||||
: "text-gray-400 group-hover:text-gray-500",
|
: "text-gray-400 group-hover:text-gray-500",
|
||||||
"flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
"-ml-1 mr-3 h-6 w-6 flex-shrink-0"
|
||||||
)}
|
)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@ -115,20 +109,14 @@ export default function Setttings() {
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
className="divide-y divide-gray-200 lg:col-span-9 min-h-[251px]"
|
className="min-h-[251px] divide-y divide-gray-200 lg:col-span-9"
|
||||||
action="#"
|
action="#"
|
||||||
method="POST"
|
method="POST"
|
||||||
hidden={
|
hidden={subNavigation.filter((e) => e.current)[0]?.name !== subNavigation[0].name}>
|
||||||
subNavigation.filter((e) => e.current)[0]?.name !==
|
|
||||||
subNavigation[0].name
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{/* Profile section */}
|
{/* Profile section */}
|
||||||
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-medium leading-6 text-gray-900">
|
<h2 className="text-lg font-medium leading-6 text-gray-900">Profile</h2>
|
||||||
Profile
|
|
||||||
</h2>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Let people know who they are dealing with builds trust.
|
Let people know who they are dealing with builds trust.
|
||||||
</p>
|
</p>
|
||||||
@ -136,10 +124,7 @@ export default function Setttings() {
|
|||||||
|
|
||||||
<div className="my-6 grid grid-cols-12 gap-6">
|
<div className="my-6 grid grid-cols-12 gap-6">
|
||||||
<div className="col-span-12 sm:col-span-6">
|
<div className="col-span-12 sm:col-span-6">
|
||||||
<label
|
<label htmlFor="first-name" className="block text-sm font-medium text-gray-700">
|
||||||
htmlFor="first-name"
|
|
||||||
className="block text-sm font-medium text-gray-700"
|
|
||||||
>
|
|
||||||
Full Name
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -150,14 +135,11 @@ export default function Setttings() {
|
|||||||
onChange={(e) => handleNameChange(e)}
|
onChange={(e) => handleNameChange(e)}
|
||||||
onKeyDown={handleKeyPress}
|
onKeyDown={handleKeyPress}
|
||||||
autoComplete="given-name"
|
autoComplete="given-name"
|
||||||
className="mt-1 block w-full rounded-md border border-gray-300 py-2 px-3 shadow-sm focus:border-neon focus:outline-none focus:ring-neon sm:text-sm"
|
className="focus:border-neon focus:ring-neon mt-1 block w-full rounded-md border border-gray-300 py-2 px-3 shadow-sm focus:outline-none sm:text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-12 sm:col-span-6">
|
<div className="col-span-12 sm:col-span-6">
|
||||||
<label
|
<label htmlFor="first-name" className="block text-sm font-medium text-gray-700">
|
||||||
htmlFor="first-name"
|
|
||||||
className="block text-sm font-medium text-gray-700"
|
|
||||||
>
|
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -167,7 +149,7 @@ export default function Setttings() {
|
|||||||
name="first-name"
|
name="first-name"
|
||||||
id="first-name"
|
id="first-name"
|
||||||
autoComplete="given-name"
|
autoComplete="given-name"
|
||||||
className="mt-1 block w-full rounded-md border disabled:bg-neutral-100 border-gray-300 py-2 px-3 shadow-sm focus:border-neon focus:outline-none focus:ring-neon sm:text-sm"
|
className="focus:border-neon focus:ring-neon mt-1 block w-full rounded-md border border-gray-300 py-2 px-3 shadow-sm focus:outline-none disabled:bg-neutral-100 sm:text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -175,21 +157,14 @@ export default function Setttings() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div
|
<div
|
||||||
hidden={
|
hidden={subNavigation.filter((e) => e.current)[0]?.name !== subNavigation[1].name}
|
||||||
subNavigation.filter((e) => e.current)[0]?.name !==
|
className="min-h-[251px] divide-y divide-gray-200 lg:col-span-9">
|
||||||
subNavigation[1].name
|
|
||||||
}
|
|
||||||
className="divide-y divide-gray-200 lg:col-span-9 min-h-[251px]"
|
|
||||||
>
|
|
||||||
{/* Passwords section */}
|
{/* Passwords section */}
|
||||||
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-medium leading-6 text-gray-900">
|
<h2 className="text-lg font-medium leading-6 text-gray-900">Password</h2>
|
||||||
Password
|
|
||||||
</h2>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Forgot your passwort? Email <b>hi@documenso.com</b> to reset
|
Forgot your passwort? Email <b>hi@documenso.com</b> to reset it.
|
||||||
it.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import Link from "next/link";
|
||||||
import { signup } from "@documenso/lib/api";
|
import { signup } from "@documenso/lib/api";
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants";
|
||||||
import { Button } from "@documenso/ui";
|
import { Button } from "@documenso/ui";
|
||||||
import { XCircleIcon } from "@heroicons/react/24/outline";
|
import { XCircleIcon } from "@heroicons/react/24/outline";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import Link from "next/link";
|
|
||||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
|
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
@ -107,8 +107,7 @@ export default function Signup(props: { source: string }) {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
stroke="rgb(17 24 39 / var(--tw-text-opacity))"
|
stroke="rgb(17 24 39 / var(--tw-text-opacity))"
|
||||||
className="w-8 h-8 inline mb-1"
|
className="mb-1 inline h-8 w-8">
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@ -130,8 +129,7 @@ export default function Signup(props: { source: string }) {
|
|||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
trigger();
|
trigger();
|
||||||
}}
|
}}
|
||||||
className="mt-8 space-y-6"
|
className="mt-8 space-y-6">
|
||||||
>
|
|
||||||
<input type="hidden" name="remember" defaultValue="true" />
|
<input type="hidden" name="remember" defaultValue="true" />
|
||||||
<div className="-space-y-px rounded-md shadow-sm">
|
<div className="-space-y-px rounded-md shadow-sm">
|
||||||
<div>
|
<div>
|
||||||
@ -145,7 +143,7 @@ export default function Signup(props: { source: string }) {
|
|||||||
type="email"
|
type="email"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
required
|
required
|
||||||
className="relative block w-full appearance-none rounded-none rounded-t-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-neon focus:outline-none focus:ring-neon sm:text-sm"
|
className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-none rounded-t-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -157,8 +155,7 @@ export default function Signup(props: { source: string }) {
|
|||||||
{...register("password", {
|
{...register("password", {
|
||||||
minLength: {
|
minLength: {
|
||||||
value: 7,
|
value: 7,
|
||||||
message:
|
message: "Your password has to be at least 7 characters long.",
|
||||||
"Your password has to be at least 7 characters long.",
|
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
id="password"
|
id="password"
|
||||||
@ -166,7 +163,7 @@ export default function Signup(props: { source: string }) {
|
|||||||
type="password"
|
type="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
required
|
required
|
||||||
className="relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-neon focus:outline-none focus:ring-neon sm:text-sm"
|
className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -177,16 +174,12 @@ export default function Signup(props: { source: string }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
}}
|
}}
|
||||||
className="sgroup relative flex w-full"
|
className="sgroup relative flex w-full">
|
||||||
>
|
|
||||||
Create Account
|
Create Account
|
||||||
</Button>
|
</Button>
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div
|
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||||
className="absolute inset-0 flex items-center"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<div className="w-full border-t border-gray-300" />
|
<div className="w-full border-t border-gray-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex justify-center"></div>
|
<div className="relative flex justify-center"></div>
|
||||||
@ -194,10 +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
|
<Link href="/login" className="text-neon hover:text-neon font-medium">
|
||||||
href="/login"
|
|
||||||
className="font-medium text-neon hover:text-neon"
|
|
||||||
>
|
|
||||||
Sign In
|
Sign In
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -18,7 +18,6 @@ const withTM = require("next-transpile-modules")([
|
|||||||
const plugins = [];
|
const plugins = [];
|
||||||
plugins.push(withTM);
|
plugins.push(withTM);
|
||||||
|
|
||||||
const moduleExports = () =>
|
const moduleExports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
|
||||||
plugins.reduce((acc, next) => next(acc), nextConfig);
|
|
||||||
|
|
||||||
module.exports = moduleExports;
|
module.exports = moduleExports;
|
||||||
|
|||||||
@ -1,31 +1,27 @@
|
|||||||
import { ArrowSmallLeftIcon } from "@heroicons/react/20/solid";
|
|
||||||
|
|
||||||
import { Button } from "@documenso/ui";
|
import { Button } from "@documenso/ui";
|
||||||
import Logo from "../components/logo";
|
import Logo from "../components/logo";
|
||||||
|
import { ArrowSmallLeftIcon } from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
export default function Custom404() {
|
export default function Custom404() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="relative min-h-full bg-gray-100 isolate">
|
<main className="relative isolate min-h-full bg-gray-100">
|
||||||
<div className="absolute top-10 left-10">
|
<div className="absolute top-10 left-10">
|
||||||
<Logo className="w-10 md:w-20" />
|
<Logo className="w-10 md:w-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-6 py-48 mx-auto text-center max-w-7xl sm:py-40 lg:px-8">
|
<div className="mx-auto max-w-7xl px-6 py-48 text-center sm:py-40 lg:px-8">
|
||||||
<p className="text-base font-semibold leading-8 text-brown">404</p>
|
<p className="text-brown text-base font-semibold leading-8">404</p>
|
||||||
<h1 className="mt-4 text-3xl font-bold tracking-tight text-brown sm:text-5xl">
|
<h1 className="text-brown mt-4 text-3xl font-bold tracking-tight sm:text-5xl">Page not found</h1>
|
||||||
Page not found
|
|
||||||
</h1>
|
|
||||||
<p className="mt-4 text-base text-gray-700 sm:mt-6">
|
<p className="mt-4 text-base text-gray-700 sm:mt-6">
|
||||||
Sorry, we couldn’t find the page you’re looking for.
|
Sorry, we couldn’t find the page you’re looking for.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-center mt-10">
|
<div className="mt-10 flex justify-center">
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
href="/"
|
href="/"
|
||||||
icon={ArrowSmallLeftIcon}
|
icon={ArrowSmallLeftIcon}
|
||||||
className="text-base font-semibold leading-7 text-brown"
|
className="text-brown text-base font-semibold leading-7">
|
||||||
>
|
|
||||||
Back to home
|
Back to home
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,27 +1,23 @@
|
|||||||
import Logo from "../components/logo";
|
|
||||||
import { Button } from "@documenso/ui";
|
import { Button } from "@documenso/ui";
|
||||||
|
import Logo from "../components/logo";
|
||||||
import { ArrowSmallLeftIcon } from "@heroicons/react/20/solid";
|
import { ArrowSmallLeftIcon } from "@heroicons/react/20/solid";
|
||||||
import { EllipsisVerticalIcon } from "@heroicons/react/20/solid";
|
import { EllipsisVerticalIcon } from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
export default function Custom500() {
|
export default function Custom500() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex flex-col items-center justify-center min-h-full text-white bg-black">
|
<div className="relative flex min-h-full flex-col items-center justify-center bg-black text-white">
|
||||||
<div className="absolute top-10 left-10">
|
<div className="absolute top-10 left-10">
|
||||||
<Logo dark className="w-10 md:w-20" />
|
<Logo dark className="w-10 md:w-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-4 py-10 mt-20 max-w-7xl">
|
<div className="mt-20 max-w-7xl px-4 py-10">
|
||||||
<p className="inline-flex items-center text-3xl font-bold sm:text-5xl">
|
<p className="inline-flex items-center text-3xl font-bold sm:text-5xl">
|
||||||
500
|
500
|
||||||
<span className="relative px-3 font-thin sm:text-6xl -top-1.5">
|
<span className="relative -top-1.5 px-3 font-thin sm:text-6xl">|</span>{" "}
|
||||||
|
|
<span className="align-middle text-base font-semibold sm:text-2xl">Something went wrong.</span>
|
||||||
</span>{" "}
|
|
||||||
<span className="text-base font-semibold align-middle sm:text-2xl">
|
|
||||||
Something went wrong.
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-center mt-10">
|
<div className="mt-10 flex justify-center">
|
||||||
<Button color="secondary" href="/" icon={ArrowSmallLeftIcon}>
|
<Button color="secondary" href="/" icon={ArrowSmallLeftIcon}>
|
||||||
Back to home
|
Back to home
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import "../styles/tailwind.css";
|
import { ReactElement, ReactNode } from "react";
|
||||||
|
import { NextPage } from "next";
|
||||||
|
import type { AppProps } from "next/app";
|
||||||
import "../../../node_modules/placeholder-loading/src/scss/placeholder-loading.scss";
|
import "../../../node_modules/placeholder-loading/src/scss/placeholder-loading.scss";
|
||||||
import "../../../node_modules/react-resizable/css/styles.css";
|
import "../../../node_modules/react-resizable/css/styles.css";
|
||||||
import "react-tooltip/dist/react-tooltip.css";
|
import "../styles/tailwind.css";
|
||||||
import { ReactElement, ReactNode } from "react";
|
|
||||||
import type { AppProps } from "next/app";
|
|
||||||
import { NextPage } from "next";
|
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
export { coloredConsole } from "@documenso/lib";
|
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
|
import "react-tooltip/dist/react-tooltip.css";
|
||||||
|
|
||||||
|
export { coloredConsole } from "@documenso/lib";
|
||||||
|
|
||||||
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
|
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
|
||||||
getLayout?: (page: ReactElement) => ReactNode;
|
getLayout?: (page: ReactElement) => ReactNode;
|
||||||
@ -17,10 +18,7 @@ type AppPropsWithLayout = AppProps & {
|
|||||||
Component: NextPageWithLayout;
|
Component: NextPageWithLayout;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function App({
|
export default function App({ Component, pageProps: { session, ...pageProps } }: AppPropsWithLayout) {
|
||||||
Component,
|
|
||||||
pageProps: { session, ...pageProps },
|
|
||||||
}: AppPropsWithLayout) {
|
|
||||||
const getLayout = Component.getLayout || ((page: any) => page);
|
const getLayout = Component.getLayout || ((page: any) => page);
|
||||||
return (
|
return (
|
||||||
<SessionProvider session={session}>
|
<SessionProvider session={session}>
|
||||||
|
|||||||
@ -5,10 +5,7 @@ export default function Document(props) {
|
|||||||
let pageProps = props.__NEXT_DATA__?.props?.pageProps;
|
let pageProps = props.__NEXT_DATA__?.props?.pageProps;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html
|
<Html className="h-full scroll-smooth bg-gray-100 font-normal antialiased" lang="en">
|
||||||
className="h-full bg-gray-100 scroll-smooth font-normal antialiased"
|
|
||||||
lang="en"
|
|
||||||
>
|
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="color-scheme"></meta>
|
<meta name="color-scheme"></meta>
|
||||||
</Head>
|
</Head>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import NextAuth, { Session } from "next-auth";
|
|
||||||
import GitHubProvider from "next-auth/providers/github";
|
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
|
||||||
import { ErrorCode } from "@documenso/lib/auth";
|
import { ErrorCode } from "@documenso/lib/auth";
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { verifyPassword } from "@documenso/lib/auth";
|
import { verifyPassword } from "@documenso/lib/auth";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import NextAuth, { Session } from "next-auth";
|
||||||
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
|
import GitHubProvider from "next-auth/providers/github";
|
||||||
|
|
||||||
export default NextAuth({
|
export default NextAuth({
|
||||||
secret: process.env.AUTH_SECRET,
|
secret: process.env.AUTH_SECRET,
|
||||||
@ -27,8 +27,7 @@ export default NextAuth({
|
|||||||
password: {
|
password: {
|
||||||
label: "Password",
|
label: "Password",
|
||||||
type: "password",
|
type: "password",
|
||||||
placeholder:
|
placeholder: "Select a password. Here is some inspiration: https://xkcd.com/936/",
|
||||||
"Select a password. Here is some inspiration: https://xkcd.com/936/",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async authorize(credentials: any) {
|
async authorize(credentials: any) {
|
||||||
@ -57,10 +56,7 @@ export default NextAuth({
|
|||||||
throw new Error(ErrorCode.UserMissingPassword);
|
throw new Error(ErrorCode.UserMissingPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCorrectPassword = await verifyPassword(
|
const isCorrectPassword = await verifyPassword(credentials.password, user.password);
|
||||||
credentials.password,
|
|
||||||
user.password
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isCorrectPassword) {
|
if (!isCorrectPassword) {
|
||||||
throw new Error(ErrorCode.IncorrectPassword);
|
throw new Error(ErrorCode.IncorrectPassword);
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { IdentityProvider } from "@prisma/client";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { hashPassword } from "@documenso/lib/auth";
|
import { hashPassword } from "@documenso/lib/auth";
|
||||||
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { IdentityProvider } from "@prisma/client";
|
||||||
|
|
||||||
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { email, password, source } = req.body;
|
const { email, password, source } = req.body;
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
import { addDigitalSignature } from "@documenso/signing/addDigitalSignature";
|
import { addDigitalSignature } from "@documenso/signing/addDigitalSignature";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { id: documentId } = req.query;
|
const { id: documentId } = req.query;
|
||||||
@ -46,8 +42,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
document = await getDocument(+documentId, req, res);
|
document = await getDocument(+documentId, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!document)
|
if (!document) res.status(404).end(`No document with id ${documentId} found.`);
|
||||||
res.status(404).end(`No document with id ${documentId} found.`);
|
|
||||||
|
|
||||||
const signaturesCount = await prisma.signature.count({
|
const signaturesCount = await prisma.signature.count({
|
||||||
where: {
|
where: {
|
||||||
@ -61,18 +56,13 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
// No need to add a signature, if no one signed yet.
|
// No need to add a signature, if no one signed yet.
|
||||||
if (signaturesCount > 0) {
|
if (signaturesCount > 0) {
|
||||||
signedDocumentAsBase64 = await addDigitalSignature(
|
signedDocumentAsBase64 = await addDigitalSignature(document?.document || "");
|
||||||
document?.document || ""
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer: Buffer = Buffer.from(signedDocumentAsBase64, "base64");
|
const buffer: Buffer = Buffer.from(signedDocumentAsBase64, "base64");
|
||||||
res.setHeader("Content-Type", "application/pdf");
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
res.setHeader("Content-Length", buffer.length);
|
res.setHeader("Content-Length", buffer.length);
|
||||||
res.setHeader(
|
res.setHeader("Content-Disposition", `attachment; filename=${document?.title}`);
|
||||||
"Content-Disposition",
|
|
||||||
`attachment; filename=${document?.title}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send(buffer);
|
return res.status(200).send(buffer);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import short from "short-uuid";
|
|
||||||
import { Document as PrismaDocument, FieldType } from "@prisma/client";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { FieldType, Document as PrismaDocument } from "@prisma/client";
|
||||||
|
import short from "short-uuid";
|
||||||
|
|
||||||
async function deleteHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function deleteHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const user = await getUserFromToken(req, res);
|
const user = await getUserFromToken(req, res);
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { Document as PrismaDocument, FieldType } from "@prisma/client";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { FieldType, Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const user = await getUserFromToken(req, res);
|
const user = await getUserFromToken(req, res);
|
||||||
@ -61,18 +57,14 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!recipient || recipient?.documentId !== +documentId)
|
if (!recipient || recipient?.documentId !== +documentId)
|
||||||
return res
|
return res.status(401).send("Recipient does not have access to this document.");
|
||||||
.status(401)
|
|
||||||
.send("Recipient does not have access to this document.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const document: PrismaDocument = await getDocument(+documentId, req, res);
|
const document: PrismaDocument = await getDocument(+documentId, req, res);
|
||||||
// todo entity ownerships checks
|
// todo entity ownerships checks
|
||||||
if (document.userId !== user.id) {
|
if (document.userId !== user.id) {
|
||||||
return res
|
return res.status(401).send("User does not have access to this document.");
|
||||||
.status(401)
|
|
||||||
.send("User does not have access to this document.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import short from "short-uuid";
|
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
import short from "short-uuid";
|
||||||
|
|
||||||
async function deleteHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function deleteHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const user = await getUserFromToken(req, res);
|
const user = await getUserFromToken(req, res);
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import short from "short-uuid";
|
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
import short from "short-uuid";
|
||||||
|
|
||||||
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const user = await getUserFromToken(req, res);
|
const user = await getUserFromToken(req, res);
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { sendSigningRequest } from "@documenso/lib/mail";
|
import { sendSigningRequest } from "@documenso/lib/mail";
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
import { Document as PrismaDocument, SendStatus } from "@prisma/client";
|
import { Document as PrismaDocument, SendStatus } from "@prisma/client";
|
||||||
|
|
||||||
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@ -23,8 +19,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
const document: PrismaDocument = await getDocument(+documentId, req, res);
|
const document: PrismaDocument = await getDocument(+documentId, req, res);
|
||||||
|
|
||||||
if (!document)
|
if (!document) res.status(404).end(`No document with id ${documentId} found.`);
|
||||||
res.status(404).end(`No document with id ${documentId} found.`);
|
|
||||||
|
|
||||||
let recipientCondition: any = {
|
let recipientCondition: any = {
|
||||||
documentId: +documentId,
|
documentId: +documentId,
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { SigningStatus, DocumentStatus } from "@prisma/client";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
|
||||||
import { Document as PrismaDocument, FieldType } from "@prisma/client";
|
|
||||||
import { insertImageInPDF, insertTextInPDF } from "@documenso/pdf";
|
|
||||||
import { sendSigningDoneMail } from "@documenso/lib/mail";
|
import { sendSigningDoneMail } from "@documenso/lib/mail";
|
||||||
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
||||||
|
import { insertImageInPDF, insertTextInPDF } from "@documenso/pdf";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { DocumentStatus, SigningStatus } from "@prisma/client";
|
||||||
|
import { FieldType, Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { token: recipientToken } = req.query;
|
const { token: recipientToken } = req.query;
|
||||||
@ -115,10 +115,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
document: documentWithInserts,
|
document: documentWithInserts,
|
||||||
status:
|
status: unsignedRecipients.length > 0 ? DocumentStatus.PENDING : DocumentStatus.COMPLETED,
|
||||||
unsignedRecipients.length > 0
|
|
||||||
? DocumentStatus.PENDING
|
|
||||||
: DocumentStatus.COMPLETED,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -129,8 +126,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.document = documentWithInserts;
|
document.document = documentWithInserts;
|
||||||
if (documentOwner)
|
if (documentOwner) await sendSigningDoneMail(recipient, document, documentOwner);
|
||||||
await sendSigningDoneMail(recipient, document, documentOwner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).end();
|
return res.status(200).end();
|
||||||
@ -139,9 +135,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
if (signedField?.Signature?.signatureImageAsBase64) {
|
if (signedField?.Signature?.signatureImageAsBase64) {
|
||||||
documentWithInserts = await insertImageInPDF(
|
documentWithInserts = await insertImageInPDF(
|
||||||
documentWithInserts,
|
documentWithInserts,
|
||||||
signedField.Signature
|
signedField.Signature ? signedField.Signature?.signatureImageAsBase64 : "",
|
||||||
? signedField.Signature?.signatureImageAsBase64
|
|
||||||
: "",
|
|
||||||
signedField.positionX,
|
signedField.positionX,
|
||||||
signedField.positionY,
|
signedField.positionY,
|
||||||
signedField.page
|
signedField.page
|
||||||
@ -169,12 +163,8 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
create: {
|
create: {
|
||||||
recipientId: recipient.id,
|
recipientId: recipient.id,
|
||||||
fieldId: signature.fieldId,
|
fieldId: signature.fieldId,
|
||||||
signatureImageAsBase64: signature.signatureImage
|
signatureImageAsBase64: signature.signatureImage ? signature.signatureImage : null,
|
||||||
? signature.signatureImage
|
typedSignature: signature.typedSignature ? signature.typedSignature : null,
|
||||||
: null,
|
|
||||||
typedSignature: signature.typedSignature
|
|
||||||
? signature.typedSignature
|
|
||||||
: null,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getUserFromToken } from "@documenso/lib/server";
|
|
||||||
import formidable from "formidable";
|
|
||||||
import { getDocumentsForUserFromToken } from "@documenso/lib/query";
|
import { getDocumentsForUserFromToken } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
||||||
|
import { getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import formidable from "formidable";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
||||||
import prisma from "@documenso/prisma";
|
import prisma from "@documenso/prisma";
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
import { addDigitalSignature } from "@documenso/signing/addDigitalSignature";
|
import { addDigitalSignature } from "@documenso/signing/addDigitalSignature";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
// todo remove before launch
|
// todo remove before launch
|
||||||
|
|
||||||
@ -17,10 +13,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const signedDocument = await addDigitalSignature(document.document);
|
const signedDocument = await addDigitalSignature(document.document);
|
||||||
res.setHeader("Content-Type", "application/pdf");
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
res.setHeader("Content-Length", signedDocument.length);
|
res.setHeader("Content-Length", signedDocument.length);
|
||||||
res.setHeader(
|
res.setHeader("Content-Disposition", `attachment; filename=${document.title}`);
|
||||||
"Content-Disposition",
|
|
||||||
`attachment; filename=${document.title}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send(signedDocument);
|
return res.status(200).send(signedDocument);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
|
||||||
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { method, body } = req;
|
const { method, body } = req;
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import {
|
|
||||||
defaultHandler,
|
|
||||||
defaultResponder,
|
|
||||||
getUserFromToken,
|
|
||||||
} from "@documenso/lib/server";
|
|
||||||
import prisma from "@documenso/prisma";
|
|
||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
|
||||||
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const user = await getUserFromToken(req, res);
|
const user = await getUserFromToken(req, res);
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import Head from "next/head";
|
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import Layout from "../components/layout";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { uploadDocument } from "@documenso/features";
|
||||||
|
import { getDocumentsForUserFromToken } from "@documenso/lib/query";
|
||||||
|
import { getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import Layout from "../components/layout";
|
||||||
import type { NextPageWithLayout } from "./_app";
|
import type { NextPageWithLayout } from "./_app";
|
||||||
import {
|
import {
|
||||||
CheckBadgeIcon,
|
CheckBadgeIcon,
|
||||||
@ -9,15 +12,7 @@ import {
|
|||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { uploadDocument } from "@documenso/features";
|
import { DocumentStatus, Document as PrismaDocument, SendStatus, SigningStatus } from "@prisma/client";
|
||||||
import {
|
|
||||||
DocumentStatus,
|
|
||||||
SendStatus,
|
|
||||||
SigningStatus,
|
|
||||||
Document as PrismaDocument,
|
|
||||||
} from "@prisma/client";
|
|
||||||
import { getUserFromToken } from "@documenso/lib/server";
|
|
||||||
import { getDocumentsForUserFromToken } from "@documenso/lib/query";
|
|
||||||
import { truncate } from "fs";
|
import { truncate } from "fs";
|
||||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||||
|
|
||||||
@ -55,19 +50,16 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
|
|||||||
|
|
||||||
<div className="py-10 max-sm:px-4">
|
<div className="py-10 max-sm:px-4">
|
||||||
<header>
|
<header>
|
||||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">Dashboard</h1>
|
||||||
Dashboard
|
|
||||||
</h1>
|
|
||||||
</header>
|
</header>
|
||||||
<dl className="grid gap-5 mt-8 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="px-4 py-3 overflow-hidden bg-white rounded-lg shadow md:p-6 sm:py-5">
|
<div className="overflow-hidden rounded-lg bg-white px-4 py-3 shadow sm:py-5 md:p-6">
|
||||||
<dt className="text-sm font-medium text-gray-500 truncate ">
|
<dt className="truncate text-sm font-medium text-gray-500 ">
|
||||||
<item.icon
|
<item.icon
|
||||||
className="flex-shrink-0 inline w-5 h-5 mr-3 text-neon sm:w-6 sm:h-6"
|
className="text-neon mr-3 inline h-5 w-5 flex-shrink-0 sm:h-6 sm:w-6"
|
||||||
aria-hidden="true"
|
aria-hidden="true"></item.icon>
|
||||||
></item.icon>
|
|
||||||
{item.name}
|
{item.name}
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-1 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">
|
||||||
@ -92,25 +84,20 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
document?.getElementById("fileUploadHelper")?.click();
|
document?.getElementById("fileUploadHelper")?.click();
|
||||||
}}
|
}}
|
||||||
className="relative block w-full p-12 text-center border-2 border-gray-300 border-dashed rounded-lg cursor-pointer hover:border-neon 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="w-12 h-12 mx-auto text-gray-400"
|
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"
|
||||||
aria-hidden="true"
|
aria-hidden="true">
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
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
|
<span id="add_document" className="text-neon mt-2 block text-sm font-medium">
|
||||||
id="add_document"
|
|
||||||
className="mt-2 block text-sm font-medium text-neon"
|
|
||||||
>
|
|
||||||
Add a new PDF document.
|
Add a new PDF document.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -147,9 +134,7 @@ export async function getServerSideProps(context: any) {
|
|||||||
|
|
||||||
const documents: any[] = await getDocumentsForUserFromToken(context);
|
const documents: any[] = await getDocumentsForUserFromToken(context);
|
||||||
|
|
||||||
const drafts: PrismaDocument[] = documents.filter(
|
const drafts: PrismaDocument[] = documents.filter((d) => d.status === DocumentStatus.DRAFT);
|
||||||
(d) => d.status === DocumentStatus.DRAFT
|
|
||||||
);
|
|
||||||
|
|
||||||
const waiting: any[] = documents.filter(
|
const waiting: any[] = documents.filter(
|
||||||
(e) =>
|
(e) =>
|
||||||
@ -158,9 +143,7 @@ export async function getServerSideProps(context: any) {
|
|||||||
e.Recipient.some((r: any) => r.signingStatus === SigningStatus.NOT_SIGNED)
|
e.Recipient.some((r: any) => r.signingStatus === SigningStatus.NOT_SIGNED)
|
||||||
);
|
);
|
||||||
|
|
||||||
const completed: PrismaDocument[] = documents.filter(
|
const completed: PrismaDocument[] = documents.filter((d) => d.status === DocumentStatus.COMPLETED);
|
||||||
(d) => d.status === DocumentStatus.COMPLETED
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { ReactElement, useEffect, useState } from "react";
|
import { ReactElement, useEffect, useState } from "react";
|
||||||
|
import { NextPageContext } from "next";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { uploadDocument } from "@documenso/features";
|
||||||
|
import { deleteDocument, getDocuments } from "@documenso/lib/api";
|
||||||
|
import { Button, IconButton, SelectBox } from "@documenso/ui";
|
||||||
import Layout from "../components/layout";
|
import Layout from "../components/layout";
|
||||||
import type { NextPageWithLayout } from "./_app";
|
import type { NextPageWithLayout } from "./_app";
|
||||||
import Head from "next/head";
|
|
||||||
import {
|
import {
|
||||||
ArrowDownTrayIcon,
|
ArrowDownTrayIcon,
|
||||||
CheckBadgeIcon,
|
CheckBadgeIcon,
|
||||||
@ -13,12 +18,7 @@ import {
|
|||||||
PlusIcon,
|
PlusIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { uploadDocument } from "@documenso/features";
|
|
||||||
import { DocumentStatus } from "@prisma/client";
|
import { DocumentStatus } from "@prisma/client";
|
||||||
import { Button, IconButton, SelectBox } from "@documenso/ui";
|
|
||||||
import { NextPageContext } from "next";
|
|
||||||
import { deleteDocument, getDocuments } from "@documenso/lib/api";
|
|
||||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||||
|
|
||||||
const DocumentsPage: NextPageWithLayout = (props: any) => {
|
const DocumentsPage: NextPageWithLayout = (props: any) => {
|
||||||
@ -42,12 +42,8 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
{ label: "Last 12 months", value: 366 },
|
{ label: "Last 12 months", value: 366 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const [selectedStatusFilter, setSelectedStatusFilter] = useState(
|
const [selectedStatusFilter, setSelectedStatusFilter] = useState(statusFilters[0]);
|
||||||
statusFilters[0]
|
const [selectedCreatedFilter, setSelectedCreatedFilter] = useState(createdFilter[0]);
|
||||||
);
|
|
||||||
const [selectedCreatedFilter, setSelectedCreatedFilter] = useState(
|
|
||||||
createdFilter[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadDocuments = async () => {
|
const loadDocuments = async () => {
|
||||||
if (!documents.length) setLoading(true);
|
if (!documents.length) setLoading(true);
|
||||||
@ -62,9 +58,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDocuments().finally(() => {
|
loadDocuments().finally(() => {
|
||||||
setSelectedStatusFilter(
|
setSelectedStatusFilter(
|
||||||
statusFilters.filter(
|
statusFilters.filter((status) => status.value === props.filter.toUpperCase())[0]
|
||||||
(status) => status.value === props.filter.toUpperCase()
|
|
||||||
)[0]
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@ -79,9 +73,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
|
|
||||||
function filterDocumentes(documents: []): any {
|
function filterDocumentes(documents: []): any {
|
||||||
let filteredDocuments = documents.filter(
|
let filteredDocuments = documents.filter(
|
||||||
(d: any) =>
|
(d: any) => d.status === selectedStatusFilter.value || selectedStatusFilter.value === "ALL"
|
||||||
d.status === selectedStatusFilter.value ||
|
|
||||||
selectedStatusFilter.value === "ALL"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
filteredDocuments = filteredDocuments.filter((document: any) =>
|
filteredDocuments = filteredDocuments.filter((document: any) =>
|
||||||
@ -98,9 +90,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
const today: Date = new Date(); // Today's date
|
const today: Date = new Date(); // Today's date
|
||||||
|
|
||||||
// Calculate the difference between the two dates in days
|
// Calculate the difference between the two dates in days
|
||||||
const diffInDays = Math.floor(
|
const diffInDays = Math.floor((today.getTime() - documentDate.getTime()) / millisecondsInDay);
|
||||||
(today.getTime() - documentDate.getTime()) / millisecondsInDay
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(diffInDays);
|
console.log(diffInDays);
|
||||||
|
|
||||||
@ -114,12 +104,10 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<title>Documents | Documenso</title>
|
<title>Documents | Documenso</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="px-4 sm:px-6 lg:px-8">
|
<div className="px-4 sm:px-6 lg:px-8">
|
||||||
<div className="sm:flex sm:items-center mt-10">
|
<div className="mt-10 sm:flex sm:items-center">
|
||||||
<div className="sm:flex-auto">
|
<div className="sm:flex-auto">
|
||||||
<header>
|
<header>
|
||||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">Documents</h1>
|
||||||
Documents
|
|
||||||
</h1>
|
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
<div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||||
@ -127,27 +115,24 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
icon={DocumentPlusIcon}
|
icon={DocumentPlusIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
document?.getElementById("fileUploadHelper")?.click();
|
document?.getElementById("fileUploadHelper")?.click();
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Add Document
|
Add Document
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 mb-12">
|
<div className="mt-3 mb-12">
|
||||||
<div className="w-fit block float-right ml-3 mt-7">
|
<div className="float-right ml-3 mt-7 block w-fit">
|
||||||
{filteredDocuments.length != 1
|
{filteredDocuments.length != 1 ? filteredDocuments.length + " Documents" : "1 Document"}
|
||||||
? filteredDocuments.length + " Documents"
|
|
||||||
: "1 Document"}
|
|
||||||
</div>
|
</div>
|
||||||
<SelectBox
|
<SelectBox
|
||||||
className="w-1/4 block float-right"
|
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="w-1/4 block float-right ml-3"
|
className="float-right ml-3 block w-1/4"
|
||||||
label="Status"
|
label="Status"
|
||||||
options={statusFilters}
|
options={statusFilters}
|
||||||
value={selectedStatusFilter}
|
value={selectedStatusFilter}
|
||||||
@ -171,47 +156,28 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="mt-28 flex flex-col" hidden={!documents.length || loading}>
|
||||||
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}>
|
||||||
>
|
|
||||||
<div className="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
|
<div className="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
|
||||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
|
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
|
||||||
<table className="min-w-full divide-y divide-gray-300">
|
<table className="min-w-full divide-y divide-gray-300">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||||
scope="col"
|
|
||||||
className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
Title
|
Title
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||||
scope="col"
|
|
||||||
className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
Recipients
|
Recipients
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||||
scope="col"
|
|
||||||
className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||||
scope="col"
|
|
||||||
className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
Created
|
Created
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||||
scope="col"
|
|
||||||
className="relative py-3.5 pl-3 pr-4 sm:pr-6"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Delete</span>
|
<span className="sr-only">Delete</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -220,9 +186,8 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
{filteredDocuments.map((document: any, index: number) => (
|
{filteredDocuments.map((document: any, index: number) => (
|
||||||
<tr
|
<tr
|
||||||
key={document.id}
|
key={document.id}
|
||||||
className="hover:bg-gray-100 cursor-pointer"
|
className="cursor-pointer hover:bg-gray-100"
|
||||||
onClick={(event) => showDocument(document.id)}
|
onClick={(event) => showDocument(document.id)}>
|
||||||
>
|
|
||||||
<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>
|
||||||
@ -232,43 +197,32 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
{item.sendStatus === "NOT_SENT" ? (
|
{item.sendStatus === "NOT_SENT" ? (
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="inline-block flex-shrink-0 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>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{item.sendStatus === "SENT" &&
|
{item.sendStatus === "SENT" && item.readStatus !== "OPENED" ? (
|
||||||
item.readStatus !== "OPENED" ? (
|
|
||||||
<span id="sent_icon">
|
<span id="sent_icon">
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-green-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-5"></EnvelopeIcon>
|
||||||
<EnvelopeIcon className="inline h-5 mr-1"></EnvelopeIcon>
|
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
||||||
{item.name
|
|
||||||
? item.name + " <" + item.email + ">"
|
|
||||||
: item.email}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{item.readStatus === "OPENED" &&
|
{item.readStatus === "OPENED" && item.signingStatus === "NOT_SIGNED" ? (
|
||||||
item.signingStatus === "NOT_SIGNED" ? (
|
|
||||||
<span id="read_icon">
|
<span id="read_icon">
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-green-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-5"></CheckIcon>
|
||||||
<CheckIcon className="inline h-5 -mr-2"></CheckIcon>
|
<CheckIcon className="mr-1 inline h-5"></CheckIcon>
|
||||||
<CheckIcon className="inline h-5 mr-1"></CheckIcon>
|
{item.name ? item.name + " <" + item.email + ">" : item.email}
|
||||||
{item.name
|
|
||||||
? item.name + " <" + item.email + ">"
|
|
||||||
: item.email}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@ -277,8 +231,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
{item.signingStatus === "SIGNED" ? (
|
{item.signingStatus === "SIGNED" ? (
|
||||||
<span id="signed_icon">
|
<span id="signed_icon">
|
||||||
<span className="inline-block flex-shrink-0 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="inline h-5 mr-1"></CheckBadgeIcon>{" "}
|
<CheckBadgeIcon className="mr-1 inline h-5"></CheckBadgeIcon> {item.email}
|
||||||
{item.email}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@ -307,9 +260,8 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
{formatDocumentStatus(document.status)}
|
{formatDocumentStatus(document.status)}
|
||||||
<p>
|
<p>
|
||||||
<small hidden={document.Recipient.length === 0}>
|
<small hidden={document.Recipient.length === 0}>
|
||||||
{document.Recipient.filter(
|
{document.Recipient.filter((r: any) => r.signingStatus === "SIGNED").length ||
|
||||||
(r: any) => r.signingStatus === "SIGNED"
|
0}
|
||||||
).length || 0}
|
|
||||||
/{document.Recipient.length || 0}
|
/{document.Recipient.length || 0}
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
@ -342,30 +294,20 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
onClick={(event: any) => {
|
onClick={(event: any) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (
|
if (confirm("Are you sure you want to delete this document")) {
|
||||||
confirm(
|
|
||||||
"Are you sure you want to delete this document"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const documentsWithoutIndex = [...documents];
|
const documentsWithoutIndex = [...documents];
|
||||||
const removedItem: any =
|
const removedItem: any = documentsWithoutIndex.splice(index, 1);
|
||||||
documentsWithoutIndex.splice(index, 1);
|
|
||||||
setDocuments(documentsWithoutIndex);
|
setDocuments(documentsWithoutIndex);
|
||||||
deleteDocument(document.id)
|
deleteDocument(document.id)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
documentsWithoutIndex.splice(
|
documentsWithoutIndex.splice(index, 0, removedItem);
|
||||||
index,
|
|
||||||
0,
|
|
||||||
removedItem
|
|
||||||
);
|
|
||||||
setDocuments(documentsWithoutIndex);
|
setDocuments(documentsWithoutIndex);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loadDocuments();
|
loadDocuments();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}></IconButton>
|
||||||
></IconButton>
|
|
||||||
<span className="sr-only">, {document.name}</span>
|
<span className="sr-only">, {document.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -374,29 +316,21 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div hidden={filteredDocuments.length > 0} className="mx-auto mt-12 w-fit p-3">
|
||||||
hidden={filteredDocuments.length > 0}
|
<FunnelIcon className="mr-1 inline w-5 align-middle" /> Nothing here. Maybe try a different
|
||||||
className="mx-auto w-fit mt-12 p-3"
|
filter.
|
||||||
>
|
|
||||||
<FunnelIcon className="w-5 inline mr-1 align-middle" /> Nothing
|
|
||||||
here. Maybe try a different filter.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="mt-24 text-center" id="empty" hidden={documents.length > 0 || loading}>
|
||||||
className="text-center mt-24"
|
|
||||||
id="empty"
|
|
||||||
hidden={documents.length > 0 || loading}
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
className="mx-auto h-12 w-12 text-gray-400"
|
className="mx-auto h-12 w-12 text-gray-400"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
aria-hidden="true"
|
aria-hidden="true">
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@ -405,16 +339,13 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">No documents</h3>
|
<h3 className="mt-2 text-sm font-medium text-gray-900">No documents</h3>
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">Get started by adding a document. Any PDF will do.</p>
|
||||||
Get started by adding a document. Any PDF will do.
|
|
||||||
</p>
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Button
|
<Button
|
||||||
icon={PlusIcon}
|
icon={PlusIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
document?.getElementById("fileUploadHelper")?.click();
|
document?.getElementById("fileUploadHelper")?.click();
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Add Document
|
Add Document
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
@ -428,11 +359,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ReactTooltip
|
<ReactTooltip anchorId="empty" place="bottom" content="No preparation needed. Any PDF will do." />
|
||||||
anchorId="empty"
|
|
||||||
place="bottom"
|
|
||||||
content="No preparation needed. Any PDF will do."
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,20 +1,16 @@
|
|||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import Layout from "../../../components/layout";
|
import Link from "next/link";
|
||||||
import { NextPageWithLayout } from "../../_app";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib";
|
||||||
import { getUserFromToken } from "@documenso/lib/server";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { DocumentStatus } from "@prisma/client";
|
|
||||||
import {
|
|
||||||
InformationCircleIcon,
|
|
||||||
PaperAirplaneIcon,
|
|
||||||
UsersIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
import { getUserFromToken } from "@documenso/lib/server";
|
||||||
import { Button, Breadcrumb } from "@documenso/ui";
|
import { Breadcrumb, Button } from "@documenso/ui";
|
||||||
import PDFEditor from "../../../components/editor/pdf-editor";
|
import PDFEditor from "../../../components/editor/pdf-editor";
|
||||||
|
import Layout from "../../../components/layout";
|
||||||
|
import { NextPageWithLayout } from "../../_app";
|
||||||
|
import { InformationCircleIcon, PaperAirplaneIcon, UsersIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { DocumentStatus } from "@prisma/client";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
const DocumentsDetailPage: NextPageWithLayout = (props: any) => {
|
const DocumentsDetailPage: NextPageWithLayout = (props: any) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -32,8 +28,7 @@ const DocumentsDetailPage: NextPageWithLayout = (props: any) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: props.document.title,
|
title: props.document.title,
|
||||||
href:
|
href: NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id,
|
||||||
NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id,
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -45,10 +40,7 @@ const DocumentsDetailPage: NextPageWithLayout = (props: any) => {
|
|||||||
</h2>
|
</h2>
|
||||||
<div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
|
<div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
|
||||||
<div className="mt-2 flex items-center text-sm text-gray-500">
|
<div className="mt-2 flex items-center text-sm text-gray-500">
|
||||||
<UsersIcon
|
<UsersIcon className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />
|
||||||
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Link href={`/documents/${props.document.id}/recipients`}>
|
<Link href={`/documents/${props.document.id}/recipients`}>
|
||||||
{props?.document?.Recipient?.length} Recipients
|
{props?.document?.Recipient?.length} Recipients
|
||||||
@ -67,21 +59,11 @@ const DocumentsDetailPage: NextPageWithLayout = (props: any) => {
|
|||||||
<Button
|
<Button
|
||||||
icon={PaperAirplaneIcon}
|
icon={PaperAirplaneIcon}
|
||||||
className="ml-3"
|
className="ml-3"
|
||||||
href={
|
href={NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id + "/recipients"}
|
||||||
NEXT_PUBLIC_WEBAPP_URL +
|
|
||||||
"/documents/" +
|
|
||||||
props.document.id +
|
|
||||||
"/recipients"
|
|
||||||
}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (confirm(`Send document out to ${props?.document?.Recipient?.length} recipients?`)) {
|
||||||
confirm(
|
|
||||||
`Send document out to ${props?.document?.Recipient?.length} recipients?`
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
}
|
}
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Prepare to Send
|
Prepare to Send
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -120,11 +102,7 @@ export async function getServerSideProps(context: any) {
|
|||||||
const { id: documentId } = context.query;
|
const { id: documentId } = context.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const document: PrismaDocument = await getDocument(
|
const document: PrismaDocument = await getDocument(+documentId, context.req, context.res);
|
||||||
+documentId,
|
|
||||||
context.req,
|
|
||||||
context.res
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@ -1,25 +1,24 @@
|
|||||||
import Head from "next/head";
|
|
||||||
import { ReactElement, useRef, useState } from "react";
|
import { ReactElement, useRef, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL, classNames } from "@documenso/lib";
|
||||||
|
import { createOrUpdateRecipient, deleteRecipient, sendSigningRequests } from "@documenso/lib/api";
|
||||||
|
import { getDocument } from "@documenso/lib/query";
|
||||||
|
import { getUserFromToken } from "@documenso/lib/server";
|
||||||
|
import { Breadcrumb, Button, Dialog, IconButton } from "@documenso/ui";
|
||||||
import Layout from "../../../components/layout";
|
import Layout from "../../../components/layout";
|
||||||
import { NextPageWithLayout } from "../../_app";
|
import { NextPageWithLayout } from "../../_app";
|
||||||
import { classNames, NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib";
|
|
||||||
import {
|
import {
|
||||||
ArrowDownTrayIcon,
|
ArrowDownTrayIcon,
|
||||||
CheckBadgeIcon,
|
CheckBadgeIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
EnvelopeIcon,
|
||||||
PaperAirplaneIcon,
|
PaperAirplaneIcon,
|
||||||
PencilSquareIcon,
|
PencilSquareIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
UserPlusIcon,
|
UserPlusIcon,
|
||||||
EnvelopeIcon,
|
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { getUserFromToken } from "@documenso/lib/server";
|
import { DocumentStatus, Document as PrismaDocument } from "@prisma/client";
|
||||||
import { getDocument } from "@documenso/lib/query";
|
|
||||||
import { Document as PrismaDocument, DocumentStatus } from "@prisma/client";
|
|
||||||
import { Breadcrumb, Button, Dialog, IconButton } from "@documenso/ui";
|
|
||||||
import { createOrUpdateRecipient, deleteRecipient, sendSigningRequests } from "@documenso/lib/api";
|
|
||||||
|
|
||||||
import { FormProvider, useFieldArray, useForm, useWatch } from "react-hook-form";
|
import { FormProvider, useFieldArray, useForm, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
export type FormValues = {
|
export type FormValues = {
|
||||||
@ -71,23 +70,22 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<Head>
|
<Head>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="px-6 mt-10 sm:px-0">
|
<div className="mt-10 px-6 sm:px-0">
|
||||||
<div>
|
<div>
|
||||||
<Breadcrumb document={props.document} items={breadcrumbItems} />
|
<Breadcrumb document={props.document} items={breadcrumbItems} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 md:flex md:items-center md:justify-between">
|
<div className="mt-2 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="min-w-0 flex-1">
|
||||||
<h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
|
<h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
|
||||||
{props.document.title}
|
{props.document.title}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-shrink-0 mt-4 md:mt-0 md:ml-4">
|
<div className="mt-4 flex flex-shrink-0 md:mt-0 md:ml-4">
|
||||||
<Button
|
<Button
|
||||||
icon={ArrowDownTrayIcon}
|
icon={ArrowDownTrayIcon}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
href={"/api/documents/" + props.document.id}
|
href={"/api/documents/" + props.document.id}>
|
||||||
>
|
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -95,8 +93,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
disabled={props.document.status === DocumentStatus.COMPLETED}
|
disabled={props.document.status === DocumentStatus.COMPLETED}
|
||||||
color={props.document.status === DocumentStatus.COMPLETED ? "primary" : "secondary"}
|
color={props.document.status === DocumentStatus.COMPLETED ? "primary" : "secondary"}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
href={breadcrumbItems[1].href}
|
href={breadcrumbItems[1].href}>
|
||||||
>
|
|
||||||
Edit Document
|
Edit Document
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -108,43 +105,33 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
}}
|
}}
|
||||||
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: any) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
|
||||||
) ||
|
|
||||||
loading
|
loading
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
Send
|
Send
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 mt-10 overflow-hidden bg-white rounded-md shadow sm:p-6">
|
<div className="mt-10 overflow-hidden rounded-md bg-white p-4 shadow sm:p-6">
|
||||||
<div className="pb-3 border-b border-gray-200 sm:pb-5">
|
<div className="border-b border-gray-200 pb-3 sm:pb-5">
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900 ">Signers</h3>
|
<h3 className="text-lg font-medium leading-6 text-gray-900 ">Signers</h3>
|
||||||
<p className="max-w-4xl mt-2 text-sm text-gray-500">
|
<p className="mt-2 max-w-4xl text-sm text-gray-500">The people who will sign the document.</p>
|
||||||
The people who will sign the document.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<form
|
<form
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
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: any, index: number) => (
|
||||||
<li
|
<li key={index} className="group w-full border-0 px-2 py-3 hover:bg-green-50 sm:py-4">
|
||||||
key={index}
|
|
||||||
className="w-full px-2 py-3 border-0 hover:bg-green-50 group sm:py-4"
|
|
||||||
>
|
|
||||||
<div id="container" className="block w-full lg:flex lg:justify-between">
|
<div id="container" className="block w-full lg:flex lg:justify-between">
|
||||||
<div className="block space-y-2 md:space-x-2 md:space-y-0 md:flex">
|
<div className="block space-y-2 md:flex md:space-x-2 md:space-y-0">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"md:w-[250px] rounded-md border border-gray-300 px-3 py-2 shadow-sm focus-within:border-neon focus-within:ring-1 focus-within:ring-neon",
|
"focus-within:border-neon focus-within:ring-neon rounded-md border border-gray-300 px-3 py-2 shadow-sm focus-within:ring-1 md:w-[250px]",
|
||||||
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
@ -170,7 +157,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
documentId: props.document.id,
|
documentId: props.document.id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="block w-full p-0 text-gray-900 placeholder-gray-500 disabled:bg-neutral-100 border-0 outline-none sm:text-sm bg-inherit"
|
className="block w-full border-0 bg-inherit p-0 text-gray-900 placeholder-gray-500 outline-none disabled:bg-neutral-100 sm:text-sm"
|
||||||
placeholder="john.dorian@loremipsum.com"
|
placeholder="john.dorian@loremipsum.com"
|
||||||
/>
|
/>
|
||||||
{errors?.signers?.[index] ? (
|
{errors?.signers?.[index] ? (
|
||||||
@ -183,10 +170,9 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"md:w-[250px] rounded-md border border-gray-300 px-3 py-2 shadow-sm focus-within:border-neon focus-within:ring-1 focus-within:ring-neon",
|
"focus-within:border-neon focus-within:ring-neon rounded-md border border-gray-300 px-3 py-2 shadow-sm focus-within:ring-1 md:w-[250px]",
|
||||||
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||||
Name (optional)
|
Name (optional)
|
||||||
</label>
|
</label>
|
||||||
@ -209,19 +195,18 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
documentId: props.document.id,
|
documentId: props.document.id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="block w-full p-0 text-gray-900 placeholder-gray-500 disabled:bg-neutral-100 border-0 outline-none sm:text-sm bg-inherit"
|
className="block w-full border-0 bg-inherit p-0 text-gray-900 placeholder-gray-500 outline-none disabled:bg-neutral-100 sm:text-sm"
|
||||||
placeholder="John Dorian"
|
placeholder="John Dorian"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2 lg:ml-2">
|
<div className="flex items-center space-x-2 lg:ml-2">
|
||||||
<div className="flex mb-2 mr-2 lg:mr-0">
|
<div className="mb-2 mr-2 flex lg:mr-0">
|
||||||
<div key={item.id} className="space-x-2">
|
<div key={item.id} className="space-x-2">
|
||||||
{item.sendStatus === "NOT_SENT" ? (
|
{item.sendStatus === "NOT_SENT" ? (
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="inline-block mt-3 flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-gray-800"
|
className="mt-3 inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-gray-800">
|
||||||
>
|
|
||||||
Not Sent
|
Not Sent
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@ -231,9 +216,8 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<span id="sent_icon">
|
<span id="sent_icon">
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="inline-block mt-3 flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-gray-800 "
|
className="mt-3 inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-gray-800 ">
|
||||||
>
|
<CheckIcon className="mr-1 inline h-5" /> Sent
|
||||||
<CheckIcon className="inline h-5 mr-1" /> Sent
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@ -243,10 +227,9 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<span id="read_icon">
|
<span id="read_icon">
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="inline-block mt-3 flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-gray-800"
|
className="mt-3 inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-gray-800">
|
||||||
>
|
<CheckIcon className="-mr-2 inline h-5"></CheckIcon>
|
||||||
<CheckIcon className="inline h-5 -mr-2"></CheckIcon>
|
<CheckIcon className="mr-1 inline h-5"></CheckIcon>
|
||||||
<CheckIcon className="inline h-5 mr-1"></CheckIcon>
|
|
||||||
Seen
|
Seen
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -257,9 +240,8 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<span id="signed_icon">
|
<span id="signed_icon">
|
||||||
<span
|
<span
|
||||||
id="sent_icon"
|
id="sent_icon"
|
||||||
className="inline-block mt-3 flex-shrink-0 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800"
|
className="mt-3 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="inline h-5 mr-1"></CheckBadgeIcon>
|
|
||||||
Signed
|
Signed
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -268,7 +250,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mr-1">
|
<div className="mr-1 flex">
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={PaperAirplaneIcon}
|
icon={PaperAirplaneIcon}
|
||||||
disabled={
|
disabled={
|
||||||
@ -286,8 +268,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Resend
|
Resend
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -320,8 +301,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
append(res);
|
append(res);
|
||||||
});
|
});
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Add Signer
|
Add Signer
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
@ -336,7 +316,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
open={open}
|
open={open}
|
||||||
setLoading={setLoading}
|
setLoading={setLoading}
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
icon={<EnvelopeIcon className="w-6 h-6 text-green-600" aria-hidden="true" />}
|
icon={<EnvelopeIcon className="h-6 w-6 text-green-600" aria-hidden="true" />}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import prisma from "@documenso/prisma";
|
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { NextPageWithLayout } from "../../_app";
|
|
||||||
import { ReadStatus } from "@prisma/client";
|
|
||||||
import PDFSigner from "../../../components/editor/pdf-signer";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import PDFSigner from "../../../components/editor/pdf-signer";
|
||||||
|
import { NextPageWithLayout } from "../../_app";
|
||||||
import { ClockIcon } from "@heroicons/react/24/outline";
|
import { ClockIcon } from "@heroicons/react/24/outline";
|
||||||
import { FieldType, DocumentStatus } from "@prisma/client";
|
import { ReadStatus } from "@prisma/client";
|
||||||
|
import { DocumentStatus, FieldType } from "@prisma/client";
|
||||||
|
|
||||||
const SignPage: NextPageWithLayout = (props: any) => {
|
const SignPage: NextPageWithLayout = (props: any) => {
|
||||||
return (
|
return (
|
||||||
@ -14,36 +14,22 @@ const SignPage: NextPageWithLayout = (props: any) => {
|
|||||||
<title>Sign | Documenso</title>
|
<title>Sign | Documenso</title>
|
||||||
</Head>
|
</Head>
|
||||||
{!props.expired ? (
|
{!props.expired ? (
|
||||||
<PDFSigner
|
<PDFSigner document={props.document} recipient={props.recipient} fields={props.fields} />
|
||||||
document={props.document}
|
|
||||||
recipient={props.recipient}
|
|
||||||
fields={props.fields}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="mx-auto w-fit px-4 py-16 sm:px-6 sm:py-24 lg:px-8">
|
<div className="mx-auto w-fit px-4 py-16 sm:px-6 sm:py-24 lg:px-8">
|
||||||
<ClockIcon className="text-neon w-10 inline mr-1"></ClockIcon>
|
<ClockIcon className="text-neon mr-1 inline w-10"></ClockIcon>
|
||||||
<h1 className="text-base font-medium text-neon inline align-middle">
|
<h1 className="text-neon inline align-middle text-base font-medium">Time flies.</h1>
|
||||||
Time flies.
|
<p className="mt-2 text-4xl font-bold tracking-tight">This signing link is expired.</p>
|
||||||
</h1>
|
|
||||||
<p className="mt-2 text-4xl font-bold tracking-tight">
|
|
||||||
This signing link is expired.
|
|
||||||
</p>
|
|
||||||
<p className="mt-2 text-base text-gray-500">
|
<p className="mt-2 text-base text-gray-500">
|
||||||
Please ask{" "}
|
Please ask {props.document.User.name ? `${props.document.User.name}` : `the sender`} to resend
|
||||||
{props.document.User.name
|
it.
|
||||||
? `${props.document.User.name}`
|
|
||||||
: `the sender`}{" "}
|
|
||||||
to resend it.
|
|
||||||
</p>
|
</p>
|
||||||
<div className="mx-auto w-fit text-xl pt-20"></div>
|
<div className="mx-auto w-fit pt-20 text-xl"></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="relative mx-96">
|
<div className="relative mx-96">
|
||||||
<div
|
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||||
className="absolute inset-0 flex items-center"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<div className="w-full border-t border-gray-300" />
|
<div className="w-full border-t border-gray-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex justify-center"></div>
|
<div className="relative flex justify-center"></div>
|
||||||
@ -51,10 +37,7 @@ const SignPage: NextPageWithLayout = (props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
<p className="mt-4 text-center text-sm text-gray-600">
|
<p className="mt-4 text-center text-sm text-gray-600">
|
||||||
Want to send of your own?{" "}
|
Want to send of your own?{" "}
|
||||||
<Link
|
<Link href="/signup?source=expired" className="text-neon hover:text-neon font-medium">
|
||||||
href="/signup?source=expired"
|
|
||||||
className="font-medium text-neon hover:text-neon"
|
|
||||||
>
|
|
||||||
Create your own Account
|
Create your own Account
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
@ -118,13 +101,9 @@ export async function getServerSideProps(context: any) {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
recipient: JSON.parse(JSON.stringify(recipient)),
|
recipient: JSON.parse(JSON.stringify(recipient)),
|
||||||
document: JSON.parse(
|
document: JSON.parse(JSON.stringify({ ...recipient.Document, document: "" })),
|
||||||
JSON.stringify({ ...recipient.Document, document: "" })
|
|
||||||
),
|
|
||||||
fields: JSON.parse(JSON.stringify(unsignedFields)),
|
fields: JSON.parse(JSON.stringify(unsignedFields)),
|
||||||
expired: recipient.expired
|
expired: recipient.expired ? new Date(recipient.expired) < new Date() : false,
|
||||||
? new Date(recipient.expired) < new Date()
|
|
||||||
: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import prisma from "@documenso/prisma";
|
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { NextPageWithLayout } from "../../_app";
|
|
||||||
import { ArrowDownTrayIcon, CheckBadgeIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { Button, IconButton } from "@documenso/ui";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { Button, IconButton } from "@documenso/ui";
|
||||||
|
import { NextPageWithLayout } from "../../_app";
|
||||||
|
import { ArrowDownTrayIcon, CheckBadgeIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
const Signed: NextPageWithLayout = (props: any) => {
|
const Signed: NextPageWithLayout = (props: any) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const allRecipientsSigned = props.document.Recipient?.every(
|
const allRecipientsSigned = props.document.Recipient?.every((r: any) => r.signingStatus === "SIGNED");
|
||||||
(r: any) => r.signingStatus === "SIGNED"
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -18,53 +16,31 @@ const Signed: NextPageWithLayout = (props: any) => {
|
|||||||
<title>Sign | Documenso</title>
|
<title>Sign | Documenso</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="mx-auto w-fit px-4 py-16 sm:px-6 sm:py-24 lg:px-8">
|
<div className="mx-auto w-fit px-4 py-16 sm:px-6 sm:py-24 lg:px-8">
|
||||||
<CheckBadgeIcon className="text-neon w-10 inline mr-1"></CheckBadgeIcon>
|
<CheckBadgeIcon className="text-neon mr-1 inline w-10"></CheckBadgeIcon>
|
||||||
<h1 className="text-base font-medium text-neon inline align-middle">
|
<h1 className="text-neon inline align-middle text-base font-medium">It's done!</h1>
|
||||||
It's done!
|
<p className="mt-2 text-4xl font-bold tracking-tight">You signed "{props.document.title}"</p>
|
||||||
</h1>
|
<p className="mt-2 max-w-sm text-base text-gray-500" hidden={allRecipientsSigned}>
|
||||||
<p className="mt-2 text-4xl font-bold tracking-tight">
|
|
||||||
You signed "{props.document.title}"
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="mt-2 text-base text-gray-500 max-w-sm"
|
|
||||||
hidden={allRecipientsSigned}
|
|
||||||
>
|
|
||||||
You will be notfied when all recipients have signed.
|
You will be notfied when all recipients have signed.
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p className="mt-2 max-w-sm text-base text-gray-500" hidden={!allRecipientsSigned}>
|
||||||
className="mt-2 text-base text-gray-500 max-w-sm"
|
|
||||||
hidden={!allRecipientsSigned}
|
|
||||||
>
|
|
||||||
All recipients signed.
|
All recipients signed.
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div className="mx-auto w-fit pt-20 text-xl" hidden={!allRecipientsSigned}>
|
||||||
className="mx-auto w-fit text-xl pt-20"
|
|
||||||
hidden={!allRecipientsSigned}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
icon={ArrowDownTrayIcon}
|
icon={ArrowDownTrayIcon}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={(event: any) => {
|
onClick={(event: any) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
router.push(
|
router.push("/api/documents/" + props.document.id + "?token=" + props.recipient.token);
|
||||||
"/api/documents/" +
|
}}>
|
||||||
props.document.id +
|
|
||||||
"?token=" +
|
|
||||||
props.recipient.token
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Download "{props.document.title}"
|
Download "{props.document.title}"
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="relative mx-96">
|
<div className="relative mx-96">
|
||||||
<div
|
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||||
className="absolute inset-0 flex items-center"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<div className="w-full border-t border-gray-300" />
|
<div className="w-full border-t border-gray-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex justify-center"></div>
|
<div className="relative flex justify-center"></div>
|
||||||
@ -72,10 +48,7 @@ const Signed: NextPageWithLayout = (props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
<p className="mt-4 text-center text-sm text-gray-600">
|
<p className="mt-4 text-center text-sm text-gray-600">
|
||||||
Want to send slick signing links like this one?{" "}
|
Want to send slick signing links like this one?{" "}
|
||||||
<Link
|
<Link href="https://documenso.com" className="text-neon hover:text-neon font-medium">
|
||||||
href="https://documenso.com"
|
|
||||||
className="font-medium text-neon hover:text-neon"
|
|
||||||
>
|
|
||||||
Hosted Documenso is coming soon™
|
Hosted Documenso is coming soon™
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
import SettingsPage from ".";
|
import SettingsPage from ".";
|
||||||
|
|
||||||
export default SettingsPage;
|
export default SettingsPage;
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
import SettingsPage from ".";
|
import SettingsPage from ".";
|
||||||
|
|
||||||
export default SettingsPage;
|
export default SettingsPage;
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
import SettingsPage from ".";
|
import SettingsPage from ".";
|
||||||
|
|
||||||
export default SettingsPage;
|
export default SettingsPage;
|
||||||
|
|||||||
@ -3,4 +3,4 @@ module.exports = {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@ -24,9 +24,8 @@ body,
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("/fonts/montserrat.woff2") format("woff2");
|
src: url("/fonts/montserrat.woff2") format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074,
|
||||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
U+FEFF, U+FFFD;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* latin */
|
/* latin */
|
||||||
@ -36,7 +35,6 @@ body,
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("/fonts/montserrat.woff2") format("woff2");
|
src: url("/fonts/montserrat.woff2") format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074,
|
||||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
U+FEFF, U+FFFD;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,8 @@ export const createField = (
|
|||||||
if (newFieldX < 0) newFieldX = 0;
|
if (newFieldX < 0) newFieldX = 0;
|
||||||
if (newFieldY < 0) newFieldY = 0;
|
if (newFieldY < 0) newFieldY = 0;
|
||||||
|
|
||||||
if (newFieldX + fieldSize.width > rect.width)
|
if (newFieldX + fieldSize.width > rect.width) newFieldX = rect.width - fieldSize.width;
|
||||||
newFieldX = rect.width - fieldSize.width;
|
if (newFieldY + fieldSize.height > rect.height) newFieldY = rect.height - fieldSize.height;
|
||||||
if (newFieldY + fieldSize.height > rect.height)
|
|
||||||
newFieldY = rect.height - fieldSize.height;
|
|
||||||
|
|
||||||
const signatureField = {
|
const signatureField = {
|
||||||
id: -1,
|
id: -1,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import router from "next/router";
|
import router from "next/router";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "../lib/constants";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "../lib/constants";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export const uploadDocument = async (event: any) => {
|
export const uploadDocument = async (event: any) => {
|
||||||
if (event.target.files && event.target.files[0]) {
|
if (event.target.files && event.target.files[0]) {
|
||||||
@ -27,9 +27,7 @@ export const uploadDocument = async (event: any) => {
|
|||||||
)
|
)
|
||||||
.then((response: Response) => {
|
.then((response: Response) => {
|
||||||
response.json().then((createdDocumentIdFromBody) => {
|
response.json().then((createdDocumentIdFromBody) => {
|
||||||
router.push(
|
router.push(`${NEXT_PUBLIC_WEBAPP_URL}/documents/${createdDocumentIdFromBody}/recipients`);
|
||||||
`${NEXT_PUBLIC_WEBAPP_URL}/documents/${createdDocumentIdFromBody}/recipients`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,16 +6,13 @@ export const deleteRecipient = (recipient: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return toast.promise(
|
return toast.promise(
|
||||||
fetch(
|
fetch("/api/documents/" + recipient.documentId + "/recipients/" + recipient.id, {
|
||||||
"/api/documents/" + recipient.documentId + "/recipients/" + recipient.id,
|
method: "DELETE",
|
||||||
{
|
headers: {
|
||||||
method: "DELETE",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
body: JSON.stringify(recipient),
|
||||||
},
|
}),
|
||||||
body: JSON.stringify(recipient),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
loading: "Deleting...",
|
loading: "Deleting...",
|
||||||
success: "Deleted.",
|
success: "Deleted.",
|
||||||
|
|||||||
@ -7,4 +7,4 @@ export { getDocuments } from "./getDocuments";
|
|||||||
export { deleteDocument } from "./deleteDocument";
|
export { deleteDocument } from "./deleteDocument";
|
||||||
export { deleteRecipient } from "./deleteRecipient";
|
export { deleteRecipient } from "./deleteRecipient";
|
||||||
export { createOrUpdateRecipient } from "./createOrUpdateRecipient";
|
export { createOrUpdateRecipient } from "./createOrUpdateRecipient";
|
||||||
export { sendSigningRequests } from "./sendSigningRequests";
|
export { sendSigningRequests } from "./sendSigningRequests";
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export const sendSigningRequests = async (
|
export const sendSigningRequests = async (document: any, resendTo: number[] = []) => {
|
||||||
document: any,
|
|
||||||
resendTo: number[] = []
|
|
||||||
) => {
|
|
||||||
if (!document || !document.id) return;
|
if (!document || !document.id) return;
|
||||||
try {
|
try {
|
||||||
const sent = await toast.promise(
|
const sent = await toast.promise(
|
||||||
|
|||||||
@ -1,11 +1,7 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export const signDocument = (
|
export const signDocument = (document: any, signatures: any[], token: string): Promise<any> => {
|
||||||
document: any,
|
|
||||||
signatures: any[],
|
|
||||||
token: string
|
|
||||||
): Promise<any> => {
|
|
||||||
const body = { documentId: document.id, signatures };
|
const body = { documentId: document.id, signatures };
|
||||||
|
|
||||||
return toast.promise(
|
return toast.promise(
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import { compare, hash } from "bcryptjs";
|
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
import type { Session } from "next-auth";
|
|
||||||
import {
|
|
||||||
getSession as getSessionInner,
|
|
||||||
GetSessionParams,
|
|
||||||
} from "next-auth/react";
|
|
||||||
|
|
||||||
import { HttpError } from "@documenso/lib/server";
|
import { HttpError } from "@documenso/lib/server";
|
||||||
|
import { compare, hash } from "bcryptjs";
|
||||||
|
import type { Session } from "next-auth";
|
||||||
|
import { GetSessionParams, getSession as getSessionInner } from "next-auth/react";
|
||||||
|
|
||||||
export async function hashPassword(password: string) {
|
export async function hashPassword(password: string) {
|
||||||
const hashedPassword = await hash(password, 12);
|
const hashedPassword = await hash(password, 12);
|
||||||
@ -28,9 +24,7 @@ export function validPassword(password: string) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSession(
|
export async function getSession(options: GetSessionParams): Promise<Session | null> {
|
||||||
options: GetSessionParams
|
|
||||||
): Promise<Session | null> {
|
|
||||||
const session = await getSessionInner(options);
|
const session = await getSessionInner(options);
|
||||||
|
|
||||||
// that these are equal are ensured in `[...nextauth]`'s callback
|
// that these are equal are ensured in `[...nextauth]`'s callback
|
||||||
@ -43,11 +37,7 @@ export function isPasswordValid(
|
|||||||
breakdown: boolean,
|
breakdown: boolean,
|
||||||
strict?: boolean
|
strict?: boolean
|
||||||
): { caplow: boolean; num: boolean; min: boolean; admin_min: boolean };
|
): { caplow: boolean; num: boolean; min: boolean; admin_min: boolean };
|
||||||
export function isPasswordValid(
|
export function isPasswordValid(password: string, breakdown?: boolean, strict?: boolean) {
|
||||||
password: string,
|
|
||||||
breakdown?: boolean,
|
|
||||||
strict?: boolean
|
|
||||||
) {
|
|
||||||
let cap = false, // Has uppercase characters
|
let cap = false, // Has uppercase characters
|
||||||
low = false, // Has lowercase characters
|
low = false, // Has lowercase characters
|
||||||
num = false, // At least one number
|
num = false, // At least one number
|
||||||
@ -63,8 +53,7 @@ export function isPasswordValid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!breakdown)
|
if (!breakdown) return cap && low && num && min && (strict ? admin_min : true);
|
||||||
return cap && low && num && min && (strict ? admin_min : true);
|
|
||||||
|
|
||||||
let errors: Record<string, boolean> = { caplow: cap && low, num, min };
|
let errors: Record<string, boolean> = { caplow: cap && low, num, min };
|
||||||
// Only return the admin key if strict mode is enabled.
|
// Only return the admin key if strict mode is enabled.
|
||||||
@ -73,14 +62,11 @@ export function isPasswordValid(
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CtxOrReq =
|
type CtxOrReq = { req: NextApiRequest; ctx?: never } | { ctx: { req: NextApiRequest }; req?: never };
|
||||||
| { req: NextApiRequest; ctx?: never }
|
|
||||||
| { ctx: { req: NextApiRequest }; req?: never };
|
|
||||||
|
|
||||||
export const ensureSession = async (ctxOrReq: CtxOrReq) => {
|
export const ensureSession = async (ctxOrReq: CtxOrReq) => {
|
||||||
const session = await getSession(ctxOrReq);
|
const session = await getSession(ctxOrReq);
|
||||||
if (!session?.user)
|
if (!session?.user) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||||
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
|
||||||
return session;
|
return session;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,11 +12,7 @@ export const getSafeRedirectUrl = (url = "") => {
|
|||||||
const urlParsed = new URL(url);
|
const urlParsed = new URL(url);
|
||||||
|
|
||||||
// Avoid open redirection security vulnerability
|
// Avoid open redirection security vulnerability
|
||||||
if (
|
if (!["CONSOLE_URL", "WEBAPP_URL", "WEBSITE_URL"].some((u) => new URL(u).origin === urlParsed.origin)) {
|
||||||
!["CONSOLE_URL", "WEBAPP_URL", "WEBSITE_URL"].some(
|
|
||||||
(u) => new URL(u).origin === urlParsed.origin
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
url = `${"WEBAPP_URL"}/`;
|
url = `${"WEBAPP_URL"}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import { sendMail } from "./sendMail";
|
|
||||||
import { signingCompleteTemplate } from "@documenso/lib/mail";
|
import { signingCompleteTemplate } from "@documenso/lib/mail";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
|
||||||
import { addDigitalSignature } from "@documenso/signing/addDigitalSignature";
|
import { addDigitalSignature } from "@documenso/signing/addDigitalSignature";
|
||||||
|
import { sendMail } from "./sendMail";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
export const sendSigningDoneMail = async (
|
export const sendSigningDoneMail = async (recipient: any, document: PrismaDocument, user: any) => {
|
||||||
recipient: any,
|
|
||||||
document: PrismaDocument,
|
|
||||||
user: any
|
|
||||||
) => {
|
|
||||||
await sendMail(
|
await sendMail(
|
||||||
user.email,
|
user.email,
|
||||||
`Completed: "${document.title}"`,
|
`Completed: "${document.title}"`,
|
||||||
@ -15,10 +11,7 @@ export const sendSigningDoneMail = async (
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
filename: document.title,
|
filename: document.title,
|
||||||
content: Buffer.from(
|
content: Buffer.from(await addDigitalSignature(document.document), "base64"),
|
||||||
await addDigitalSignature(document.document),
|
|
||||||
"base64"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { sendMail } from "./sendMail";
|
|
||||||
import { SendStatus, ReadStatus, DocumentStatus } from "@prisma/client";
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
|
|
||||||
import { signingRequestTemplate } from "@documenso/lib/mail";
|
import { signingRequestTemplate } from "@documenso/lib/mail";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
|
||||||
|
import { sendMail } from "./sendMail";
|
||||||
|
import { DocumentStatus, ReadStatus, SendStatus } from "@prisma/client";
|
||||||
|
|
||||||
export const sendSigningRequest = async (recipient: any, document: any, user: any) => {
|
export const sendSigningRequest = async (recipient: any, document: any, user: any) => {
|
||||||
const signingRequestMessage = user.name
|
const signingRequestMessage = user.name
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
|
||||||
import { baseEmailTemplate } from "./baseTemplate";
|
import { baseEmailTemplate } from "./baseTemplate";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
export const signingCompleteTemplate = (message: string) => {
|
export const signingCompleteTemplate = (message: string) => {
|
||||||
const customContent = `
|
const customContent = `
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
|
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
|
||||||
import { baseEmailTemplate } from "./baseTemplate";
|
import { baseEmailTemplate } from "./baseTemplate";
|
||||||
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
export const signingRequestTemplate = (
|
export const signingRequestTemplate = (
|
||||||
message: string,
|
message: string,
|
||||||
|
|||||||
@ -2,11 +2,7 @@ import { getUserFromToken } from "@documenso/lib/server";
|
|||||||
import prisma from "@documenso/prisma";
|
import prisma from "@documenso/prisma";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
export const getDocument = async (
|
export const getDocument = async (documentId: number, req: any, res: any): Promise<PrismaDocument> => {
|
||||||
documentId: number,
|
|
||||||
req: any,
|
|
||||||
res: any
|
|
||||||
): Promise<PrismaDocument> => {
|
|
||||||
const user = await getUserFromToken(req, res);
|
const user = await getUserFromToken(req, res);
|
||||||
if (!user) return Promise.reject("Invalid user or token.");
|
if (!user) return Promise.reject("Invalid user or token.");
|
||||||
if (!documentId) Promise.reject("No documentId");
|
if (!documentId) Promise.reject("No documentId");
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { getUserFromToken } from "@documenso/lib/server";
|
import { getUserFromToken } from "@documenso/lib/server";
|
||||||
import prisma from "@documenso/prisma";
|
import prisma from "@documenso/prisma";
|
||||||
|
|
||||||
export const getDocumentsForUserFromToken = async (
|
export const getDocumentsForUserFromToken = async (context: any): Promise<any> => {
|
||||||
context: any
|
|
||||||
): Promise<any> => {
|
|
||||||
const user = await getUserFromToken(context.req, context.res);
|
const user = await getUserFromToken(context.req, context.res);
|
||||||
if (!user) return Promise.reject("Invalid user or token.");
|
if (!user) return Promise.reject("Invalid user or token.");
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { getServerErrorFromUnknown } from "@documenso/lib/server";
|
import { getServerErrorFromUnknown } from "@documenso/lib/server";
|
||||||
|
|
||||||
type Handle<T> = (req: NextApiRequest, res: NextApiResponse) => Promise<T>;
|
type Handle<T> = (req: NextApiRequest, res: NextApiResponse) => Promise<T>;
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
import {
|
|
||||||
PrismaClientKnownRequestError,
|
|
||||||
NotFoundError,
|
|
||||||
} from "@prisma/client/runtime";
|
|
||||||
|
|
||||||
import { HttpError } from "@documenso/lib/server";
|
import { HttpError } from "@documenso/lib/server";
|
||||||
|
import { NotFoundError, PrismaClientKnownRequestError } from "@prisma/client/runtime";
|
||||||
|
|
||||||
export function getServerErrorFromUnknown(cause: unknown): HttpError {
|
export function getServerErrorFromUnknown(cause: unknown): HttpError {
|
||||||
// Error was manually thrown and does not need to be parsed.
|
// Error was manually thrown and does not need to be parsed.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import prisma from "@documenso/prisma";
|
import prisma from "@documenso/prisma";
|
||||||
import { User as PrismaUser } from "@prisma/client";
|
import { User as PrismaUser } from "@prisma/client";
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
import { getToken } from "next-auth/jwt";
|
||||||
import { signOut } from "next-auth/react";
|
import { signOut } from "next-auth/react";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
|
|
||||||
import fontkit from "@pdf-lib/fontkit";
|
import fontkit from "@pdf-lib/fontkit";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
|
||||||
|
|
||||||
export async function insertTextInPDF(
|
export async function insertTextInPDF(
|
||||||
pdfAsBase64: string,
|
pdfAsBase64: string,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { PrismaClient, Document, User } from "@prisma/client";
|
import { isENVProd } from "@documenso/lib";
|
||||||
import { isENVProd } from "@documenso/lib"
|
import { Document, PrismaClient, User } from "@prisma/client";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var client: PrismaClient | undefined;
|
var client: PrismaClient | undefined;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import prisma from "@documenso/prisma";
|
|
||||||
import { hashPassword } from "@documenso/lib/auth";
|
|
||||||
import { IdentityProvider } from "@prisma/client";
|
|
||||||
import { coloredConsole } from "@documenso/lib";
|
import { coloredConsole } from "@documenso/lib";
|
||||||
|
import { hashPassword } from "@documenso/lib/auth";
|
||||||
|
import prisma from "@documenso/prisma";
|
||||||
|
import { IdentityProvider } from "@prisma/client";
|
||||||
|
|
||||||
async function createUser(userData: { email: string; password: string }) {
|
async function createUser(userData: { email: string; password: string }) {
|
||||||
try {
|
try {
|
||||||
@ -15,9 +15,7 @@ async function createUser(userData: { email: string; password: string }) {
|
|||||||
|
|
||||||
return user;
|
return user;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.info(
|
console.info(`WARN: Could not create user "${userData.email}". Maybe the email is already taken?`);
|
||||||
`WARN: Could not create user "${userData.email}". Maybe the email is already taken?`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,10 @@
|
|||||||
|
import { PDFDocument, PDFHexString, PDFName, PDFNumber, PDFString } from "pdf-lib";
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
// Local copy of Node SignPDF because https://github.com/vbuch/node-signpdf/pull/187 was not published in NPM yet. Can be switched to npm packge.
|
// Local copy of Node SignPDF because https://github.com/vbuch/node-signpdf/pull/187 was not published in NPM yet. Can be switched to npm packge.
|
||||||
const signer = require("./node-signpdf/dist/signpdf");
|
const signer = require("./node-signpdf/dist/signpdf");
|
||||||
import {
|
|
||||||
PDFDocument,
|
|
||||||
PDFName,
|
|
||||||
PDFNumber,
|
|
||||||
PDFHexString,
|
|
||||||
PDFString,
|
|
||||||
} from "pdf-lib";
|
|
||||||
|
|
||||||
export const addDigitalSignature = async (
|
export const addDigitalSignature = async (documentAsBase64: string): Promise<string> => {
|
||||||
documentAsBase64: string
|
|
||||||
): Promise<string> => {
|
|
||||||
// Custom code to add Byterange to PDF
|
// Custom code to add Byterange to PDF
|
||||||
const PDFArrayCustom = require("./PDFArrayCustom");
|
const PDFArrayCustom = require("./PDFArrayCustom");
|
||||||
const pdfBuffer = Buffer.from(documentAsBase64, "base64");
|
const pdfBuffer = Buffer.from(documentAsBase64, "base64");
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { signDocument } from "./signDocument";
|
export { signDocument } from "./signDocument";
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = exports.ERROR_VERIFY_SIGNATURE = exports.ERROR_TYPE_UNKNOWN = exports.ERROR_TYPE_PARSE = exports.ERROR_TYPE_INPUT = void 0;
|
exports.default =
|
||||||
|
exports.ERROR_VERIFY_SIGNATURE =
|
||||||
|
exports.ERROR_TYPE_UNKNOWN =
|
||||||
|
exports.ERROR_TYPE_PARSE =
|
||||||
|
exports.ERROR_TYPE_INPUT =
|
||||||
|
void 0;
|
||||||
const ERROR_TYPE_UNKNOWN = 1;
|
const ERROR_TYPE_UNKNOWN = 1;
|
||||||
exports.ERROR_TYPE_UNKNOWN = ERROR_TYPE_UNKNOWN;
|
exports.ERROR_TYPE_UNKNOWN = ERROR_TYPE_UNKNOWN;
|
||||||
const ERROR_TYPE_INPUT = 2;
|
const ERROR_TYPE_INPUT = 2;
|
||||||
@ -18,13 +23,11 @@ class SignPdfError extends Error {
|
|||||||
super(msg);
|
super(msg);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // Shorthand
|
} // Shorthand
|
||||||
|
|
||||||
|
|
||||||
SignPdfError.TYPE_UNKNOWN = ERROR_TYPE_UNKNOWN;
|
SignPdfError.TYPE_UNKNOWN = ERROR_TYPE_UNKNOWN;
|
||||||
SignPdfError.TYPE_INPUT = ERROR_TYPE_INPUT;
|
SignPdfError.TYPE_INPUT = ERROR_TYPE_INPUT;
|
||||||
SignPdfError.TYPE_PARSE = ERROR_TYPE_PARSE;
|
SignPdfError.TYPE_PARSE = ERROR_TYPE_PARSE;
|
||||||
SignPdfError.VERIFY_SIGNATURE = ERROR_VERIFY_SIGNATURE;
|
SignPdfError.VERIFY_SIGNATURE = ERROR_VERIFY_SIGNATURE;
|
||||||
var _default = SignPdfError;
|
var _default = SignPdfError;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,18 +1,24 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.SUBFILTER_ETSI_CADES_DETACHED = exports.SUBFILTER_ADOBE_X509_SHA1 = exports.SUBFILTER_ADOBE_PKCS7_SHA1 = exports.SUBFILTER_ADOBE_PKCS7_DETACHED = exports.DEFAULT_SIGNATURE_LENGTH = exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = void 0;
|
exports.SUBFILTER_ETSI_CADES_DETACHED =
|
||||||
|
exports.SUBFILTER_ADOBE_X509_SHA1 =
|
||||||
|
exports.SUBFILTER_ADOBE_PKCS7_SHA1 =
|
||||||
|
exports.SUBFILTER_ADOBE_PKCS7_DETACHED =
|
||||||
|
exports.DEFAULT_SIGNATURE_LENGTH =
|
||||||
|
exports.DEFAULT_BYTE_RANGE_PLACEHOLDER =
|
||||||
|
void 0;
|
||||||
const DEFAULT_SIGNATURE_LENGTH = 8192;
|
const DEFAULT_SIGNATURE_LENGTH = 8192;
|
||||||
exports.DEFAULT_SIGNATURE_LENGTH = DEFAULT_SIGNATURE_LENGTH;
|
exports.DEFAULT_SIGNATURE_LENGTH = DEFAULT_SIGNATURE_LENGTH;
|
||||||
const DEFAULT_BYTE_RANGE_PLACEHOLDER = '**********';
|
const DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
||||||
exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = DEFAULT_BYTE_RANGE_PLACEHOLDER;
|
exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = DEFAULT_BYTE_RANGE_PLACEHOLDER;
|
||||||
const SUBFILTER_ADOBE_PKCS7_DETACHED = 'adbe.pkcs7.detached';
|
const SUBFILTER_ADOBE_PKCS7_DETACHED = "adbe.pkcs7.detached";
|
||||||
exports.SUBFILTER_ADOBE_PKCS7_DETACHED = SUBFILTER_ADOBE_PKCS7_DETACHED;
|
exports.SUBFILTER_ADOBE_PKCS7_DETACHED = SUBFILTER_ADOBE_PKCS7_DETACHED;
|
||||||
const SUBFILTER_ADOBE_PKCS7_SHA1 = 'adbe.pkcs7.sha1';
|
const SUBFILTER_ADOBE_PKCS7_SHA1 = "adbe.pkcs7.sha1";
|
||||||
exports.SUBFILTER_ADOBE_PKCS7_SHA1 = SUBFILTER_ADOBE_PKCS7_SHA1;
|
exports.SUBFILTER_ADOBE_PKCS7_SHA1 = SUBFILTER_ADOBE_PKCS7_SHA1;
|
||||||
const SUBFILTER_ADOBE_X509_SHA1 = 'adbe.x509.rsa.sha1';
|
const SUBFILTER_ADOBE_X509_SHA1 = "adbe.x509.rsa.sha1";
|
||||||
exports.SUBFILTER_ADOBE_X509_SHA1 = SUBFILTER_ADOBE_X509_SHA1;
|
exports.SUBFILTER_ADOBE_X509_SHA1 = SUBFILTER_ADOBE_X509_SHA1;
|
||||||
const SUBFILTER_ETSI_CADES_DETACHED = 'ETSI.CAdES.detached';
|
const SUBFILTER_ETSI_CADES_DETACHED = "ETSI.CAdES.detached";
|
||||||
exports.SUBFILTER_ETSI_CADES_DETACHED = SUBFILTER_ETSI_CADES_DETACHED;
|
exports.SUBFILTER_ETSI_CADES_DETACHED = SUBFILTER_ETSI_CADES_DETACHED;
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _SignPdfError = _interopRequireDefault(require("../SignPdfError"));
|
var _SignPdfError = _interopRequireDefault(require("../SignPdfError"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const getSubstringIndex = (str, substring, n) => {
|
const getSubstringIndex = (str, substring, n) => {
|
||||||
let times = 0;
|
let times = 0;
|
||||||
@ -30,42 +32,49 @@ const getSubstringIndex = (str, substring, n) => {
|
|||||||
* @returns {Object} {ByteRange: Number[], signature: Buffer, signedData: Buffer}
|
* @returns {Object} {ByteRange: Number[], signature: Buffer, signedData: Buffer}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
const extractSignature = (pdf, signatureCount = 1) => {
|
const extractSignature = (pdf, signatureCount = 1) => {
|
||||||
if (!(pdf instanceof Buffer)) {
|
if (!(pdf instanceof Buffer)) {
|
||||||
throw new _SignPdfError.default('PDF expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
|
throw new _SignPdfError.default("PDF expected as Buffer.", _SignPdfError.default.TYPE_INPUT);
|
||||||
} // const byteRangePos = pdf.indexOf('/ByteRange [');
|
} // const byteRangePos = pdf.indexOf('/ByteRange [');
|
||||||
|
|
||||||
|
const byteRangePos = getSubstringIndex(pdf, "/ByteRange [", signatureCount);
|
||||||
const byteRangePos = getSubstringIndex(pdf, '/ByteRange [', signatureCount);
|
|
||||||
|
|
||||||
if (byteRangePos === -1) {
|
if (byteRangePos === -1) {
|
||||||
throw new _SignPdfError.default('Failed to locate ByteRange.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default("Failed to locate ByteRange.", _SignPdfError.default.TYPE_PARSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteRangeEnd = pdf.indexOf(']', byteRangePos);
|
const byteRangeEnd = pdf.indexOf("]", byteRangePos);
|
||||||
|
|
||||||
if (byteRangeEnd === -1) {
|
if (byteRangeEnd === -1) {
|
||||||
throw new _SignPdfError.default('Failed to locate the end of the ByteRange.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
"Failed to locate the end of the ByteRange.",
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteRange = pdf.slice(byteRangePos, byteRangeEnd + 1).toString();
|
const byteRange = pdf.slice(byteRangePos, byteRangeEnd + 1).toString();
|
||||||
const matches = /\/ByteRange \[(\d+) +(\d+) +(\d+) +(\d+) *\]/.exec(byteRange);
|
const matches = /\/ByteRange \[(\d+) +(\d+) +(\d+) +(\d+) *\]/.exec(byteRange);
|
||||||
|
|
||||||
if (matches === null) {
|
if (matches === null) {
|
||||||
throw new _SignPdfError.default('Failed to parse the ByteRange.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default("Failed to parse the ByteRange.", _SignPdfError.default.TYPE_PARSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ByteRange = matches.slice(1).map(Number);
|
const ByteRange = matches.slice(1).map(Number);
|
||||||
const signedData = Buffer.concat([pdf.slice(ByteRange[0], ByteRange[0] + ByteRange[1]), pdf.slice(ByteRange[2], ByteRange[2] + ByteRange[3])]);
|
const signedData = Buffer.concat([
|
||||||
const signatureHex = pdf.slice(ByteRange[0] + ByteRange[1] + 1, ByteRange[2]).toString('binary').replace(/(?:00|>)+$/, '');
|
pdf.slice(ByteRange[0], ByteRange[0] + ByteRange[1]),
|
||||||
const signature = Buffer.from(signatureHex, 'hex').toString('binary');
|
pdf.slice(ByteRange[2], ByteRange[2] + ByteRange[3]),
|
||||||
|
]);
|
||||||
|
const signatureHex = pdf
|
||||||
|
.slice(ByteRange[0] + ByteRange[1] + 1, ByteRange[2])
|
||||||
|
.toString("binary")
|
||||||
|
.replace(/(?:00|>)+$/, "");
|
||||||
|
const signature = Buffer.from(signatureHex, "hex").toString("binary");
|
||||||
return {
|
return {
|
||||||
ByteRange: matches.slice(1, 5).map(Number),
|
ByteRange: matches.slice(1, 5).map(Number),
|
||||||
signature,
|
signature,
|
||||||
signedData
|
signedData,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = extractSignature;
|
var _default = extractSignature;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
@ -9,7 +9,9 @@ var _SignPdfError = _interopRequireDefault(require("../SignPdfError"));
|
|||||||
|
|
||||||
var _const = require("./const");
|
var _const = require("./const");
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds ByteRange information within a given PDF Buffer if one exists
|
* Finds ByteRange information within a given PDF Buffer if one exists
|
||||||
@ -17,25 +19,32 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|||||||
* @param {Buffer} pdf
|
* @param {Buffer} pdf
|
||||||
* @returns {Object} {byteRangePlaceholder: String, byteRangeStrings: String[], byteRange: String[]}
|
* @returns {Object} {byteRangePlaceholder: String, byteRangeStrings: String[], byteRange: String[]}
|
||||||
*/
|
*/
|
||||||
const findByteRange = pdf => {
|
const findByteRange = (pdf) => {
|
||||||
if (!(pdf instanceof Buffer)) {
|
if (!(pdf instanceof Buffer)) {
|
||||||
throw new _SignPdfError.default('PDF expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
|
throw new _SignPdfError.default("PDF expected as Buffer.", _SignPdfError.default.TYPE_INPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteRangeStrings = pdf.toString().match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g);
|
const byteRangeStrings = pdf
|
||||||
|
.toString()
|
||||||
|
.match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g);
|
||||||
|
|
||||||
if (!byteRangeStrings) {
|
if (!byteRangeStrings) {
|
||||||
throw new _SignPdfError.default('No ByteRangeStrings found within PDF buffer', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
"No ByteRangeStrings found within PDF buffer",
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteRangePlaceholder = byteRangeStrings.find(s => s.includes(`/${_const.DEFAULT_BYTE_RANGE_PLACEHOLDER}`));
|
const byteRangePlaceholder = byteRangeStrings.find((s) =>
|
||||||
const byteRanges = byteRangeStrings.map(brs => brs.match(/[^[\s]*(?:\d|\/\*{10})/g));
|
s.includes(`/${_const.DEFAULT_BYTE_RANGE_PLACEHOLDER}`)
|
||||||
|
);
|
||||||
|
const byteRanges = byteRangeStrings.map((brs) => brs.match(/[^[\s]*(?:\d|\/\*{10})/g));
|
||||||
return {
|
return {
|
||||||
byteRangePlaceholder,
|
byteRangePlaceholder,
|
||||||
byteRangeStrings,
|
byteRangeStrings,
|
||||||
byteRanges
|
byteRanges,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = findByteRange;
|
var _default = findByteRange;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,37 +1,37 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
Object.defineProperty(exports, "extractSignature", {
|
Object.defineProperty(exports, "extractSignature", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _extractSignature.default;
|
return _extractSignature.default;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
Object.defineProperty(exports, "findByteRange", {
|
Object.defineProperty(exports, "findByteRange", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _findByteRange.default;
|
return _findByteRange.default;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
Object.defineProperty(exports, "pdfkitAddPlaceholder", {
|
Object.defineProperty(exports, "pdfkitAddPlaceholder", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _pdfkitAddPlaceholder.default;
|
return _pdfkitAddPlaceholder.default;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
Object.defineProperty(exports, "plainAddPlaceholder", {
|
Object.defineProperty(exports, "plainAddPlaceholder", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _plainAddPlaceholder.default;
|
return _plainAddPlaceholder.default;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
Object.defineProperty(exports, "removeTrailingNewLine", {
|
Object.defineProperty(exports, "removeTrailingNewLine", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _removeTrailingNewLine.default;
|
return _removeTrailingNewLine.default;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var _extractSignature = _interopRequireDefault(require("./extractSignature"));
|
var _extractSignature = _interopRequireDefault(require("./extractSignature"));
|
||||||
@ -44,6 +44,8 @@ var _removeTrailingNewLine = _interopRequireDefault(require("./removeTrailingNew
|
|||||||
|
|
||||||
var _findByteRange = _interopRequireDefault(require("./findByteRange"));
|
var _findByteRange = _interopRequireDefault(require("./findByteRange"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
'This string is added so that jest collects coverage for this file'; // eslint-disable-line
|
("This string is added so that jest collects coverage for this file"); // eslint-disable-line
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
@ -17,10 +17,9 @@ PDFAbstractReference - abstract class for PDF reference
|
|||||||
*/
|
*/
|
||||||
class PDFAbstractReference {
|
class PDFAbstractReference {
|
||||||
toString() {
|
toString() {
|
||||||
throw new Error('Must be implemented by subclasses');
|
throw new Error("Must be implemented by subclasses");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _default = PDFAbstractReference;
|
var _default = PDFAbstractReference;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _abstract_reference = _interopRequireDefault(require("./abstract_reference"));
|
var _abstract_reference = _interopRequireDefault(require("./abstract_reference"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
PDFObject by Devon Govett used below.
|
PDFObject by Devon Govett used below.
|
||||||
@ -20,26 +22,26 @@ Modifications may have been applied for the purposes of node-signpdf.
|
|||||||
PDFObject - converts JavaScript types into their corresponding PDF types.
|
PDFObject - converts JavaScript types into their corresponding PDF types.
|
||||||
By Devon Govett
|
By Devon Govett
|
||||||
*/
|
*/
|
||||||
const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
|
const pad = (str, length) => (Array(length + 1).join("0") + str).slice(-length);
|
||||||
|
|
||||||
const escapableRe = /[\n\r\t\b\f()\\]/g;
|
const escapableRe = /[\n\r\t\b\f()\\]/g;
|
||||||
const escapable = {
|
const escapable = {
|
||||||
'\n': '\\n',
|
"\n": "\\n",
|
||||||
'\r': '\\r',
|
"\r": "\\r",
|
||||||
'\t': '\\t',
|
"\t": "\\t",
|
||||||
'\b': '\\b',
|
"\b": "\\b",
|
||||||
'\f': '\\f',
|
"\f": "\\f",
|
||||||
'\\': '\\\\',
|
"\\": "\\\\",
|
||||||
'(': '\\(',
|
"(": "\\(",
|
||||||
')': '\\)'
|
")": "\\)",
|
||||||
}; // Convert little endian UTF-16 to big endian
|
}; // Convert little endian UTF-16 to big endian
|
||||||
|
|
||||||
const swapBytes = buff => buff.swap16();
|
const swapBytes = (buff) => buff.swap16();
|
||||||
|
|
||||||
class PDFObject {
|
class PDFObject {
|
||||||
static convert(object, encryptFn = null) {
|
static convert(object, encryptFn = null) {
|
||||||
// String literals are converted to the PDF name type
|
// String literals are converted to the PDF name type
|
||||||
if (typeof object === 'string') {
|
if (typeof object === "string") {
|
||||||
return `/${object}`; // String objects are converted to PDF strings (UTF-16)
|
return `/${object}`; // String objects are converted to PDF strings (UTF-16)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,29 +57,26 @@ class PDFObject {
|
|||||||
}
|
}
|
||||||
} // If so, encode it as big endian UTF-16
|
} // If so, encode it as big endian UTF-16
|
||||||
|
|
||||||
|
|
||||||
let stringBuffer;
|
let stringBuffer;
|
||||||
|
|
||||||
if (isUnicode) {
|
if (isUnicode) {
|
||||||
stringBuffer = swapBytes(Buffer.from(`\ufeff${string}`, 'utf16le'));
|
stringBuffer = swapBytes(Buffer.from(`\ufeff${string}`, "utf16le"));
|
||||||
} else {
|
} else {
|
||||||
stringBuffer = Buffer.from(string, 'ascii');
|
stringBuffer = Buffer.from(string, "ascii");
|
||||||
} // Encrypt the string when necessary
|
} // Encrypt the string when necessary
|
||||||
|
|
||||||
|
|
||||||
if (encryptFn) {
|
if (encryptFn) {
|
||||||
string = encryptFn(stringBuffer).toString('binary');
|
string = encryptFn(stringBuffer).toString("binary");
|
||||||
} else {
|
} else {
|
||||||
string = stringBuffer.toString('binary');
|
string = stringBuffer.toString("binary");
|
||||||
} // Escape characters as required by the spec
|
} // Escape characters as required by the spec
|
||||||
|
|
||||||
|
string = string.replace(escapableRe, (c) => escapable[c]);
|
||||||
string = string.replace(escapableRe, c => escapable[c]);
|
|
||||||
return `(${string})`; // Buffers are converted to PDF hex strings
|
return `(${string})`; // Buffers are converted to PDF hex strings
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Buffer.isBuffer(object)) {
|
if (Buffer.isBuffer(object)) {
|
||||||
return `<${object.toString('hex')}>`;
|
return `<${object.toString("hex")}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof _abstract_reference.default) {
|
if (object instanceof _abstract_reference.default) {
|
||||||
@ -85,51 +84,54 @@ class PDFObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof Date) {
|
if (object instanceof Date) {
|
||||||
let string = `D:${pad(object.getUTCFullYear(), 4)}${pad(object.getUTCMonth() + 1, 2)}${pad(object.getUTCDate(), 2)}${pad(object.getUTCHours(), 2)}${pad(object.getUTCMinutes(), 2)}${pad(object.getUTCSeconds(), 2)}Z`; // Encrypt the string when necessary
|
let string = `D:${pad(object.getUTCFullYear(), 4)}${pad(object.getUTCMonth() + 1, 2)}${pad(
|
||||||
|
object.getUTCDate(),
|
||||||
|
2
|
||||||
|
)}${pad(object.getUTCHours(), 2)}${pad(object.getUTCMinutes(), 2)}${pad(object.getUTCSeconds(), 2)}Z`; // Encrypt the string when necessary
|
||||||
|
|
||||||
if (encryptFn) {
|
if (encryptFn) {
|
||||||
string = encryptFn(Buffer.from(string, 'ascii')).toString('binary'); // Escape characters as required by the spec
|
string = encryptFn(Buffer.from(string, "ascii")).toString("binary"); // Escape characters as required by the spec
|
||||||
|
|
||||||
string = string.replace(escapableRe, c => escapable[c]);
|
string = string.replace(escapableRe, (c) => escapable[c]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `(${string})`;
|
return `(${string})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(object)) {
|
if (Array.isArray(object)) {
|
||||||
const items = object.map(e => PDFObject.convert(e, encryptFn)).join(' ');
|
const items = object.map((e) => PDFObject.convert(e, encryptFn)).join(" ");
|
||||||
return `[${items}]`;
|
return `[${items}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ({}.toString.call(object) === '[object Object]') {
|
if ({}.toString.call(object) === "[object Object]") {
|
||||||
const out = ['<<'];
|
const out = ["<<"];
|
||||||
let streamData; // @todo this can probably be refactored into a reduce
|
let streamData; // @todo this can probably be refactored into a reduce
|
||||||
|
|
||||||
Object.entries(object).forEach(([key, val]) => {
|
Object.entries(object).forEach(([key, val]) => {
|
||||||
let checkedValue = '';
|
let checkedValue = "";
|
||||||
|
|
||||||
if (val.toString().indexOf('<<') !== -1) {
|
if (val.toString().indexOf("<<") !== -1) {
|
||||||
checkedValue = val;
|
checkedValue = val;
|
||||||
} else {
|
} else {
|
||||||
checkedValue = PDFObject.convert(val, encryptFn);
|
checkedValue = PDFObject.convert(val, encryptFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'stream') {
|
if (key === "stream") {
|
||||||
streamData = `${key}\n${val}\nendstream`;
|
streamData = `${key}\n${val}\nendstream`;
|
||||||
} else {
|
} else {
|
||||||
out.push(`/${key} ${checkedValue}`);
|
out.push(`/${key} ${checkedValue}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
out.push('>>');
|
out.push(">>");
|
||||||
|
|
||||||
if (streamData) {
|
if (streamData) {
|
||||||
out.push(streamData);
|
out.push(streamData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out.join('\n');
|
return out.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof object === 'number') {
|
if (typeof object === "number") {
|
||||||
return PDFObject.number(object);
|
return PDFObject.number(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +145,6 @@ class PDFObject {
|
|||||||
|
|
||||||
throw new Error(`unsupported number: ${n}`);
|
throw new Error(`unsupported number: ${n}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.default = PDFObject;
|
exports.default = PDFObject;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
@ -9,7 +9,9 @@ var _const = require("./const");
|
|||||||
|
|
||||||
var _pdfkitReferenceMock = _interopRequireDefault(require("./pdfkitReferenceMock"));
|
var _pdfkitReferenceMock = _interopRequireDefault(require("./pdfkitReferenceMock"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
|
||||||
@ -25,18 +27,18 @@ const pdfkitAddPlaceholder = ({
|
|||||||
pdf,
|
pdf,
|
||||||
pdfBuffer,
|
pdfBuffer,
|
||||||
reason,
|
reason,
|
||||||
contactInfo = 'emailfromp1289@gmail.com',
|
contactInfo = "emailfromp1289@gmail.com",
|
||||||
name = 'Name from p12',
|
name = "Name from p12",
|
||||||
location = 'Location from p12',
|
location = "Location from p12",
|
||||||
signatureLength = _const.DEFAULT_SIGNATURE_LENGTH,
|
signatureLength = _const.DEFAULT_SIGNATURE_LENGTH,
|
||||||
byteRangePlaceholder = _const.DEFAULT_BYTE_RANGE_PLACEHOLDER,
|
byteRangePlaceholder = _const.DEFAULT_BYTE_RANGE_PLACEHOLDER,
|
||||||
subFilter = _const.SUBFILTER_ADOBE_PKCS7_DETACHED
|
subFilter = _const.SUBFILTER_ADOBE_PKCS7_DETACHED,
|
||||||
}) => {
|
}) => {
|
||||||
/* eslint-disable no-underscore-dangle,no-param-reassign */
|
/* eslint-disable no-underscore-dangle,no-param-reassign */
|
||||||
// Generate the signature placeholder
|
// Generate the signature placeholder
|
||||||
const signature = pdf.ref({
|
const signature = pdf.ref({
|
||||||
Type: 'Sig',
|
Type: "Sig",
|
||||||
Filter: 'Adobe.PPKLite',
|
Filter: "Adobe.PPKLite",
|
||||||
SubFilter: subFilter,
|
SubFilter: subFilter,
|
||||||
ByteRange: [0, byteRangePlaceholder, byteRangePlaceholder, byteRangePlaceholder],
|
ByteRange: [0, byteRangePlaceholder, byteRangePlaceholder, byteRangePlaceholder],
|
||||||
Contents: Buffer.from(String.fromCharCode(0).repeat(signatureLength)),
|
Contents: Buffer.from(String.fromCharCode(0).repeat(signatureLength)),
|
||||||
@ -47,11 +49,10 @@ const pdfkitAddPlaceholder = ({
|
|||||||
// eslint-disable-line no-new-wrappers
|
// eslint-disable-line no-new-wrappers
|
||||||
Name: new String(name),
|
Name: new String(name),
|
||||||
// eslint-disable-line no-new-wrappers
|
// eslint-disable-line no-new-wrappers
|
||||||
Location: new String(location) // eslint-disable-line no-new-wrappers
|
Location: new String(location), // eslint-disable-line no-new-wrappers
|
||||||
|
|
||||||
}); // Check if pdf already contains acroform field
|
}); // Check if pdf already contains acroform field
|
||||||
|
|
||||||
const acroFormPosition = pdfBuffer.lastIndexOf('/Type /AcroForm');
|
const acroFormPosition = pdfBuffer.lastIndexOf("/Type /AcroForm");
|
||||||
const isAcroFormExists = acroFormPosition !== -1;
|
const isAcroFormExists = acroFormPosition !== -1;
|
||||||
let fieldIds = [];
|
let fieldIds = [];
|
||||||
let acroFormId;
|
let acroFormId;
|
||||||
@ -65,13 +66,13 @@ const pdfkitAddPlaceholder = ({
|
|||||||
// (generally it's 2 or 3, but I'm giving a big space though)
|
// (generally it's 2 or 3, but I'm giving a big space though)
|
||||||
|
|
||||||
const maxAcroFormIdLength = 12;
|
const maxAcroFormIdLength = 12;
|
||||||
let foundAcroFormId = '';
|
let foundAcroFormId = "";
|
||||||
let index = charsUntilIdEnd + 1;
|
let index = charsUntilIdEnd + 1;
|
||||||
|
|
||||||
for (index; index < charsUntilIdEnd + maxAcroFormIdLength; index += 1) {
|
for (index; index < charsUntilIdEnd + maxAcroFormIdLength; index += 1) {
|
||||||
const acroFormIdString = pdfBuffer.slice(acroFormPosition - index, acroFormIdEnd).toString();
|
const acroFormIdString = pdfBuffer.slice(acroFormPosition - index, acroFormIdEnd).toString();
|
||||||
|
|
||||||
if (acroFormIdString[0] === '\n') {
|
if (acroFormIdString[0] === "\n") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,25 +81,27 @@ const pdfkitAddPlaceholder = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pdfSlice = pdfBuffer.slice(acroFormStart);
|
const pdfSlice = pdfBuffer.slice(acroFormStart);
|
||||||
const acroForm = pdfSlice.slice(0, pdfSlice.indexOf('endobj')).toString();
|
const acroForm = pdfSlice.slice(0, pdfSlice.indexOf("endobj")).toString();
|
||||||
acroFormId = parseInt(foundAcroFormId);
|
acroFormId = parseInt(foundAcroFormId);
|
||||||
const acroFormFields = acroForm.slice(acroForm.indexOf('/Fields [') + 9, acroForm.indexOf(']'));
|
const acroFormFields = acroForm.slice(acroForm.indexOf("/Fields [") + 9, acroForm.indexOf("]"));
|
||||||
fieldIds = acroFormFields.split(' ').filter((element, i) => i % 3 === 0).map(fieldId => new _pdfkitReferenceMock.default(fieldId));
|
fieldIds = acroFormFields
|
||||||
|
.split(" ")
|
||||||
|
.filter((element, i) => i % 3 === 0)
|
||||||
|
.map((fieldId) => new _pdfkitReferenceMock.default(fieldId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const signatureName = 'Signature'; // Generate signature annotation widget
|
const signatureName = "Signature"; // Generate signature annotation widget
|
||||||
|
|
||||||
const widget = pdf.ref({
|
const widget = pdf.ref({
|
||||||
Type: 'Annot',
|
Type: "Annot",
|
||||||
Subtype: 'Widget',
|
Subtype: "Widget",
|
||||||
FT: 'Sig',
|
FT: "Sig",
|
||||||
Rect: [0, 0, 0, 0],
|
Rect: [0, 0, 0, 0],
|
||||||
V: signature,
|
V: signature,
|
||||||
T: new String(signatureName + (fieldIds.length + 1)),
|
T: new String(signatureName + (fieldIds.length + 1)),
|
||||||
// eslint-disable-line no-new-wrappers
|
// eslint-disable-line no-new-wrappers
|
||||||
F: 4,
|
F: 4,
|
||||||
P: pdf.page.dictionary // eslint-disable-line no-underscore-dangle
|
P: pdf.page.dictionary, // eslint-disable-line no-underscore-dangle
|
||||||
|
|
||||||
});
|
});
|
||||||
pdf.page.dictionary.data.Annots = [widget]; // Include the widget in a page
|
pdf.page.dictionary.data.Annots = [widget]; // Include the widget in a page
|
||||||
|
|
||||||
@ -107,27 +110,30 @@ const pdfkitAddPlaceholder = ({
|
|||||||
if (!isAcroFormExists) {
|
if (!isAcroFormExists) {
|
||||||
// Create a form (with the widget) and link in the _root
|
// Create a form (with the widget) and link in the _root
|
||||||
form = pdf.ref({
|
form = pdf.ref({
|
||||||
Type: 'AcroForm',
|
Type: "AcroForm",
|
||||||
SigFlags: 3,
|
SigFlags: 3,
|
||||||
Fields: [...fieldIds, widget]
|
Fields: [...fieldIds, widget],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Use existing acroform and extend the fields with newly created widgets
|
// Use existing acroform and extend the fields with newly created widgets
|
||||||
form = pdf.ref({
|
form = pdf.ref(
|
||||||
Type: 'AcroForm',
|
{
|
||||||
SigFlags: 3,
|
Type: "AcroForm",
|
||||||
Fields: [...fieldIds, widget]
|
SigFlags: 3,
|
||||||
}, acroFormId);
|
Fields: [...fieldIds, widget],
|
||||||
|
},
|
||||||
|
acroFormId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pdf._root.data.AcroForm = form;
|
pdf._root.data.AcroForm = form;
|
||||||
return {
|
return {
|
||||||
signature,
|
signature,
|
||||||
form,
|
form,
|
||||||
widget
|
widget,
|
||||||
};
|
};
|
||||||
/* eslint-enable no-underscore-dangle,no-param-reassign */
|
/* eslint-enable no-underscore-dangle,no-param-reassign */
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = pdfkitAddPlaceholder;
|
var _default = pdfkitAddPlaceholder;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _abstract_reference = _interopRequireDefault(require("./pdfkit/abstract_reference"));
|
var _abstract_reference = _interopRequireDefault(require("./pdfkit/abstract_reference"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
class PDFKitReferenceMock extends _abstract_reference.default {
|
class PDFKitReferenceMock extends _abstract_reference.default {
|
||||||
constructor(index, additionalData = undefined) {
|
constructor(index, additionalData = undefined) {
|
||||||
super();
|
super();
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
|
||||||
if (typeof additionalData !== 'undefined') {
|
if (typeof additionalData !== "undefined") {
|
||||||
Object.assign(this, additionalData);
|
Object.assign(this, additionalData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,8 +24,7 @@ class PDFKitReferenceMock extends _abstract_reference.default {
|
|||||||
toString() {
|
toString() {
|
||||||
return `${this.index} 0 R`;
|
return `${this.index} 0 R`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _default = PDFKitReferenceMock;
|
var _default = PDFKitReferenceMock;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
@ -9,7 +9,9 @@ var _findObject = _interopRequireDefault(require("./findObject"));
|
|||||||
|
|
||||||
var _getIndexFromRef = _interopRequireDefault(require("./getIndexFromRef"));
|
var _getIndexFromRef = _interopRequireDefault(require("./getIndexFromRef"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const createBufferPageWithAnnotation = (pdf, info, pagesRef, widget) => {
|
const createBufferPageWithAnnotation = (pdf, info, pagesRef, widget) => {
|
||||||
const pagesDictionary = (0, _findObject.default)(pdf, info.xref, pagesRef).toString(); // Extend page dictionary with newly created annotations
|
const pagesDictionary = (0, _findObject.default)(pdf, info.xref, pagesRef).toString(); // Extend page dictionary with newly created annotations
|
||||||
@ -17,16 +19,16 @@ const createBufferPageWithAnnotation = (pdf, info, pagesRef, widget) => {
|
|||||||
let annotsStart;
|
let annotsStart;
|
||||||
let annotsEnd;
|
let annotsEnd;
|
||||||
let annots;
|
let annots;
|
||||||
annotsStart = pagesDictionary.indexOf('/Annots');
|
annotsStart = pagesDictionary.indexOf("/Annots");
|
||||||
|
|
||||||
if (annotsStart > -1) {
|
if (annotsStart > -1) {
|
||||||
annotsEnd = pagesDictionary.indexOf(']', annotsStart);
|
annotsEnd = pagesDictionary.indexOf("]", annotsStart);
|
||||||
annots = pagesDictionary.substr(annotsStart, annotsEnd + 1 - annotsStart);
|
annots = pagesDictionary.substr(annotsStart, annotsEnd + 1 - annotsStart);
|
||||||
annots = annots.substr(0, annots.length - 1); // remove the trailing ]
|
annots = annots.substr(0, annots.length - 1); // remove the trailing ]
|
||||||
} else {
|
} else {
|
||||||
annotsStart = pagesDictionary.length;
|
annotsStart = pagesDictionary.length;
|
||||||
annotsEnd = pagesDictionary.length;
|
annotsEnd = pagesDictionary.length;
|
||||||
annots = '/Annots [';
|
annots = "/Annots [";
|
||||||
}
|
}
|
||||||
|
|
||||||
const pagesDictionaryIndex = (0, _getIndexFromRef.default)(info.xref, pagesRef);
|
const pagesDictionaryIndex = (0, _getIndexFromRef.default)(info.xref, pagesRef);
|
||||||
@ -34,14 +36,19 @@ const createBufferPageWithAnnotation = (pdf, info, pagesRef, widget) => {
|
|||||||
annots = `${annots} ${widgetValue}]`; // add the trailing ] back
|
annots = `${annots} ${widgetValue}]`; // add the trailing ] back
|
||||||
|
|
||||||
const preAnnots = pagesDictionary.substr(0, annotsStart);
|
const preAnnots = pagesDictionary.substr(0, annotsStart);
|
||||||
let postAnnots = '';
|
let postAnnots = "";
|
||||||
|
|
||||||
if (pagesDictionary.length > annotsEnd) {
|
if (pagesDictionary.length > annotsEnd) {
|
||||||
postAnnots = pagesDictionary.substr(annotsEnd + 1);
|
postAnnots = pagesDictionary.substr(annotsEnd + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Buffer.concat([Buffer.from(`${pagesDictionaryIndex} 0 obj\n`), Buffer.from('<<\n'), Buffer.from(`${preAnnots + annots + postAnnots}\n`), Buffer.from('\n>>\nendobj\n')]);
|
return Buffer.concat([
|
||||||
|
Buffer.from(`${pagesDictionaryIndex} 0 obj\n`),
|
||||||
|
Buffer.from("<<\n"),
|
||||||
|
Buffer.from(`${preAnnots + annots + postAnnots}\n`),
|
||||||
|
Buffer.from("\n>>\nendobj\n"),
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = createBufferPageWithAnnotation;
|
var _default = createBufferPageWithAnnotation;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,18 +1,26 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _getIndexFromRef = _interopRequireDefault(require("./getIndexFromRef"));
|
var _getIndexFromRef = _interopRequireDefault(require("./getIndexFromRef"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const createBufferRootWithAcroform = (pdf, info, form) => {
|
const createBufferRootWithAcroform = (pdf, info, form) => {
|
||||||
const rootIndex = (0, _getIndexFromRef.default)(info.xref, info.rootRef);
|
const rootIndex = (0, _getIndexFromRef.default)(info.xref, info.rootRef);
|
||||||
return Buffer.concat([Buffer.from(`${rootIndex} 0 obj\n`), Buffer.from('<<\n'), Buffer.from(`${info.root}\n`), Buffer.from(`/AcroForm ${form}`), Buffer.from('\n>>\nendobj\n')]);
|
return Buffer.concat([
|
||||||
|
Buffer.from(`${rootIndex} 0 obj\n`),
|
||||||
|
Buffer.from("<<\n"),
|
||||||
|
Buffer.from(`${info.root}\n`),
|
||||||
|
Buffer.from(`/AcroForm ${form}`),
|
||||||
|
Buffer.from("\n>>\nendobj\n"),
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = createBufferRootWithAcroform;
|
var _default = createBufferRootWithAcroform;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,21 +1,35 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
const createBufferTrailer = (pdf, info, addedReferences) => {
|
const createBufferTrailer = (pdf, info, addedReferences) => {
|
||||||
let rows = [];
|
let rows = [];
|
||||||
rows[0] = '0000000000 65535 f '; // info.xref.tableRows[0];
|
rows[0] = "0000000000 65535 f "; // info.xref.tableRows[0];
|
||||||
|
|
||||||
addedReferences.forEach((offset, index) => {
|
addedReferences.forEach((offset, index) => {
|
||||||
const paddedOffset = `0000000000${offset}`.slice(-10);
|
const paddedOffset = `0000000000${offset}`.slice(-10);
|
||||||
rows[index + 1] = `${index} 1\n${paddedOffset} 00000 n `;
|
rows[index + 1] = `${index} 1\n${paddedOffset} 00000 n `;
|
||||||
});
|
});
|
||||||
rows = rows.filter(row => row !== undefined);
|
rows = rows.filter((row) => row !== undefined);
|
||||||
return Buffer.concat([Buffer.from('xref\n'), Buffer.from(`${info.xref.startingIndex} 1\n`), Buffer.from(rows.join('\n')), Buffer.from('\ntrailer\n'), Buffer.from('<<\n'), Buffer.from(`/Size ${info.xref.maxIndex + 1}\n`), Buffer.from(`/Root ${info.rootRef}\n`), Buffer.from(info.infoRef ? `/Info ${info.infoRef}\n` : ''), Buffer.from(`/Prev ${info.xRefPosition}\n`), Buffer.from('>>\n'), Buffer.from('startxref\n'), Buffer.from(`${pdf.length}\n`), Buffer.from('%%EOF')]);
|
return Buffer.concat([
|
||||||
|
Buffer.from("xref\n"),
|
||||||
|
Buffer.from(`${info.xref.startingIndex} 1\n`),
|
||||||
|
Buffer.from(rows.join("\n")),
|
||||||
|
Buffer.from("\ntrailer\n"),
|
||||||
|
Buffer.from("<<\n"),
|
||||||
|
Buffer.from(`/Size ${info.xref.maxIndex + 1}\n`),
|
||||||
|
Buffer.from(`/Root ${info.rootRef}\n`),
|
||||||
|
Buffer.from(info.infoRef ? `/Info ${info.infoRef}\n` : ""),
|
||||||
|
Buffer.from(`/Prev ${info.xRefPosition}\n`),
|
||||||
|
Buffer.from(">>\n"),
|
||||||
|
Buffer.from("startxref\n"),
|
||||||
|
Buffer.from(`${pdf.length}\n`),
|
||||||
|
Buffer.from("%%EOF"),
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = createBufferTrailer;
|
var _default = createBufferTrailer;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _getIndexFromRef = _interopRequireDefault(require("./getIndexFromRef"));
|
var _getIndexFromRef = _interopRequireDefault(require("./getIndexFromRef"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Buffer} pdf
|
* @param {Buffer} pdf
|
||||||
@ -18,12 +20,12 @@ const findObject = (pdf, refTable, ref) => {
|
|||||||
const index = (0, _getIndexFromRef.default)(refTable, ref);
|
const index = (0, _getIndexFromRef.default)(refTable, ref);
|
||||||
const offset = refTable.offsets.get(index);
|
const offset = refTable.offsets.get(index);
|
||||||
let slice = pdf.slice(offset);
|
let slice = pdf.slice(offset);
|
||||||
slice = slice.slice(0, slice.indexOf('endobj', 'utf8')); // FIXME: What if it is a stream?
|
slice = slice.slice(0, slice.indexOf("endobj", "utf8")); // FIXME: What if it is a stream?
|
||||||
|
|
||||||
slice = slice.slice(slice.indexOf('<<', 'utf8') + 2);
|
slice = slice.slice(slice.indexOf("<<", "utf8") + 2);
|
||||||
slice = slice.slice(0, slice.lastIndexOf('>>', 'utf8'));
|
slice = slice.slice(0, slice.lastIndexOf(">>", "utf8"));
|
||||||
return slice;
|
return slice;
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = findObject;
|
var _default = findObject;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _SignPdfError = _interopRequireDefault(require("../../SignPdfError"));
|
var _SignPdfError = _interopRequireDefault(require("../../SignPdfError"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} refTable
|
* @param {object} refTable
|
||||||
@ -15,7 +17,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
const getIndexFromRef = (refTable, ref) => {
|
const getIndexFromRef = (refTable, ref) => {
|
||||||
let [index] = ref.split(' ');
|
let [index] = ref.split(" ");
|
||||||
index = parseInt(index);
|
index = parseInt(index);
|
||||||
|
|
||||||
if (!refTable.offsets.has(index)) {
|
if (!refTable.offsets.has(index)) {
|
||||||
@ -26,4 +28,4 @@ const getIndexFromRef = (refTable, ref) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var _default = getIndexFromRef;
|
var _default = getIndexFromRef;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = getPageRef;
|
exports.default = getPageRef;
|
||||||
|
|
||||||
@ -9,7 +9,9 @@ var _getPagesDictionaryRef = _interopRequireDefault(require("./getPagesDictionar
|
|||||||
|
|
||||||
var _findObject = _interopRequireDefault(require("./findObject"));
|
var _findObject = _interopRequireDefault(require("./findObject"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the reference to a page.
|
* Finds the reference to a page.
|
||||||
@ -20,10 +22,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|||||||
function getPageRef(pdfBuffer, info) {
|
function getPageRef(pdfBuffer, info) {
|
||||||
const pagesRef = (0, _getPagesDictionaryRef.default)(info);
|
const pagesRef = (0, _getPagesDictionaryRef.default)(info);
|
||||||
const pagesDictionary = (0, _findObject.default)(pdfBuffer, info.xref, pagesRef);
|
const pagesDictionary = (0, _findObject.default)(pdfBuffer, info.xref, pagesRef);
|
||||||
const kidsPosition = pagesDictionary.indexOf('/Kids');
|
const kidsPosition = pagesDictionary.indexOf("/Kids");
|
||||||
const kidsStart = pagesDictionary.indexOf('[', kidsPosition) + 1;
|
const kidsStart = pagesDictionary.indexOf("[", kidsPosition) + 1;
|
||||||
const kidsEnd = pagesDictionary.indexOf(']', kidsPosition);
|
const kidsEnd = pagesDictionary.indexOf("]", kidsPosition);
|
||||||
const pages = pagesDictionary.slice(kidsStart, kidsEnd).toString();
|
const pages = pagesDictionary.slice(kidsStart, kidsEnd).toString();
|
||||||
const split = pages.trim().split(' ', 3);
|
const split = pages.trim().split(" ", 3);
|
||||||
return `${split[0]} ${split[1]} ${split[2]}`;
|
return `${split[0]} ${split[1]} ${split[2]}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = getPagesDictionaryRef;
|
exports.default = getPagesDictionaryRef;
|
||||||
|
|
||||||
var _SignPdfError = _interopRequireDefault(require("../../SignPdfError"));
|
var _SignPdfError = _interopRequireDefault(require("../../SignPdfError"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} info As extracted from readRef()
|
* @param {Object} info As extracted from readRef()
|
||||||
@ -17,8 +19,11 @@ function getPagesDictionaryRef(info) {
|
|||||||
const match = pagesRefRegex.exec(info.root);
|
const match = pagesRefRegex.exec(info.root);
|
||||||
|
|
||||||
if (match === null) {
|
if (match === null) {
|
||||||
throw new _SignPdfError.default('Failed to find the pages descriptor. This is probably a problem in node-signpdf.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
"Failed to find the pages descriptor. This is probably a problem in node-signpdf.",
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return match[1];
|
return match[1];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
@ -27,12 +27,14 @@ var _createBufferPageWithAnnotation = _interopRequireDefault(require("./createBu
|
|||||||
|
|
||||||
var _createBufferTrailer = _interopRequireDefault(require("./createBufferTrailer"));
|
var _createBufferTrailer = _interopRequireDefault(require("./createBufferTrailer"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const isContainBufferRootWithAcroform = pdf => {
|
const isContainBufferRootWithAcroform = (pdf) => {
|
||||||
const bufferRootWithAcroformRefRegex = /\/AcroForm\s+(\d+\s\d+\sR)/g;
|
const bufferRootWithAcroformRefRegex = /\/AcroForm\s+(\d+\s\d+\sR)/g;
|
||||||
const match = bufferRootWithAcroformRefRegex.exec(pdf.toString());
|
const match = bufferRootWithAcroformRefRegex.exec(pdf.toString());
|
||||||
return match != null && match[1] != null && match[1] !== '';
|
return match != null && match[1] != null && match[1] !== "";
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Adds a signature placeholder to a PDF Buffer.
|
* Adds a signature placeholder to a PDF Buffer.
|
||||||
@ -44,15 +46,14 @@ const isContainBufferRootWithAcroform = pdf => {
|
|||||||
* not only on a freshly created through PDFKit one.
|
* not only on a freshly created through PDFKit one.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
const plainAddPlaceholder = ({
|
const plainAddPlaceholder = ({
|
||||||
pdfBuffer,
|
pdfBuffer,
|
||||||
reason,
|
reason,
|
||||||
contactInfo = 'emailfromp1289@gmail.com',
|
contactInfo = "emailfromp1289@gmail.com",
|
||||||
name = 'Name from p12',
|
name = "Name from p12",
|
||||||
location = 'Location from p12',
|
location = "Location from p12",
|
||||||
signatureLength = _const.DEFAULT_SIGNATURE_LENGTH,
|
signatureLength = _const.DEFAULT_SIGNATURE_LENGTH,
|
||||||
subFilter = _const.SUBFILTER_ADOBE_PKCS7_DETACHED
|
subFilter = _const.SUBFILTER_ADOBE_PKCS7_DETACHED,
|
||||||
}) => {
|
}) => {
|
||||||
let pdf = (0, _removeTrailingNewLine.default)(pdfBuffer);
|
let pdf = (0, _removeTrailingNewLine.default)(pdfBuffer);
|
||||||
const info = (0, _readPdf.default)(pdf);
|
const info = (0, _readPdf.default)(pdf);
|
||||||
@ -65,24 +66,27 @@ const plainAddPlaceholder = ({
|
|||||||
const index = additionalIndex != null ? additionalIndex : info.xref.maxIndex;
|
const index = additionalIndex != null ? additionalIndex : info.xref.maxIndex;
|
||||||
addedReferences.set(index, pdf.length + 1); // + 1 new line
|
addedReferences.set(index, pdf.length + 1); // + 1 new line
|
||||||
|
|
||||||
pdf = Buffer.concat([pdf, Buffer.from('\n'), Buffer.from(`${index} 0 obj\n`), Buffer.from(_pdfobject.default.convert(input)), Buffer.from('\nendobj\n')]);
|
pdf = Buffer.concat([
|
||||||
|
pdf,
|
||||||
|
Buffer.from("\n"),
|
||||||
|
Buffer.from(`${index} 0 obj\n`),
|
||||||
|
Buffer.from(_pdfobject.default.convert(input)),
|
||||||
|
Buffer.from("\nendobj\n"),
|
||||||
|
]);
|
||||||
return new _pdfkitReferenceMock.default(info.xref.maxIndex);
|
return new _pdfkitReferenceMock.default(info.xref.maxIndex);
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
dictionary: new _pdfkitReferenceMock.default(pageIndex, {
|
dictionary: new _pdfkitReferenceMock.default(pageIndex, {
|
||||||
data: {
|
data: {
|
||||||
Annots: []
|
Annots: [],
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
_root: {
|
_root: {
|
||||||
data: {}
|
data: {},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const {
|
const { form, widget } = (0, _pdfkitAddPlaceholder.default)({
|
||||||
form,
|
|
||||||
widget
|
|
||||||
} = (0, _pdfkitAddPlaceholder.default)({
|
|
||||||
pdf: pdfKitMock,
|
pdf: pdfKitMock,
|
||||||
pdfBuffer,
|
pdfBuffer,
|
||||||
reason,
|
reason,
|
||||||
@ -90,20 +94,32 @@ const plainAddPlaceholder = ({
|
|||||||
name,
|
name,
|
||||||
location,
|
location,
|
||||||
signatureLength,
|
signatureLength,
|
||||||
subFilter
|
subFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isContainBufferRootWithAcroform(pdf)) {
|
if (!isContainBufferRootWithAcroform(pdf)) {
|
||||||
const rootIndex = (0, _getIndexFromRef.default)(info.xref, info.rootRef);
|
const rootIndex = (0, _getIndexFromRef.default)(info.xref, info.rootRef);
|
||||||
addedReferences.set(rootIndex, pdf.length + 1);
|
addedReferences.set(rootIndex, pdf.length + 1);
|
||||||
pdf = Buffer.concat([pdf, Buffer.from('\n'), (0, _createBufferRootWithAcroform.default)(pdf, info, form)]);
|
pdf = Buffer.concat([
|
||||||
|
pdf,
|
||||||
|
Buffer.from("\n"),
|
||||||
|
(0, _createBufferRootWithAcroform.default)(pdf, info, form),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
addedReferences.set(pageIndex, pdf.length + 1);
|
addedReferences.set(pageIndex, pdf.length + 1);
|
||||||
pdf = Buffer.concat([pdf, Buffer.from('\n'), (0, _createBufferPageWithAnnotation.default)(pdf, info, pageRef, widget)]);
|
pdf = Buffer.concat([
|
||||||
pdf = Buffer.concat([pdf, Buffer.from('\n'), (0, _createBufferTrailer.default)(pdf, info, addedReferences)]);
|
pdf,
|
||||||
|
Buffer.from("\n"),
|
||||||
|
(0, _createBufferPageWithAnnotation.default)(pdf, info, pageRef, widget),
|
||||||
|
]);
|
||||||
|
pdf = Buffer.concat([
|
||||||
|
pdf,
|
||||||
|
Buffer.from("\n"),
|
||||||
|
(0, _createBufferTrailer.default)(pdf, info, addedReferences),
|
||||||
|
]);
|
||||||
return pdf;
|
return pdf;
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = plainAddPlaceholder;
|
var _default = plainAddPlaceholder;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
@ -9,7 +9,9 @@ var _readRefTable = _interopRequireDefault(require("./readRefTable"));
|
|||||||
|
|
||||||
var _findObject = _interopRequireDefault(require("./findObject"));
|
var _findObject = _interopRequireDefault(require("./findObject"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const getValue = (trailer, key) => {
|
const getValue = (trailer, key) => {
|
||||||
let index = trailer.indexOf(key);
|
let index = trailer.indexOf(key);
|
||||||
@ -19,13 +21,16 @@ const getValue = (trailer, key) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const slice = trailer.slice(index);
|
const slice = trailer.slice(index);
|
||||||
index = slice.indexOf('/', 1);
|
index = slice.indexOf("/", 1);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
index = slice.indexOf('>', 1);
|
index = slice.indexOf(">", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return slice.slice(key.length + 1, index).toString().trim(); // key + at least one space
|
return slice
|
||||||
|
.slice(key.length + 1, index)
|
||||||
|
.toString()
|
||||||
|
.trim(); // key + at least one space
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Simplified parsing of a PDF Buffer.
|
* Simplified parsing of a PDF Buffer.
|
||||||
@ -36,18 +41,17 @@ const getValue = (trailer, key) => {
|
|||||||
* @param {Buffer} pdfBuffer
|
* @param {Buffer} pdfBuffer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const readPdf = (pdfBuffer) => {
|
||||||
const readPdf = pdfBuffer => {
|
|
||||||
// Extract the trailer dictionary.
|
// Extract the trailer dictionary.
|
||||||
const trailerStart = pdfBuffer.lastIndexOf('trailer'); // The trailer is followed by xref. Then an EOF. EOF's length is 6 characters.
|
const trailerStart = pdfBuffer.lastIndexOf("trailer"); // The trailer is followed by xref. Then an EOF. EOF's length is 6 characters.
|
||||||
|
|
||||||
const trailer = pdfBuffer.slice(trailerStart, pdfBuffer.length - 6);
|
const trailer = pdfBuffer.slice(trailerStart, pdfBuffer.length - 6);
|
||||||
let xRefPosition = trailer.slice(trailer.lastIndexOf('startxref') + 10).toString();
|
let xRefPosition = trailer.slice(trailer.lastIndexOf("startxref") + 10).toString();
|
||||||
xRefPosition = parseInt(xRefPosition);
|
xRefPosition = parseInt(xRefPosition);
|
||||||
const refTable = (0, _readRefTable.default)(pdfBuffer);
|
const refTable = (0, _readRefTable.default)(pdfBuffer);
|
||||||
const rootRef = getValue(trailer, '/Root');
|
const rootRef = getValue(trailer, "/Root");
|
||||||
const root = (0, _findObject.default)(pdfBuffer, refTable, rootRef).toString();
|
const root = (0, _findObject.default)(pdfBuffer, refTable, rootRef).toString();
|
||||||
const infoRef = getValue(trailer, '/Info');
|
const infoRef = getValue(trailer, "/Info");
|
||||||
return {
|
return {
|
||||||
xref: refTable,
|
xref: refTable,
|
||||||
rootRef,
|
rootRef,
|
||||||
@ -55,9 +59,9 @@ const readPdf = pdfBuffer => {
|
|||||||
infoRef,
|
infoRef,
|
||||||
trailerStart,
|
trailerStart,
|
||||||
previousXrefs: [],
|
previousXrefs: [],
|
||||||
xRefPosition
|
xRefPosition,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = readPdf;
|
var _default = readPdf;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.getXref = exports.getLastTrailerPosition = exports.getFullXrefTable = exports.default = void 0;
|
exports.getXref = exports.getLastTrailerPosition = exports.getFullXrefTable = exports.default = void 0;
|
||||||
|
|
||||||
@ -9,12 +9,14 @@ var _SignPdfError = _interopRequireDefault(require("../../SignPdfError"));
|
|||||||
|
|
||||||
var _xrefToRefMap = _interopRequireDefault(require("./xrefToRefMap"));
|
var _xrefToRefMap = _interopRequireDefault(require("./xrefToRefMap"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const getLastTrailerPosition = pdf => {
|
const getLastTrailerPosition = (pdf) => {
|
||||||
const trailerStart = pdf.lastIndexOf(Buffer.from('trailer', 'utf8'));
|
const trailerStart = pdf.lastIndexOf(Buffer.from("trailer", "utf8"));
|
||||||
const trailer = pdf.slice(trailerStart, pdf.length - 6);
|
const trailer = pdf.slice(trailerStart, pdf.length - 6);
|
||||||
const xRefPosition = trailer.slice(trailer.lastIndexOf(Buffer.from('startxref', 'utf8')) + 10).toString();
|
const xRefPosition = trailer.slice(trailer.lastIndexOf(Buffer.from("startxref", "utf8")) + 10).toString();
|
||||||
return parseInt(xRefPosition);
|
return parseInt(xRefPosition);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,47 +25,56 @@ exports.getLastTrailerPosition = getLastTrailerPosition;
|
|||||||
const getXref = (pdf, position) => {
|
const getXref = (pdf, position) => {
|
||||||
let refTable = pdf.slice(position); // slice starting from where xref starts
|
let refTable = pdf.slice(position); // slice starting from where xref starts
|
||||||
|
|
||||||
const realPosition = refTable.indexOf(Buffer.from('xref', 'utf8'));
|
const realPosition = refTable.indexOf(Buffer.from("xref", "utf8"));
|
||||||
|
|
||||||
if (realPosition === -1) {
|
if (realPosition === -1) {
|
||||||
throw new _SignPdfError.default(`Could not find xref anywhere at or after ${position}.`, _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
`Could not find xref anywhere at or after ${position}.`,
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (realPosition > 0) {
|
if (realPosition > 0) {
|
||||||
const prefix = refTable.slice(0, realPosition);
|
const prefix = refTable.slice(0, realPosition);
|
||||||
|
|
||||||
if (prefix.toString().replace(/\s*/g, '') !== '') {
|
if (prefix.toString().replace(/\s*/g, "") !== "") {
|
||||||
throw new _SignPdfError.default(`Expected xref at ${position} but found other content.`, _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
`Expected xref at ${position} but found other content.`,
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextEofPosition = refTable.indexOf(Buffer.from('%%EOF', 'utf8'));
|
const nextEofPosition = refTable.indexOf(Buffer.from("%%EOF", "utf8"));
|
||||||
|
|
||||||
if (nextEofPosition === -1) {
|
if (nextEofPosition === -1) {
|
||||||
throw new _SignPdfError.default('Expected EOF after xref and trailer but could not find one.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
"Expected EOF after xref and trailer but could not find one.",
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
refTable = refTable.slice(0, nextEofPosition);
|
refTable = refTable.slice(0, nextEofPosition);
|
||||||
refTable = refTable.slice(realPosition + 4); // move ahead with the "xref"
|
refTable = refTable.slice(realPosition + 4); // move ahead with the "xref"
|
||||||
|
|
||||||
refTable = refTable.slice(refTable.indexOf('\n') + 1); // move after the next new line
|
refTable = refTable.slice(refTable.indexOf("\n") + 1); // move after the next new line
|
||||||
// extract the size
|
// extract the size
|
||||||
|
|
||||||
let size = refTable.toString().split('/Size')[1];
|
let size = refTable.toString().split("/Size")[1];
|
||||||
|
|
||||||
if (!size) {
|
if (!size) {
|
||||||
throw new _SignPdfError.default('Size not found in xref table.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default("Size not found in xref table.", _SignPdfError.default.TYPE_PARSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
size = /^\s*(\d+)/.exec(size);
|
size = /^\s*(\d+)/.exec(size);
|
||||||
|
|
||||||
if (size === null) {
|
if (size === null) {
|
||||||
throw new _SignPdfError.default('Failed to parse size of xref table.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default("Failed to parse size of xref table.", _SignPdfError.default.TYPE_PARSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
size = parseInt(size[1]);
|
size = parseInt(size[1]);
|
||||||
const [objects, infos] = refTable.toString().split('trailer');
|
const [objects, infos] = refTable.toString().split("trailer");
|
||||||
const isContainingPrev = infos.split('/Prev')[1] != null;
|
const isContainingPrev = infos.split("/Prev")[1] != null;
|
||||||
let prev;
|
let prev;
|
||||||
|
|
||||||
if (isContainingPrev) {
|
if (isContainingPrev) {
|
||||||
@ -77,13 +88,13 @@ const getXref = (pdf, position) => {
|
|||||||
return {
|
return {
|
||||||
size,
|
size,
|
||||||
prev,
|
prev,
|
||||||
xRefContent
|
xRefContent,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getXref = getXref;
|
exports.getXref = getXref;
|
||||||
|
|
||||||
const getFullXrefTable = pdf => {
|
const getFullXrefTable = (pdf) => {
|
||||||
const lastTrailerPosition = getLastTrailerPosition(pdf);
|
const lastTrailerPosition = getLastTrailerPosition(pdf);
|
||||||
const lastXrefTable = getXref(pdf, lastTrailerPosition);
|
const lastXrefTable = getXref(pdf, lastTrailerPosition);
|
||||||
|
|
||||||
@ -101,19 +112,18 @@ const getFullXrefTable = pdf => {
|
|||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
exports.getFullXrefTable = getFullXrefTable;
|
exports.getFullXrefTable = getFullXrefTable;
|
||||||
|
|
||||||
const readRefTable = pdf => {
|
const readRefTable = (pdf) => {
|
||||||
const fullXrefTable = getFullXrefTable(pdf);
|
const fullXrefTable = getFullXrefTable(pdf);
|
||||||
const startingIndex = 0;
|
const startingIndex = 0;
|
||||||
const maxIndex = Math.max(...fullXrefTable.keys());
|
const maxIndex = Math.max(...fullXrefTable.keys());
|
||||||
return {
|
return {
|
||||||
startingIndex,
|
startingIndex,
|
||||||
maxIndex,
|
maxIndex,
|
||||||
offsets: fullXrefTable
|
offsets: fullXrefTable,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = readRefTable;
|
var _default = readRefTable;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _SignPdfError = _interopRequireDefault(require("../../SignPdfError"));
|
var _SignPdfError = _interopRequireDefault(require("../../SignPdfError"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const xrefToRefMap = xrefString => {
|
const xrefToRefMap = (xrefString) => {
|
||||||
const lines = xrefString.split('\n').filter(l => l !== '');
|
const lines = xrefString.split("\n").filter((l) => l !== "");
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let expectedLines = 0;
|
let expectedLines = 0;
|
||||||
const xref = new Map();
|
const xref = new Map();
|
||||||
lines.forEach(line => {
|
lines.forEach((line) => {
|
||||||
const split = line.split(' ');
|
const split = line.split(" ");
|
||||||
|
|
||||||
if (split.length === 2) {
|
if (split.length === 2) {
|
||||||
index = parseInt(split[0]);
|
index = parseInt(split[0]);
|
||||||
@ -24,23 +26,29 @@ const xrefToRefMap = xrefString => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expectedLines <= 0) {
|
if (expectedLines <= 0) {
|
||||||
throw new _SignPdfError.default('Too many lines in xref table.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default("Too many lines in xref table.", _SignPdfError.default.TYPE_PARSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedLines -= 1;
|
expectedLines -= 1;
|
||||||
const [offset,, inUse] = split;
|
const [offset, , inUse] = split;
|
||||||
|
|
||||||
if (inUse.trim() === 'f') {
|
if (inUse.trim() === "f") {
|
||||||
index += 1;
|
index += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inUse.trim() !== 'n') {
|
if (inUse.trim() !== "n") {
|
||||||
throw new _SignPdfError.default(`Unknown in-use flag "${inUse}". Expected "n" or "f".`, _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
`Unknown in-use flag "${inUse}". Expected "n" or "f".`,
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/^\d+$/.test(offset.trim())) {
|
if (!/^\d+$/.test(offset.trim())) {
|
||||||
throw new _SignPdfError.default(`Expected integer offset. Got "${offset}".`, _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
`Expected integer offset. Got "${offset}".`,
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const storeOffset = parseInt(offset.trim());
|
const storeOffset = parseInt(offset.trim());
|
||||||
@ -51,4 +59,4 @@ const xrefToRefMap = xrefString => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var _default = xrefToRefMap;
|
var _default = xrefToRefMap;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
var _SignPdfError = _interopRequireDefault(require("../SignPdfError"));
|
var _SignPdfError = _interopRequireDefault(require("../SignPdfError"));
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
const sliceLastChar = (pdf, character) => {
|
const sliceLastChar = (pdf, character) => {
|
||||||
const lastChar = pdf.slice(pdf.length - 1).toString();
|
const lastChar = pdf.slice(pdf.length - 1).toString();
|
||||||
@ -26,23 +28,25 @@ const sliceLastChar = (pdf, character) => {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const removeTrailingNewLine = (pdf) => {
|
||||||
const removeTrailingNewLine = pdf => {
|
|
||||||
if (!(pdf instanceof Buffer)) {
|
if (!(pdf instanceof Buffer)) {
|
||||||
throw new _SignPdfError.default('PDF expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
|
throw new _SignPdfError.default("PDF expected as Buffer.", _SignPdfError.default.TYPE_INPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = pdf;
|
let output = pdf;
|
||||||
output = sliceLastChar(output, '\n');
|
output = sliceLastChar(output, "\n");
|
||||||
output = sliceLastChar(output, '\r');
|
output = sliceLastChar(output, "\r");
|
||||||
const lastLine = output.slice(output.length - 6).toString();
|
const lastLine = output.slice(output.length - 6).toString();
|
||||||
|
|
||||||
if (lastLine !== '\n%%EOF') {
|
if (lastLine !== "\n%%EOF") {
|
||||||
throw new _SignPdfError.default('A PDF file must end with an EOF line.', _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
"A PDF file must end with an EOF line.",
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
var _default = removeTrailingNewLine;
|
var _default = removeTrailingNewLine;
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
120
packages/signing/node-signpdf/dist/signpdf.js
vendored
120
packages/signing/node-signpdf/dist/signpdf.js
vendored
@ -1,18 +1,18 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
Object.defineProperty(exports, "__esModule", {
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
var _exportNames = {
|
var _exportNames = {
|
||||||
SignPdf: true,
|
SignPdf: true,
|
||||||
SignPdfError: true
|
SignPdfError: true,
|
||||||
};
|
};
|
||||||
exports.SignPdf = void 0;
|
exports.SignPdf = void 0;
|
||||||
Object.defineProperty(exports, "SignPdfError", {
|
Object.defineProperty(exports, "SignPdfError", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _SignPdfError.default;
|
return _SignPdfError.default;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
exports.default = void 0;
|
exports.default = void 0;
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ Object.keys(_helpers).forEach(function (key) {
|
|||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _helpers[key];
|
return _helpers[key];
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,11 +44,13 @@ Object.keys(_const).forEach(function (key) {
|
|||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get: function () {
|
||||||
return _const[key];
|
return _const[key];
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : { default: obj };
|
||||||
|
}
|
||||||
|
|
||||||
class SignPdf {
|
class SignPdf {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -59,77 +61,81 @@ class SignPdf {
|
|||||||
sign(pdfBuffer, p12Buffer, additionalOptions = {}) {
|
sign(pdfBuffer, p12Buffer, additionalOptions = {}) {
|
||||||
const options = {
|
const options = {
|
||||||
asn1StrictParsing: false,
|
asn1StrictParsing: false,
|
||||||
passphrase: '',
|
passphrase: "",
|
||||||
...additionalOptions
|
...additionalOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!(pdfBuffer instanceof Buffer)) {
|
if (!(pdfBuffer instanceof Buffer)) {
|
||||||
throw new _SignPdfError.default('PDF expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
|
throw new _SignPdfError.default("PDF expected as Buffer.", _SignPdfError.default.TYPE_INPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(p12Buffer instanceof Buffer)) {
|
if (!(p12Buffer instanceof Buffer)) {
|
||||||
throw new _SignPdfError.default('p12 certificate expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
|
throw new _SignPdfError.default(
|
||||||
|
"p12 certificate expected as Buffer.",
|
||||||
|
_SignPdfError.default.TYPE_INPUT
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pdf = (0, _helpers.removeTrailingNewLine)(pdfBuffer); // Find the ByteRange placeholder.
|
let pdf = (0, _helpers.removeTrailingNewLine)(pdfBuffer); // Find the ByteRange placeholder.
|
||||||
|
|
||||||
const {
|
const { byteRangePlaceholder } = (0, _helpers.findByteRange)(pdf);
|
||||||
byteRangePlaceholder
|
|
||||||
} = (0, _helpers.findByteRange)(pdf);
|
|
||||||
|
|
||||||
if (!byteRangePlaceholder) {
|
if (!byteRangePlaceholder) {
|
||||||
throw new _SignPdfError.default(`Could not find empty ByteRange placeholder: ${byteRangePlaceholder}`, _SignPdfError.default.TYPE_PARSE);
|
throw new _SignPdfError.default(
|
||||||
|
`Could not find empty ByteRange placeholder: ${byteRangePlaceholder}`,
|
||||||
|
_SignPdfError.default.TYPE_PARSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteRangePos = pdf.indexOf(byteRangePlaceholder); // Calculate the actual ByteRange that needs to replace the placeholder.
|
const byteRangePos = pdf.indexOf(byteRangePlaceholder); // Calculate the actual ByteRange that needs to replace the placeholder.
|
||||||
|
|
||||||
const byteRangeEnd = byteRangePos + byteRangePlaceholder.length;
|
const byteRangeEnd = byteRangePos + byteRangePlaceholder.length;
|
||||||
const contentsTagPos = pdf.indexOf('/Contents ', byteRangeEnd);
|
const contentsTagPos = pdf.indexOf("/Contents ", byteRangeEnd);
|
||||||
const placeholderPos = pdf.indexOf('<', contentsTagPos);
|
const placeholderPos = pdf.indexOf("<", contentsTagPos);
|
||||||
const placeholderEnd = pdf.indexOf('>', placeholderPos);
|
const placeholderEnd = pdf.indexOf(">", placeholderPos);
|
||||||
const placeholderLengthWithBrackets = placeholderEnd + 1 - placeholderPos;
|
const placeholderLengthWithBrackets = placeholderEnd + 1 - placeholderPos;
|
||||||
const placeholderLength = placeholderLengthWithBrackets - 2;
|
const placeholderLength = placeholderLengthWithBrackets - 2;
|
||||||
const byteRange = [0, 0, 0, 0];
|
const byteRange = [0, 0, 0, 0];
|
||||||
byteRange[1] = placeholderPos;
|
byteRange[1] = placeholderPos;
|
||||||
byteRange[2] = byteRange[1] + placeholderLengthWithBrackets;
|
byteRange[2] = byteRange[1] + placeholderLengthWithBrackets;
|
||||||
byteRange[3] = pdf.length - byteRange[2];
|
byteRange[3] = pdf.length - byteRange[2];
|
||||||
let actualByteRange = `/ByteRange [${byteRange.join(' ')}]`;
|
let actualByteRange = `/ByteRange [${byteRange.join(" ")}]`;
|
||||||
actualByteRange += ' '.repeat(byteRangePlaceholder.length - actualByteRange.length); // Replace the /ByteRange placeholder with the actual ByteRange
|
actualByteRange += " ".repeat(byteRangePlaceholder.length - actualByteRange.length); // Replace the /ByteRange placeholder with the actual ByteRange
|
||||||
|
|
||||||
pdf = Buffer.concat([pdf.slice(0, byteRangePos), Buffer.from(actualByteRange), pdf.slice(byteRangeEnd)]); // Remove the placeholder signature
|
pdf = Buffer.concat([pdf.slice(0, byteRangePos), Buffer.from(actualByteRange), pdf.slice(byteRangeEnd)]); // Remove the placeholder signature
|
||||||
|
|
||||||
pdf = Buffer.concat([pdf.slice(0, byteRange[1]), pdf.slice(byteRange[2], byteRange[2] + byteRange[3])]); // Convert Buffer P12 to a forge implementation.
|
pdf = Buffer.concat([pdf.slice(0, byteRange[1]), pdf.slice(byteRange[2], byteRange[2] + byteRange[3])]); // Convert Buffer P12 to a forge implementation.
|
||||||
|
|
||||||
const forgeCert = _nodeForge.default.util.createBuffer(p12Buffer.toString('binary'));
|
const forgeCert = _nodeForge.default.util.createBuffer(p12Buffer.toString("binary"));
|
||||||
|
|
||||||
const p12Asn1 = _nodeForge.default.asn1.fromDer(forgeCert);
|
const p12Asn1 = _nodeForge.default.asn1.fromDer(forgeCert);
|
||||||
|
|
||||||
const p12 = _nodeForge.default.pkcs12.pkcs12FromAsn1(p12Asn1, options.asn1StrictParsing, options.passphrase); // Extract safe bags by type.
|
const p12 = _nodeForge.default.pkcs12.pkcs12FromAsn1(
|
||||||
|
p12Asn1,
|
||||||
|
options.asn1StrictParsing,
|
||||||
|
options.passphrase
|
||||||
|
); // Extract safe bags by type.
|
||||||
// We will need all the certificates and the private key.
|
// We will need all the certificates and the private key.
|
||||||
|
|
||||||
|
|
||||||
const certBags = p12.getBags({
|
const certBags = p12.getBags({
|
||||||
bagType: _nodeForge.default.pki.oids.certBag
|
bagType: _nodeForge.default.pki.oids.certBag,
|
||||||
})[_nodeForge.default.pki.oids.certBag];
|
})[_nodeForge.default.pki.oids.certBag];
|
||||||
|
|
||||||
const keyBags = p12.getBags({
|
const keyBags = p12.getBags({
|
||||||
bagType: _nodeForge.default.pki.oids.pkcs8ShroudedKeyBag
|
bagType: _nodeForge.default.pki.oids.pkcs8ShroudedKeyBag,
|
||||||
})[_nodeForge.default.pki.oids.pkcs8ShroudedKeyBag];
|
})[_nodeForge.default.pki.oids.pkcs8ShroudedKeyBag];
|
||||||
|
|
||||||
const privateKey = keyBags[0].key; // Here comes the actual PKCS#7 signing.
|
const privateKey = keyBags[0].key; // Here comes the actual PKCS#7 signing.
|
||||||
|
|
||||||
const p7 = _nodeForge.default.pkcs7.createSignedData(); // Start off by setting the content.
|
const p7 = _nodeForge.default.pkcs7.createSignedData(); // Start off by setting the content.
|
||||||
|
|
||||||
|
p7.content = _nodeForge.default.util.createBuffer(pdf.toString("binary")); // Then add all the certificates (-cacerts & -clcerts)
|
||||||
p7.content = _nodeForge.default.util.createBuffer(pdf.toString('binary')); // Then add all the certificates (-cacerts & -clcerts)
|
|
||||||
// Keep track of the last found client certificate.
|
// Keep track of the last found client certificate.
|
||||||
// This will be the public key that will be bundled in the signature.
|
// This will be the public key that will be bundled in the signature.
|
||||||
|
|
||||||
let certificate;
|
let certificate;
|
||||||
Object.keys(certBags).forEach(i => {
|
Object.keys(certBags).forEach((i) => {
|
||||||
const {
|
const { publicKey } = certBags[i].cert;
|
||||||
publicKey
|
|
||||||
} = certBags[i].cert;
|
|
||||||
p7.addCertificate(certBags[i].cert); // Try to find the certificate that matches the private key.
|
p7.addCertificate(certBags[i].cert); // Try to find the certificate that matches the private key.
|
||||||
|
|
||||||
if (privateKey.n.compareTo(publicKey.n) === 0 && privateKey.e.compareTo(publicKey.e) === 0) {
|
if (privateKey.n.compareTo(publicKey.n) === 0 && privateKey.e.compareTo(publicKey.e) === 0) {
|
||||||
@ -137,57 +143,65 @@ class SignPdf {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (typeof certificate === 'undefined') {
|
if (typeof certificate === "undefined") {
|
||||||
throw new _SignPdfError.default('Failed to find a certificate that matches the private key.', _SignPdfError.default.TYPE_INPUT);
|
throw new _SignPdfError.default(
|
||||||
|
"Failed to find a certificate that matches the private key.",
|
||||||
|
_SignPdfError.default.TYPE_INPUT
|
||||||
|
);
|
||||||
} // Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
|
} // Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
|
||||||
|
|
||||||
|
|
||||||
p7.addSigner({
|
p7.addSigner({
|
||||||
key: privateKey,
|
key: privateKey,
|
||||||
certificate,
|
certificate,
|
||||||
digestAlgorithm: _nodeForge.default.pki.oids.sha256,
|
digestAlgorithm: _nodeForge.default.pki.oids.sha256,
|
||||||
authenticatedAttributes: [{
|
authenticatedAttributes: [
|
||||||
type: _nodeForge.default.pki.oids.contentType,
|
{
|
||||||
value: _nodeForge.default.pki.oids.data
|
type: _nodeForge.default.pki.oids.contentType,
|
||||||
}, {
|
value: _nodeForge.default.pki.oids.data,
|
||||||
type: _nodeForge.default.pki.oids.signingTime,
|
},
|
||||||
// value can also be auto-populated at signing time
|
{
|
||||||
// We may also support passing this as an option to sign().
|
type: _nodeForge.default.pki.oids.signingTime,
|
||||||
// Would be useful to match the creation time of the document for example.
|
// value can also be auto-populated at signing time
|
||||||
value: new Date()
|
// We may also support passing this as an option to sign().
|
||||||
}, {
|
// Would be useful to match the creation time of the document for example.
|
||||||
type: _nodeForge.default.pki.oids.messageDigest // value will be auto-populated at signing time
|
value: new Date(),
|
||||||
|
},
|
||||||
}]
|
{
|
||||||
|
type: _nodeForge.default.pki.oids.messageDigest, // value will be auto-populated at signing time
|
||||||
|
},
|
||||||
|
],
|
||||||
}); // Sign in detached mode.
|
}); // Sign in detached mode.
|
||||||
|
|
||||||
p7.sign({
|
p7.sign({
|
||||||
detached: true
|
detached: true,
|
||||||
}); // Check if the PDF has a good enough placeholder to fit the signature.
|
}); // Check if the PDF has a good enough placeholder to fit the signature.
|
||||||
|
|
||||||
const raw = _nodeForge.default.asn1.toDer(p7.toAsn1()).getBytes(); // placeholderLength represents the length of the HEXified symbols but we're
|
const raw = _nodeForge.default.asn1.toDer(p7.toAsn1()).getBytes(); // placeholderLength represents the length of the HEXified symbols but we're
|
||||||
// checking the actual lengths.
|
// checking the actual lengths.
|
||||||
|
|
||||||
|
|
||||||
if (raw.length * 2 > placeholderLength) {
|
if (raw.length * 2 > placeholderLength) {
|
||||||
throw new _SignPdfError.default(`Signature exceeds placeholder length: ${raw.length * 2} > ${placeholderLength}`, _SignPdfError.default.TYPE_INPUT);
|
throw new _SignPdfError.default(
|
||||||
|
`Signature exceeds placeholder length: ${raw.length * 2} > ${placeholderLength}`,
|
||||||
|
_SignPdfError.default.TYPE_INPUT
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let signature = Buffer.from(raw, 'binary').toString('hex'); // Store the HEXified signature. At least useful in tests.
|
let signature = Buffer.from(raw, "binary").toString("hex"); // Store the HEXified signature. At least useful in tests.
|
||||||
|
|
||||||
this.lastSignature = signature; // Pad the signature with zeroes so the it is the same length as the placeholder
|
this.lastSignature = signature; // Pad the signature with zeroes so the it is the same length as the placeholder
|
||||||
|
|
||||||
signature += Buffer.from(String.fromCharCode(0).repeat(placeholderLength / 2 - raw.length)).toString('hex'); // Place it in the document.
|
signature += Buffer.from(String.fromCharCode(0).repeat(placeholderLength / 2 - raw.length)).toString(
|
||||||
|
"hex"
|
||||||
|
); // Place it in the document.
|
||||||
|
|
||||||
pdf = Buffer.concat([pdf.slice(0, byteRange[1]), Buffer.from(`<${signature}>`), pdf.slice(byteRange[1])]); // Magic. Done.
|
pdf = Buffer.concat([pdf.slice(0, byteRange[1]), Buffer.from(`<${signature}>`), pdf.slice(byteRange[1])]); // Magic. Done.
|
||||||
|
|
||||||
return pdf;
|
return pdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.SignPdf = SignPdf;
|
exports.SignPdf = SignPdf;
|
||||||
|
|
||||||
var _default = new SignPdf();
|
var _default = new SignPdf();
|
||||||
|
|
||||||
exports.default = _default;
|
exports.default = _default;
|
||||||
|
|||||||
@ -1,23 +1,15 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/20/solid";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
export function Breadcrumb(props: any) {
|
export function Breadcrumb(props: any) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav className="sm:hidden" aria-label="Back">
|
<nav className="sm:hidden" aria-label="Back">
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={props.items.length > 1 ? props.items[props.items.length - 2].href : props.items[0].href}
|
||||||
props.items.length > 1
|
className="flex items-center text-sm font-medium text-gray-500 hover:text-gray-700">
|
||||||
? props.items[props.items.length - 2].href
|
<ChevronLeftIcon className="-ml-1 mr-1 h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />
|
||||||
: props.items[0].href
|
|
||||||
}
|
|
||||||
className="flex items-center text-sm font-medium text-gray-500 hover:text-gray-700"
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon
|
|
||||||
className="-ml-1 mr-1 h-5 w-5 flex-shrink-0 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Back
|
Back
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
@ -26,19 +18,13 @@ export function Breadcrumb(props: any) {
|
|||||||
{props?.items.map((item: any, index: number) => (
|
{props?.items.map((item: any, index: number) => (
|
||||||
<React.Fragment key={item.href}>
|
<React.Fragment key={item.href}>
|
||||||
{index > 0 ? (
|
{index > 0 ? (
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon className="h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />
|
||||||
className="h-5 w-5 flex-shrink-0 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
<li>
|
<li>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Link
|
<Link href={item.href} className="text-sm font-medium text-gray-500 hover:text-gray-700">
|
||||||
href={item.href}
|
|
||||||
className="text-sm font-medium text-gray-500 hover:text-gray-700"
|
|
||||||
>
|
|
||||||
{item.title}
|
{item.title}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { classNames } from "@documenso/lib";
|
|
||||||
import Link from "next/link";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { classNames } from "@documenso/lib";
|
||||||
|
|
||||||
export function Button(props: any) {
|
export function Button(props: any) {
|
||||||
const isLink = typeof props.href !== "undefined" && !props.disabled;
|
const isLink = typeof props.href !== "undefined" && !props.disabled;
|
||||||
@ -8,8 +8,7 @@ export function Button(props: any) {
|
|||||||
const baseStyles =
|
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";
|
"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";
|
const primaryStyles = "text-white bg-neon hover:bg-neon-dark";
|
||||||
const secondaryStyles =
|
const secondaryStyles = "border-gray-300 bg-white text-gray-700 hover:bg-gray-50";
|
||||||
"border-gray-300 bg-white text-gray-700 hover:bg-gray-50";
|
|
||||||
|
|
||||||
return isLink ? (
|
return isLink ? (
|
||||||
<Link
|
<Link
|
||||||
@ -20,13 +19,9 @@ export function Button(props: any) {
|
|||||||
color === "primary" ? primaryStyles : secondaryStyles,
|
color === "primary" ? primaryStyles : secondaryStyles,
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
hidden={props.hidden}
|
hidden={props.hidden}>
|
||||||
>
|
|
||||||
{props.icon ? (
|
{props.icon ? (
|
||||||
<props.icon
|
<props.icon className="mr-1 inline h-5 text-inherit" aria-hidden="true"></props.icon>
|
||||||
className="inline h-5 mr-1 text-inherit"
|
|
||||||
aria-hidden="true"
|
|
||||||
></props.icon>
|
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
@ -43,13 +38,9 @@ export function Button(props: any) {
|
|||||||
)}
|
)}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
disabled={props.disabled || props.loading}
|
disabled={props.disabled || props.loading}
|
||||||
hidden={props.hidden}
|
hidden={props.hidden}>
|
||||||
>
|
|
||||||
{props.icon ? (
|
{props.icon ? (
|
||||||
<props.icon
|
<props.icon className="mr-1 inline h-5 text-inherit" aria-hidden="true"></props.icon>
|
||||||
className="inline h-5 mr-1 text-inherit"
|
|
||||||
aria-hidden="true"
|
|
||||||
></props.icon>
|
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { classNames } from "@documenso/lib";
|
|
||||||
import Link from "next/link";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { classNames } from "@documenso/lib";
|
||||||
|
|
||||||
export function IconButton(props: any) {
|
export function IconButton(props: any) {
|
||||||
const isLink = typeof props.href !== "undefined";
|
const isLink = typeof props.href !== "undefined";
|
||||||
@ -18,13 +18,9 @@ export function IconButton(props: any) {
|
|||||||
color === "primary" ? primaryStyles : secondaryStyles,
|
color === "primary" ? primaryStyles : secondaryStyles,
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
hidden={props.hidden}
|
hidden={props.hidden}>
|
||||||
>
|
|
||||||
{props.icon ? (
|
{props.icon ? (
|
||||||
<props.icon
|
<props.icon className="mr-1 inline h-6 text-inherit" aria-hidden="true"></props.icon>
|
||||||
className="inline text-inherit h-6 mr-1"
|
|
||||||
aria-hidden="true"
|
|
||||||
></props.icon>
|
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
@ -40,13 +36,9 @@ export function IconButton(props: any) {
|
|||||||
)}
|
)}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
hidden={props.hidden}
|
hidden={props.hidden}>
|
||||||
>
|
|
||||||
{props.icon ? (
|
{props.icon ? (
|
||||||
<props.icon
|
<props.icon className="mr-1 inline h-6 text-inherit" aria-hidden="true"></props.icon>
|
||||||
className="inline text-inherit h-6 mr-1"
|
|
||||||
aria-hidden="true"
|
|
||||||
></props.icon>
|
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Transition, Dialog as DialogComponent } from "@headlessui/react";
|
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { Button } from "@documenso/ui";
|
|
||||||
import { sendSigningRequests } from "@documenso/lib/api";
|
import { sendSigningRequests } from "@documenso/lib/api";
|
||||||
|
import { Button } from "@documenso/ui";
|
||||||
|
import { Dialog as DialogComponent, Transition } from "@headlessui/react";
|
||||||
import { Document as PrismaDocument } from "@prisma/client";
|
import { Document as PrismaDocument } from "@prisma/client";
|
||||||
|
|
||||||
type FormValue = {
|
type FormValue = {
|
||||||
@ -21,18 +21,8 @@ type DialogProps = {
|
|||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Dialog({
|
export function Dialog({ title, open, setOpen, document, formValues, setLoading, icon }: DialogProps) {
|
||||||
title,
|
const unsentEmailsLength = formValues.filter((s: any) => s.email && s.sendStatus != "SENT").length;
|
||||||
open,
|
|
||||||
setOpen,
|
|
||||||
document,
|
|
||||||
formValues,
|
|
||||||
setLoading,
|
|
||||||
icon,
|
|
||||||
}: DialogProps) {
|
|
||||||
const unsentEmailsLength = formValues.filter(
|
|
||||||
(s: any) => s.email && s.sendStatus != "SENT"
|
|
||||||
).length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} as={Fragment}>
|
<Transition.Root show={open} as={Fragment}>
|
||||||
@ -44,13 +34,12 @@ export function Dialog({
|
|||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0">
|
||||||
>
|
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||||
<div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" />
|
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||||
<div className="flex items-end justify-center min-h-full p-4 text-center sm:items-center sm:p-0">
|
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
@ -58,18 +47,14 @@ export function Dialog({
|
|||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
||||||
>
|
<DialogComponent.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
|
||||||
<DialogComponent.Panel className="relative px-4 pt-5 pb-4 overflow-hidden text-left transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-center w-12 h-12 mx-auto bg-green-100 rounded-full">
|
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-5">
|
<div className="mt-3 text-center sm:mt-5">
|
||||||
<DialogComponent.Title
|
<DialogComponent.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-gray-900"
|
|
||||||
>
|
|
||||||
{title}
|
{title}
|
||||||
</DialogComponent.Title>
|
</DialogComponent.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
@ -79,7 +64,7 @@ export function Dialog({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-3 mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:flex-none ">
|
<div className="mt-5 flex justify-end gap-3 sm:mt-6 sm:grid sm:flex-none sm:grid-flow-row-dense sm:grid-cols-2 ">
|
||||||
<Button color="secondary" onClick={() => setOpen(false)}>
|
<Button color="secondary" onClick={() => setOpen(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
@ -90,8 +75,7 @@ export function Dialog({
|
|||||||
sendSigningRequests(document).finally(() => {
|
sendSigningRequests(document).finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Send
|
Send
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import React, { Fragment, useState } from "react";
|
||||||
import { classNames } from "@documenso/lib";
|
import { classNames } from "@documenso/lib";
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
|
||||||
import React, { Fragment, useState } from "react";
|
|
||||||
|
|
||||||
export function SelectBox(props: any) {
|
export function SelectBox(props: any) {
|
||||||
return (
|
return (
|
||||||
@ -10,21 +10,15 @@ export function SelectBox(props: any) {
|
|||||||
value={props.value}
|
value={props.value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.onChange(e);
|
props.onChange(e);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Listbox.Label className="block text-sm font-medium text-gray-700">
|
<Listbox.Label className="block text-sm font-medium text-gray-700">{props.label}</Listbox.Label>
|
||||||
{props.label}
|
|
||||||
</Listbox.Label>
|
|
||||||
<div className="relative mt-1">
|
<div className="relative mt-1">
|
||||||
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-neon focus:outline-none focus:ring-1 focus:ring-neon sm:text-sm">
|
<Listbox.Button className="focus:border-neon focus:ring-neon relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:outline-none focus:ring-1 sm:text-sm">
|
||||||
<span className="block truncate">{props?.value?.label}</span>
|
<span className="block truncate">{props?.value?.label}</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<ChevronUpDownIcon
|
<ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
|
|
||||||
@ -33,28 +27,25 @@ export function SelectBox(props: any) {
|
|||||||
as={Fragment}
|
as={Fragment}
|
||||||
leave="transition ease-in duration-100"
|
leave="transition ease-in duration-100"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0">
|
||||||
>
|
|
||||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||||
{props.options.map((option: any) => (
|
{props.options.map((option: any) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
classNames(
|
classNames(
|
||||||
active ? "text-white bg-neon" : "text-gray-900",
|
active ? "bg-neon 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"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={option}
|
value={option}>
|
||||||
>
|
|
||||||
{({ selected, active }) => (
|
{({ selected, active }) => (
|
||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
selected ? "font-semibold" : "font-normal",
|
selected ? "font-semibold" : "font-normal",
|
||||||
"block truncate"
|
"block truncate"
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{option.label}
|
{option.label}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -63,12 +54,8 @@ export function SelectBox(props: any) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
active ? "text-white" : "text-neon",
|
active ? "text-white" : "text-neon",
|
||||||
"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"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user