add contact search (#98)
This commit is contained in:
@@ -23,7 +23,8 @@ import { api } from "~/trpc/react";
|
|||||||
import { getGravatarUrl } from "~/utils/gravatar-utils";
|
import { getGravatarUrl } from "~/utils/gravatar-utils";
|
||||||
import DeleteContact from "./delete-contact";
|
import DeleteContact from "./delete-contact";
|
||||||
import EditContact from "./edit-contact";
|
import EditContact from "./edit-contact";
|
||||||
|
import { Input } from "@unsend/ui/src/input";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
export default function ContactList({
|
export default function ContactList({
|
||||||
contactBookId,
|
contactBookId,
|
||||||
}: {
|
}: {
|
||||||
@@ -31,12 +32,14 @@ export default function ContactList({
|
|||||||
}) {
|
}) {
|
||||||
const [page, setPage] = useUrlState("page", "1");
|
const [page, setPage] = useUrlState("page", "1");
|
||||||
const [status, setStatus] = useUrlState("status");
|
const [status, setStatus] = useUrlState("status");
|
||||||
|
const [search, setSearch] = useUrlState("search");
|
||||||
|
|
||||||
const pageNumber = Number(page);
|
const pageNumber = Number(page);
|
||||||
|
|
||||||
const contactsQuery = api.contacts.contacts.useQuery({
|
const contactsQuery = api.contacts.contacts.useQuery({
|
||||||
contactBookId,
|
contactBookId,
|
||||||
page: pageNumber,
|
page: pageNumber,
|
||||||
|
search: search ?? undefined,
|
||||||
subscribed:
|
subscribed:
|
||||||
status === "Subscribed"
|
status === "Subscribed"
|
||||||
? true
|
? true
|
||||||
@@ -45,9 +48,21 @@ export default function ContactList({
|
|||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const debouncedSearch = useDebouncedCallback((value: string) => {
|
||||||
|
setSearch(value);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-10 flex flex-col gap-4">
|
<div className="mt-10 flex flex-col gap-4">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder="Search by email or name"
|
||||||
|
className="w-[350px] mr-4"
|
||||||
|
defaultValue={search ?? ""}
|
||||||
|
onChange={(e) => debouncedSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Select
|
<Select
|
||||||
value={status ?? "All"}
|
value={status ?? "All"}
|
||||||
onValueChange={(val) => setStatus(val === "All" ? null : val)}
|
onValueChange={(val) => setStatus(val === "All" ? null : val)}
|
||||||
@@ -95,15 +110,22 @@ export default function ContactList({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Image
|
<Image
|
||||||
src={getGravatarUrl(contact.email, {
|
src={getGravatarUrl(contact.email, {
|
||||||
size: 35,
|
size: 75,
|
||||||
defaultImage: "identicon",
|
defaultImage: "robohash",
|
||||||
})}
|
})}
|
||||||
alt={contact.email + "'s gravatar"}
|
alt={contact.email + "'s gravatar"}
|
||||||
width={35}
|
width={35}
|
||||||
height={35}
|
height={35}
|
||||||
className="rounded-full"
|
className="rounded-full"
|
||||||
/>
|
/>
|
||||||
{contact.email}
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{contact.email}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{contact.firstName} {contact.lastName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
@@ -100,6 +100,7 @@ export const contactsRouter = createTRPCRouter({
|
|||||||
z.object({
|
z.object({
|
||||||
page: z.number().optional(),
|
page: z.number().optional(),
|
||||||
subscribed: z.boolean().optional(),
|
subscribed: z.boolean().optional(),
|
||||||
|
search: z.string().optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ ctx: { db }, input }) => {
|
.query(async ({ ctx: { db }, input }) => {
|
||||||
@@ -112,6 +113,15 @@ export const contactsRouter = createTRPCRouter({
|
|||||||
...(input.subscribed !== undefined
|
...(input.subscribed !== undefined
|
||||||
? { subscribed: input.subscribed }
|
? { subscribed: input.subscribed }
|
||||||
: {}),
|
: {}),
|
||||||
|
...(input.search
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ email: { contains: input.search, mode: "insensitive" } },
|
||||||
|
{ firstName: { contains: input.search, mode: "insensitive" } },
|
||||||
|
{ lastName: { contains: input.search, mode: "insensitive" } },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const countP = db.contact.count({ where: whereConditions });
|
const countP = db.contact.count({ where: whereConditions });
|
||||||
|
@@ -32,7 +32,7 @@ export function getGravatarUrl(
|
|||||||
size: 80,
|
size: 80,
|
||||||
defaultImage: "identicon",
|
defaultImage: "identicon",
|
||||||
rating: "g",
|
rating: "g",
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
const trimmedEmail = email.trim().toLowerCase();
|
const trimmedEmail = email.trim().toLowerCase();
|
||||||
const hash = crypto.createHash("sha256").update(trimmedEmail).digest("hex");
|
const hash = crypto.createHash("sha256").update(trimmedEmail).digest("hex");
|
||||||
|
Reference in New Issue
Block a user