add recipient ui

This commit is contained in:
Timur Ercan
2023-02-03 18:07:43 +01:00
parent d7b649f67a
commit e9db5acc85
4 changed files with 110 additions and 37 deletions

View File

@ -21,6 +21,10 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
return; return;
} }
if (!body.email) {
res.status(400).send("Missing parameter email.");
}
const document: PrismaDocument = await getDocument(+documentId, req, res); const document: PrismaDocument = await getDocument(+documentId, req, res);
// todo encapsulate entity ownerships checks // todo encapsulate entity ownerships checks
@ -28,15 +32,23 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
return res.status(401).send("User does not have access to this document."); return res.status(401).send("User does not have access to this document.");
} }
await prisma.recipient.create({ const upsert = await prisma.recipient.upsert({
data: { where: {
email: body.email,
},
update: {
email: body.email,
name: body.name,
},
create: {
documentId: +documentId, documentId: +documentId,
email: body.email, email: body.email,
name: body.name,
token: short.generate().toString(), token: short.generate().toString(),
}, },
}); });
return res.status(201).end(); return res.status(200).end();
} }
export default defaultHandler({ export default defaultHandler({

View File

@ -8,6 +8,7 @@ import {
EnvelopeIcon, EnvelopeIcon,
EyeIcon, EyeIcon,
PlusIcon, PlusIcon,
SunIcon,
TrashIcon, TrashIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import Link from "next/link"; import Link from "next/link";
@ -135,28 +136,51 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
<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.Recipient.map((item: any) => ( {document.Recipient.map((item: any) => (
<div key={item.id}> <div key={item.id}>
{item.sendStatus === "NOT_SENT" ? (
<span
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"
>
{item.name
? item.name + " <" + item.email + ">"
: item.email}
</span>
) : (
""
)}
{item.sendStatus === "SENT" && {item.sendStatus === "SENT" &&
item.readStatus !== "OPENED" && item.readStatus !== "OPENED" ? (
item.signingStatus !== "SIGNED" ? (
<span id="sent_icon"> <span id="sent_icon">
<EnvelopeIcon className="inline h-5 mr-1"></EnvelopeIcon> <EnvelopeIcon className="inline h-5 mr-1"></EnvelopeIcon>
{item.email} <span
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"
>
{item.name
? item.name + " <" + item.email + ">"
: item.email}
</span>
</span> </span>
) : ( ) : (
"" ""
)} )}
{item.sendStatus === "SENT" && {item.readStatus === "OPENED" &&
item.readStatus === "OPENED" ? ( item.signingStatus === "NOT_SIGNED" ? (
<span id="read_icon"> <span id="read_icon">
<EyeIcon className="inline h-5 mr-1"></EyeIcon>{" "} <EyeIcon className="inline h-5 mr-1"></EyeIcon>{" "}
{item.email} <span
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"
>
{item.name
? item.name + " <" + item.email + ">"
: item.email}
</span>
</span> </span>
) : ( ) : (
"" ""
)} )}
{item.sendStatus === "SENT" && {item.signingStatus === "SIGNED" ? (
item.readStatus === "OPENED" &&
item.signingStatus === "SIGNED" ? (
<span id="signed_icon"> <span id="signed_icon">
<CheckBadgeIcon className="inline h-5 mr-1"></CheckBadgeIcon>{" "} <CheckBadgeIcon className="inline h-5 mr-1"></CheckBadgeIcon>{" "}
{item.email} {item.email}

View File

@ -14,6 +14,7 @@ import { getUserFromToken } from "@documenso/lib/server";
import { getDocument } from "@documenso/lib/query"; import { getDocument } from "@documenso/lib/query";
import { Document as PrismaDocument } from "@prisma/client"; import { Document as PrismaDocument } from "@prisma/client";
import { Breadcrumb, Button, IconButton } from "@documenso/ui"; import { Breadcrumb, Button, IconButton } from "@documenso/ui";
import toast from "react-hot-toast";
const RecipientsPage: NextPageWithLayout = (props: any) => { const RecipientsPage: NextPageWithLayout = (props: any) => {
const title: string = const title: string =
@ -91,7 +92,34 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
htmlFor="name" htmlFor="name"
className="block text-xs font-medium text-gray-900" className="block text-xs font-medium text-gray-900"
> >
Name Email
</label>
<input
type="email"
name="email"
value={item.email}
onChange={(e) => {
const updatedSigners = [...signers];
updatedSigners[index].email = e.target.value;
setSigners(updatedSigners);
}}
onBlur={() => {
item.documentId = props.document.id;
upsertRecipient(item);
}}
onKeyDown={(event: any) => {
if (event.key === "Enter") upsertRecipient(item);
}}
className="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 sm:text-sm outline-none bg-inherit"
placeholder="john.dorian@loremipsum.com"
/>
</div>
<div className="ml-3 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">
<label
htmlFor="name"
className="block text-xs font-medium text-gray-900"
>
Name (optional)
</label> </label>
<input <input
type="email" type="email"
@ -102,32 +130,17 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
updatedSigners[index].name = e.target.value; updatedSigners[index].name = e.target.value;
setSigners(updatedSigners); setSigners(updatedSigners);
}} }}
id="name" onBlur={() => {
item.documentId = props.document.id;
upsertRecipient(item);
}}
onKeyDown={(event: any) => {
if (event.key === "Enter") upsertRecipient(item);
}}
className="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 sm:text-sm outline-none bg-inherit" className="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 sm:text-sm outline-none bg-inherit"
placeholder="John Dorian" placeholder="John Dorian"
/> />
</div> </div>
<div className="ml-3 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">
<label
htmlFor="name"
className="block text-xs font-medium text-gray-900"
>
Email
</label>
<input
type="email"
name="name"
value={item.email}
onChange={(e) => {
const updatedSigners = [...signers];
updatedSigners[index].email = e.target.value;
setSigners(updatedSigners);
}}
id="name"
className="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 sm:text-sm outline-none bg-inherit"
placeholder="john.dorian@loremipsum.com"
/>
</div>
<div className="ml-auto flex"> <div className="ml-auto flex">
<IconButton <IconButton
icon={XMarkIcon} icon={XMarkIcon}
@ -139,7 +152,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
console.log("click"); console.log("click");
// todo save to api // todo save to api
}} }}
// className="group-hover:text-neon-dark" className="group-hover:text-neon-dark"
></IconButton> ></IconButton>
</div> </div>
</div> </div>
@ -167,6 +180,29 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
); );
}; };
async function upsertRecipient(recipient: any) {
toast.promise(
fetch("/api/documents/" + recipient.documentId + "/recipients", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(recipient),
}),
{
loading: "Saving...",
success: "Saved.",
error: "Could not save :/",
},
{
id: "saving",
style: {
minWidth: "200px",
},
}
);
}
RecipientsPage.getLayout = function getLayout(page: ReactElement) { RecipientsPage.getLayout = function getLayout(page: ReactElement) {
return <Layout>{page}</Layout>; return <Layout>{page}</Layout>;
}; };

View File

@ -20,7 +20,8 @@ model Document {
model Recipient { model Recipient {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
documentId Int documentId Int
email String @db.VarChar(255) email String @unique @db.VarChar(255)
name String @default("") @db.VarChar(255)
token String token String
readStatus ReadStatus @default(NOT_OPENED) readStatus ReadStatus @default(NOT_OPENED)
signingStatus SigningStatus @default(NOT_SIGNED) signingStatus SigningStatus @default(NOT_SIGNED)