Files
GibSend/apps/web/src/app/(dashboard)/contacts/[contactBookId]/page.tsx
2024-09-28 21:17:32 +10:00

203 lines
7.4 KiB
TypeScript

"use client";
import { api } from "~/trpc/react";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@unsend/ui/src/breadcrumb";
import Link from "next/link";
import AddContact from "./add-contact";
import ContactList from "./contact-list";
import { TextWithCopyButton } from "@unsend/ui/src/text-with-copy";
import { formatDistanceToNow } from "date-fns";
import EmojiPicker, { Theme } from "emoji-picker-react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@unsend/ui/src/popover";
import { Button } from "@unsend/ui/src/button";
import { useTheme } from "@unsend/ui";
export default function ContactsPage({
params,
}: {
params: { contactBookId: string };
}) {
const { theme } = useTheme();
const contactBookDetailQuery = api.contacts.getContactBookDetails.useQuery({
contactBookId: params.contactBookId,
});
const utils = api.useUtils();
const updateContactBookMutation = api.contacts.updateContactBook.useMutation({
onMutate: async (data) => {
await utils.contacts.getContactBookDetails.cancel();
utils.contacts.getContactBookDetails.setData(
{
contactBookId: params.contactBookId,
},
(old) => {
if (!old) return old;
return {
...old,
...data,
};
}
);
},
onSettled: () => {
utils.contacts.getContactBookDetails.invalidate({
contactBookId: params.contactBookId,
});
},
});
return (
<div>
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/contacts" className="text-xl">
Contact books
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="text-xl" />
<BreadcrumbItem>
<BreadcrumbPage className="text-xl">
<div className="flex items-center gap-2">
<span className="text-lg">
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
className="p-0 hover:bg-transparent text-lg"
type="button"
>
{contactBookDetailQuery.data?.emoji}
</Button>
</PopoverTrigger>
<PopoverContent className="w-full rounded-none border-0 !bg-transparent !p-0 shadow-none drop-shadow-md">
<EmojiPicker
onEmojiClick={(emojiObject) => {
// Handle emoji selection here
// You might want to update the contactBook's emoji
updateContactBookMutation.mutate({
contactBookId: params.contactBookId,
emoji: emojiObject.emoji,
});
}}
theme={
theme === "system"
? Theme.AUTO
: theme === "dark"
? Theme.DARK
: Theme.LIGHT
}
/>
</PopoverContent>
</Popover>
</span>
<span className="text-xl">
{contactBookDetailQuery.data?.name}
</span>
</div>
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
<div className="flex gap-4">
<AddContact contactBookId={params.contactBookId} />
</div>
</div>
<div className="mt-16">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8">
<div className="flex flex-col gap-2 rounded-lg border p-4 shadow">
<p className="font-semibold mb-1">Metrics</p>
<div className="flex items-center gap-2">
<div className="text-muted-foreground w-[130px] text-sm">
Total Contacts
</div>
<div className="font-mono text-sm">
{contactBookDetailQuery.data?.totalContacts !== undefined
? contactBookDetailQuery.data?.totalContacts
: "--"}
</div>
</div>
<div className="flex items-center gap-2">
<div className="text-muted-foreground w-[130px] text-sm">
Unsubscribed
</div>
<div className="font-mono text-sm">
{contactBookDetailQuery.data?.unsubscribedContacts !== undefined
? contactBookDetailQuery.data?.unsubscribedContacts
: "--"}
</div>
</div>
</div>
<div className="flex flex-col gap-2 rounded-lg border p-4 shadow">
<p className="font-semibold">Details</p>
<div className="flex items-center gap-2">
<div className="text-muted-foreground w-[130px] text-sm">
Contact book ID
</div>
<TextWithCopyButton
value={params.contactBookId}
alwaysShowCopy
className="text-sm w-[130px] overflow-hidden text-ellipsis font-mono"
/>
</div>
<div className="flex items-center gap-2">
<div className="text-muted-foreground w-[130px] text-sm">
Created at
</div>
<div className="text-sm">
{contactBookDetailQuery.data?.createdAt
? formatDistanceToNow(contactBookDetailQuery.data.createdAt, {
addSuffix: true,
})
: "--"}
</div>
</div>
</div>
<div className="flex flex-col gap-2 rounded-lg border p-4 shadow">
<p className="font-semibold">Recent campaigns</p>
{!contactBookDetailQuery.isLoading &&
contactBookDetailQuery.data?.campaigns.length === 0 ? (
<div className="text-muted-foreground text-sm">
No campaigns yet.
</div>
) : null}
{contactBookDetailQuery.data?.campaigns.map((campaign) => (
<div key={campaign.id} className="flex items-center gap-2">
<Link href={`/campaigns/${campaign.id}`}>
<div className="text-sm hover:underline hover:decoration-dashed text-nowrap w-[200px] overflow-hidden text-ellipsis">
{campaign.name}
</div>
</Link>
<div className="text-muted-foreground text-xs">
{formatDistanceToNow(campaign.createdAt, {})}
</div>
</div>
))}
</div>
</div>
<div className="mt-16">
<ContactList contactBookId={params.contactBookId} />
</div>
</div>
</div>
);
}