mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
add recipient ui
This commit is contained in:
@ -21,6 +21,10 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!body.email) {
|
||||
res.status(400).send("Missing parameter email.");
|
||||
}
|
||||
|
||||
const document: PrismaDocument = await getDocument(+documentId, req, res);
|
||||
|
||||
// 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.");
|
||||
}
|
||||
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
const upsert = await prisma.recipient.upsert({
|
||||
where: {
|
||||
email: body.email,
|
||||
},
|
||||
update: {
|
||||
email: body.email,
|
||||
name: body.name,
|
||||
},
|
||||
create: {
|
||||
documentId: +documentId,
|
||||
email: body.email,
|
||||
name: body.name,
|
||||
token: short.generate().toString(),
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(201).end();
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
EnvelopeIcon,
|
||||
EyeIcon,
|
||||
PlusIcon,
|
||||
SunIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
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">
|
||||
{document.Recipient.map((item: any) => (
|
||||
<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.readStatus !== "OPENED" &&
|
||||
item.signingStatus !== "SIGNED" ? (
|
||||
item.readStatus !== "OPENED" ? (
|
||||
<span id="sent_icon">
|
||||
<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>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{item.sendStatus === "SENT" &&
|
||||
item.readStatus === "OPENED" ? (
|
||||
{item.readStatus === "OPENED" &&
|
||||
item.signingStatus === "NOT_SIGNED" ? (
|
||||
<span id="read_icon">
|
||||
<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>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{item.sendStatus === "SENT" &&
|
||||
item.readStatus === "OPENED" &&
|
||||
item.signingStatus === "SIGNED" ? (
|
||||
{item.signingStatus === "SIGNED" ? (
|
||||
<span id="signed_icon">
|
||||
<CheckBadgeIcon className="inline h-5 mr-1"></CheckBadgeIcon>{" "}
|
||||
{item.email}
|
||||
|
||||
@ -14,6 +14,7 @@ import { getUserFromToken } from "@documenso/lib/server";
|
||||
import { getDocument } from "@documenso/lib/query";
|
||||
import { Document as PrismaDocument } from "@prisma/client";
|
||||
import { Breadcrumb, Button, IconButton } from "@documenso/ui";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||
const title: string =
|
||||
@ -91,7 +92,34 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||
htmlFor="name"
|
||||
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>
|
||||
<input
|
||||
type="email"
|
||||
@ -102,32 +130,17 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||
updatedSigners[index].name = e.target.value;
|
||||
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"
|
||||
placeholder="John Dorian"
|
||||
/>
|
||||
</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">
|
||||
<IconButton
|
||||
icon={XMarkIcon}
|
||||
@ -139,7 +152,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||
console.log("click");
|
||||
// todo save to api
|
||||
}}
|
||||
// className="group-hover:text-neon-dark"
|
||||
className="group-hover:text-neon-dark"
|
||||
></IconButton>
|
||||
</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) {
|
||||
return <Layout>{page}</Layout>;
|
||||
};
|
||||
|
||||
@ -20,7 +20,8 @@ model Document {
|
||||
model Recipient {
|
||||
id Int @id @default(autoincrement())
|
||||
documentId Int
|
||||
email String @db.VarChar(255)
|
||||
email String @unique @db.VarChar(255)
|
||||
name String @default("") @db.VarChar(255)
|
||||
token String
|
||||
readStatus ReadStatus @default(NOT_OPENED)
|
||||
signingStatus SigningStatus @default(NOT_SIGNED)
|
||||
|
||||
Reference in New Issue
Block a user