144 lines
4.9 KiB
TypeScript
144 lines
4.9 KiB
TypeScript
import { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { getUser } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { LogoutButton } from "@/components/auth/logout-button";
|
|
import { ContactsList } from "@/components/contacts/contacts-list";
|
|
|
|
export const metadata: Metadata = {
|
|
title: "Contacts - Stones Database",
|
|
description: "Manage contacts in the Stones Database",
|
|
};
|
|
|
|
interface ContactsPageProps {
|
|
searchParams: { [key: string]: string | string[] | undefined };
|
|
}
|
|
|
|
export default async function ContactsPage({ searchParams }: ContactsPageProps) {
|
|
const user = await getUser();
|
|
const page = Number(searchParams.page) || 1;
|
|
const search = typeof searchParams.search === 'string' ? searchParams.search : '';
|
|
const limit = 25;
|
|
const skip = (page - 1) * limit;
|
|
|
|
// Get contacts with pagination and search
|
|
const contacts = await prisma.contact.findMany({
|
|
where: {
|
|
OR: [
|
|
{ name: { contains: search, mode: 'insensitive' } },
|
|
{ ensName: { contains: search, mode: 'insensitive' } },
|
|
{ ethereumAddress: { contains: search, mode: 'insensitive' } },
|
|
{ email: { contains: search, mode: 'insensitive' } },
|
|
{ twitter: { contains: search, mode: 'insensitive' } },
|
|
{ discord: { contains: search, mode: 'insensitive' } },
|
|
],
|
|
},
|
|
skip,
|
|
take: limit,
|
|
orderBy: {
|
|
createdAt: "desc",
|
|
},
|
|
include: {
|
|
nftHoldings: {
|
|
take: 1,
|
|
},
|
|
daoMemberships: {
|
|
take: 1,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Get total count for pagination with search
|
|
const totalContacts = await prisma.contact.count({
|
|
where: {
|
|
OR: [
|
|
{ name: { contains: search, mode: 'insensitive' } },
|
|
{ ensName: { contains: search, mode: 'insensitive' } },
|
|
{ ethereumAddress: { contains: search, mode: 'insensitive' } },
|
|
{ email: { contains: search, mode: 'insensitive' } },
|
|
{ twitter: { contains: search, mode: 'insensitive' } },
|
|
{ discord: { contains: search, mode: 'insensitive' } },
|
|
],
|
|
},
|
|
});
|
|
const totalPages = Math.ceil(totalContacts / limit);
|
|
|
|
return (
|
|
<div className="flex min-h-screen flex-col">
|
|
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
<div className="container flex h-14 items-center justify-between">
|
|
<div className="mr-4 flex">
|
|
<Link href="/" className="mr-6 flex items-center space-x-2">
|
|
<span className="font-bold">Stones Database</span>
|
|
</Link>
|
|
</div>
|
|
<nav className="flex items-center space-x-4">
|
|
<Link href="/contacts" className="text-sm font-medium">
|
|
Contacts
|
|
</Link>
|
|
<Link href="/dashboard" className="text-sm font-medium">
|
|
Dashboard
|
|
</Link>
|
|
{user && (
|
|
<div className="flex items-center gap-4">
|
|
<span className="text-sm text-muted-foreground">
|
|
Hello, {user.name}
|
|
</span>
|
|
<Link href="/settings" className="text-sm font-medium">
|
|
Settings
|
|
</Link>
|
|
<LogoutButton />
|
|
</div>
|
|
)}
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
<main className="flex-1 container py-6">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h1 className="text-3xl font-bold">Contacts</h1>
|
|
<div className="flex gap-4">
|
|
<div className="relative w-full md:w-60">
|
|
<form action="/contacts" method="GET">
|
|
<Input
|
|
type="search"
|
|
name="search"
|
|
placeholder="Search contacts..."
|
|
className="pr-8"
|
|
defaultValue={search}
|
|
/>
|
|
{/* Preserve page parameter if it exists */}
|
|
{page > 1 && <input type="hidden" name="page" value={page} />}
|
|
</form>
|
|
</div>
|
|
<Link href="/contacts/new">
|
|
<Button>
|
|
Add Contact
|
|
</Button>
|
|
</Link>
|
|
<Link href={`/api/contacts/export?search=${encodeURIComponent(search)}`}>
|
|
<Button variant="outline">
|
|
Export Data
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<ContactsList
|
|
contacts={contacts}
|
|
currentPage={page}
|
|
totalPages={totalPages}
|
|
search={search}
|
|
/>
|
|
</main>
|
|
<footer className="w-full border-t py-6">
|
|
<div className="container flex justify-center items-center">
|
|
<p className="text-center text-sm leading-loose text-muted-foreground">
|
|
© 2025 BoilerHaus. All rights reserved.
|
|
</p>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|