Skip to content

Commit

Permalink
add email and subject search (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
KMKoushik authored Nov 2, 2024
1 parent d15495e commit bb0d541
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 34 deletions.
19 changes: 17 additions & 2 deletions apps/web/src/app/(dashboard)/emails/email-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@unsend/ui/src/tooltip";
import { Input } from "@unsend/ui/src/input";
import { DEFAULT_QUERY_LIMIT } from "~/lib/constants";
import { useDebouncedCallback } from "use-debounce";

/* Stupid hydrating error. And I so stupid to understand the stupid NextJS docs */
const DynamicSheetWithNoSSR = dynamic(
Expand All @@ -53,12 +56,14 @@ export default function EmailsList() {
const [selectedEmail, setSelectedEmail] = useUrlState("emailId");
const [page, setPage] = useUrlState("page", "1");
const [status, setStatus] = useUrlState("status");
const [search, setSearch] = useUrlState("search");

const pageNumber = Number(page);

const emailsQuery = api.email.emails.useQuery({
page: pageNumber,
status: status?.toUpperCase() as EmailStatus,
search,
});

const handleSelectEmail = (emailId: string) => {
Expand All @@ -71,9 +76,19 @@ export default function EmailsList() {
}
};

const debouncedSearch = useDebouncedCallback((value: string) => {
setSearch(value);
}, 1000);

return (
<div className="mt-10 flex flex-col gap-4">
<div className="flex justify-end">
<div className="flex justify-between">
<Input
placeholder="Search by subject or email"
className="w-[350px] mr-4"
defaultValue={search ?? ""}
onChange={(e) => debouncedSearch(e.target.value)}
/>
<Select
value={status ?? "All statuses"}
onValueChange={(val) =>
Expand Down Expand Up @@ -207,7 +222,7 @@ export default function EmailsList() {
<Button
size="sm"
onClick={() => setPage((pageNumber + 1).toString())}
disabled={pageNumber >= (emailsQuery.data?.totalPage ?? 0)}
disabled={emailsQuery.data?.emails.length !== DEFAULT_QUERY_LIMIT}
>
Next
</Button>
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/lib/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DEFAULT_QUERY_LIMIT = 30;
65 changes: 33 additions & 32 deletions apps/web/src/server/api/routers/email.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EmailStatus } from "@prisma/client";
import { EmailStatus, Prisma, Email } from "@prisma/client";
import { format, subDays } from "date-fns";
import { z } from "zod";
import { DEFAULT_QUERY_LIMIT } from "~/lib/constants";

import {
createTRPCRouter,
Expand All @@ -12,50 +13,50 @@ import { cancelEmail, updateEmail } from "~/server/service/email-service";

const statuses = Object.values(EmailStatus) as [EmailStatus];

const DEFAULT_LIMIT = 30;

export const emailRouter = createTRPCRouter({
emails: teamProcedure
.input(
z.object({
page: z.number().optional(),
status: z.enum(statuses).optional().nullable(),
domain: z.number().optional(),
search: z.string().optional().nullable(),
})
)
.query(async ({ ctx, input }) => {
const page = input.page || 1;
const limit = DEFAULT_LIMIT;
const limit = DEFAULT_QUERY_LIMIT;
const offset = (page - 1) * limit;

const whereConditions = {
teamId: ctx.team.id,
...(input.status ? { latestStatus: input.status } : {}),
...(input.domain ? { domainId: input.domain } : {}),
};

const countP = db.email.count({ where: whereConditions });

const emailsP = db.email.findMany({
where: whereConditions,
select: {
id: true,
createdAt: true,
latestStatus: true,
subject: true,
to: true,
scheduledAt: true,
},
orderBy: {
createdAt: "desc",
},
skip: offset,
take: limit,
});

const [emails, count] = await Promise.all([emailsP, countP]);

return { emails, totalPage: Math.ceil(count / limit) };
const emails = await db.$queryRaw<Array<Email>>`
SELECT
id,
"createdAt",
"latestStatus",
subject,
"to",
"scheduledAt"
FROM "Email"
WHERE "teamId" = ${ctx.team.id}
${input.status ? Prisma.sql`AND "latestStatus" = ${input.status}` : Prisma.sql``}
${input.domain ? Prisma.sql`AND "domainId" = ${input.domain}` : Prisma.sql``}
${
input.search
? Prisma.sql`AND (
"subject" ILIKE ${`%${input.search}%`}
OR EXISTS (
SELECT 1 FROM unnest("to") AS email
WHERE email ILIKE ${`%${input.search}%`}
)
)`
: Prisma.sql``
}
ORDER BY "createdAt" DESC
LIMIT ${DEFAULT_QUERY_LIMIT}
OFFSET ${offset}
`;

return { emails };
}),

dashboard: teamProcedure
Expand Down

0 comments on commit bb0d541

Please sign in to comment.