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;
|
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({
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user