Move dialog into a seperate component

This commit is contained in:
Ephraim Atta-Duncan
2023-03-28 12:42:00 +00:00
parent f8f941a9cd
commit 16f6da01c0
3 changed files with 105 additions and 143 deletions

View File

@ -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: {

View File

@ -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>
);
} }

View File

@ -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";