mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
Move dialog into a seperate component
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { Fragment, ReactElement, useRef, useState } from "react";
|
import { ReactElement, useRef, useState } from "react";
|
||||||
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 { classNames, NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib";
|
||||||
@ -7,7 +7,6 @@ import {
|
|||||||
ArrowDownTrayIcon,
|
ArrowDownTrayIcon,
|
||||||
CheckBadgeIcon,
|
CheckBadgeIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
EnvelopeIcon,
|
|
||||||
PaperAirplaneIcon,
|
PaperAirplaneIcon,
|
||||||
PencilSquareIcon,
|
PencilSquareIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
@ -17,27 +16,17 @@ import {
|
|||||||
import { getUserFromToken } from "@documenso/lib/server";
|
import { getUserFromToken } from "@documenso/lib/server";
|
||||||
import { getDocument } from "@documenso/lib/query";
|
import { getDocument } from "@documenso/lib/query";
|
||||||
import { Document as PrismaDocument, DocumentStatus } from "@prisma/client";
|
import { Document as PrismaDocument, DocumentStatus } from "@prisma/client";
|
||||||
import { Breadcrumb, Button, IconButton } from "@documenso/ui";
|
import { Breadcrumb, Button, Dialog, IconButton } from "@documenso/ui";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { createOrUpdateRecipient, deleteRecipient, sendSigningRequests } from "@documenso/lib/api";
|
||||||
import {
|
|
||||||
createOrUpdateRecipient,
|
import { FormProvider, useFieldArray, useForm, useWatch } from "react-hook-form";
|
||||||
deleteRecipient,
|
|
||||||
sendSigningRequests,
|
|
||||||
} from "@documenso/lib/api";
|
|
||||||
import {
|
|
||||||
FormProvider,
|
|
||||||
useFieldArray,
|
|
||||||
useForm,
|
|
||||||
useWatch,
|
|
||||||
} from "react-hook-form";
|
|
||||||
|
|
||||||
type FormValues = {
|
type FormValues = {
|
||||||
signers: { id: number; email: string; name: string }[];
|
signers: { id: number; email: string; name: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const RecipientsPage: NextPageWithLayout = (props: any) => {
|
const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||||
const title: string =
|
const title: string = `"` + props?.document?.title + `"` + "Recipients | Documenso";
|
||||||
`"` + props?.document?.title + `"` + "Recipients | Documenso";
|
|
||||||
const breadcrumbItems = [
|
const breadcrumbItems = [
|
||||||
{
|
{
|
||||||
title: "Documents",
|
title: "Documents",
|
||||||
@ -49,11 +38,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Recipients",
|
title: "Recipients",
|
||||||
href:
|
href: NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id + "/recipients",
|
||||||
NEXT_PUBLIC_WEBAPP_URL +
|
|
||||||
"/documents/" +
|
|
||||||
props.document.id +
|
|
||||||
"/recipients",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -107,11 +92,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
<Button
|
<Button
|
||||||
icon={PencilSquareIcon}
|
icon={PencilSquareIcon}
|
||||||
disabled={props.document.status === DocumentStatus.COMPLETED}
|
disabled={props.document.status === DocumentStatus.COMPLETED}
|
||||||
color={
|
color={props.document.status === DocumentStatus.COMPLETED ? "primary" : "secondary"}
|
||||||
props.document.status === DocumentStatus.COMPLETED
|
|
||||||
? "primary"
|
|
||||||
: "secondary"
|
|
||||||
}
|
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
href={breadcrumbItems[1].href}
|
href={breadcrumbItems[1].href}
|
||||||
>
|
>
|
||||||
@ -127,8 +108,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
disabled={
|
disabled={
|
||||||
(formValues.length || 0) === 0 ||
|
(formValues.length || 0) === 0 ||
|
||||||
!formValues.some(
|
!formValues.some(
|
||||||
(r: any) =>
|
(r: any) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
||||||
r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
|
||||||
) ||
|
) ||
|
||||||
loading
|
loading
|
||||||
}
|
}
|
||||||
@ -139,9 +119,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-4 mt-10 overflow-hidden bg-white rounded-md shadow sm:p-6">
|
<div className="p-4 mt-10 overflow-hidden bg-white rounded-md shadow sm:p-6">
|
||||||
<div className="pb-3 border-b border-gray-200 sm:pb-5">
|
<div className="pb-3 border-b border-gray-200 sm:pb-5">
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900 ">
|
<h3 className="text-lg font-medium leading-6 text-gray-900 ">Signers</h3>
|
||||||
Signers
|
|
||||||
</h3>
|
|
||||||
<p className="max-w-4xl mt-2 text-sm text-gray-500">
|
<p className="max-w-4xl mt-2 text-sm text-gray-500">
|
||||||
The people who will sign the document.
|
The people who will sign the document.
|
||||||
</p>
|
</p>
|
||||||
@ -158,10 +136,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
key={index}
|
key={index}
|
||||||
className="w-full px-2 py-3 border-0 hover:bg-green-50 group sm:py-4"
|
className="w-full px-2 py-3 border-0 hover:bg-green-50 group sm:py-4"
|
||||||
>
|
>
|
||||||
<div
|
<div id="container" className="block w-full lg:flex lg:justify-between">
|
||||||
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:space-x-2 md:space-y-0 md:flex">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -169,17 +144,13 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<label
|
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||||
htmlFor="name"
|
|
||||||
className="block text-xs font-medium text-gray-900"
|
|
||||||
>
|
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
{...register(`signers.${index}.email`, {
|
{...register(`signers.${index}.email`, {
|
||||||
pattern:
|
pattern: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||||
/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
|
||||||
})}
|
})}
|
||||||
defaultValue={item.email}
|
defaultValue={item.email}
|
||||||
disabled={item.sendStatus === "SENT" || loading}
|
disabled={item.sendStatus === "SENT" || loading}
|
||||||
@ -202,10 +173,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
placeholder="john.dorian@loremipsum.com"
|
placeholder="john.dorian@loremipsum.com"
|
||||||
/>
|
/>
|
||||||
{errors?.signers?.[index] ? (
|
{errors?.signers?.[index] ? (
|
||||||
<p
|
<p className="mt-2 text-sm text-red-600" id="email-error">
|
||||||
className="mt-2 text-sm text-red-600"
|
|
||||||
id="email-error"
|
|
||||||
>
|
|
||||||
<XMarkIcon className="inline h-5" /> Invalid Email
|
<XMarkIcon className="inline h-5" /> Invalid Email
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@ -218,10 +186,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
item.sendStatus === "SENT" ? "bg-gray-100" : ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<label
|
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||||
htmlFor="name"
|
|
||||||
className="block text-xs font-medium text-gray-900"
|
|
||||||
>
|
|
||||||
Name (optional)
|
Name (optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -237,10 +202,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event: any) => {
|
onKeyDown={(event: any) => {
|
||||||
if (
|
if (event.key === "Enter" && !errors?.signers?.[index])
|
||||||
event.key === "Enter" &&
|
|
||||||
!errors?.signers?.[index]
|
|
||||||
)
|
|
||||||
createOrUpdateRecipient({
|
createOrUpdateRecipient({
|
||||||
...formValues[index],
|
...formValues[index],
|
||||||
documentId: props.document.id,
|
documentId: props.document.id,
|
||||||
@ -264,8 +226,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{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"
|
||||||
@ -277,8 +238,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{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"
|
||||||
@ -321,9 +281,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (confirm("Resend this signing request?")) {
|
if (confirm("Resend this signing request?")) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
sendSigningRequests(props.document, [
|
sendSigningRequests(props.document, [item.id]).finally(() => {
|
||||||
item.id,
|
|
||||||
]).finally(() => {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -333,9 +291,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={TrashIcon}
|
icon={TrashIcon}
|
||||||
disabled={
|
disabled={!item.id || item.sendStatus === "SENT" || loading}
|
||||||
!item.id || item.sendStatus === "SENT" || loading
|
|
||||||
}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const removedItem = { ...fields }[index];
|
const removedItem = { ...fields }[index];
|
||||||
remove(index);
|
remove(index);
|
||||||
@ -371,79 +327,14 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
|||||||
</FormProvider>
|
</FormProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Transition.Root show={open} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative z-10" onClose={setOpen}>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<Dialog
|
||||||
<div className="flex items-end justify-center min-h-full p-4 text-center sm:items-center sm:p-0">
|
document={props.document}
|
||||||
<Transition.Child
|
formValues={formValues}
|
||||||
as={Fragment}
|
open={open}
|
||||||
enter="ease-out duration-300"
|
setLoading={setLoading}
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
setOpen={setOpen}
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
>
|
|
||||||
<Dialog.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 className="flex items-center justify-center w-12 h-12 mx-auto bg-green-100 rounded-full">
|
|
||||||
<EnvelopeIcon
|
|
||||||
className="w-6 h-6 text-green-600"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="mt-3 text-center sm:mt-5">
|
|
||||||
<Dialog.Title
|
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-gray-900"
|
|
||||||
>
|
|
||||||
Ready to send
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="mt-2">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{`"${props.document.title}" will be sent to ${
|
|
||||||
formValues.filter(
|
|
||||||
(s: any) => s.email && s.sendStatus != "SENT"
|
|
||||||
).length
|
|
||||||
} recipients.`}
|
|
||||||
</p>
|
|
||||||
</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 ">
|
|
||||||
<Button color="secondary" onClick={() => setOpen(false)}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
setLoading(true);
|
|
||||||
sendSigningRequests(props.document).finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -463,11 +354,7 @@ export async function getServerSideProps(context: any) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { id: documentId } = context.query;
|
const { id: documentId } = context.query;
|
||||||
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,5 +1,79 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Transition, Dialog as DialogComponent } from "@headlessui/react";
|
||||||
|
import { Fragment } from "react";
|
||||||
|
import { Button } from "@documenso/ui";
|
||||||
|
import { EnvelopeIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { sendSigningRequests } from "@documenso/lib/api";
|
||||||
|
|
||||||
export function Dialog() {
|
export function Dialog({ open, setOpen, document, formValues, setLoading }: any) {
|
||||||
return <div>Dialog</div>;
|
return (
|
||||||
|
<Transition.Root show={open} as={Fragment}>
|
||||||
|
<DialogComponent as="div" className="relative z-10" onClose={setOpen}>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
<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 className="flex items-center justify-center w-12 h-12 mx-auto bg-green-100 rounded-full">
|
||||||
|
<EnvelopeIcon className="w-6 h-6 text-green-600" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 text-center sm:mt-5">
|
||||||
|
<DialogComponent.Title
|
||||||
|
as="h3"
|
||||||
|
className="text-lg font-medium leading-6 text-gray-900"
|
||||||
|
>
|
||||||
|
Ready to send
|
||||||
|
</DialogComponent.Title>
|
||||||
|
<div className="mt-2">
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{`"${document.title}" will be sent to ${
|
||||||
|
formValues.filter((s: any) => s.email && s.sendStatus != "SENT").length
|
||||||
|
} recipients.`}
|
||||||
|
</p>
|
||||||
|
</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 ">
|
||||||
|
<Button color="secondary" onClick={() => setOpen(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setLoading(true);
|
||||||
|
sendSigningRequests(document).finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogComponent.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogComponent>
|
||||||
|
</Transition.Root>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export { Button, IconButton } from "./components/button/index";
|
export { Button, IconButton } from "./components/button/index";
|
||||||
export { SelectBox } from "./components/selectBox/index";
|
export { SelectBox } from "./components/selectBox/index";
|
||||||
export { Breadcrumb } from "./components/breadcrumb/index";
|
export { Breadcrumb } from "./components/breadcrumb/index";
|
||||||
|
export { Dialog } from "./components/dialog/index";
|
||||||
|
|||||||
Reference in New Issue
Block a user