Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function BulkUnsubscribeDesktop(props: {
const { tableRows, sortColumn, setSortColumn } = props;

return (
<Table className="mt-4">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell className="pl-6">
Expand Down Expand Up @@ -67,7 +67,7 @@ export function BulkUnsubscribeRowDesktop({
onDoubleClick,
hasUnsubscribeAccess,
mutate,
setOpenedNewsletter,
onOpenNewsletter,
userGmailLabels,
openPremiumModal,
userEmail,
Expand Down Expand Up @@ -121,7 +121,7 @@ export function BulkUnsubscribeRowDesktop({
hasUnsubscribeAccess={hasUnsubscribeAccess}
mutate={mutate}
refetchPremium={refetchPremium}
setOpenedNewsletter={setOpenedNewsletter}
onOpenNewsletter={onOpenNewsletter}
selected={selected}
userGmailLabels={userGmailLabels}
openPremiumModal={openPremiumModal}
Expand Down
15 changes: 4 additions & 11 deletions apps/web/app/(app)/bulk-unsubscribe/BulkUnsubscribeMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function BulkUnsubscribeRowMobile({
refetchPremium,
mutate,
hasUnsubscribeAccess,
setOpenedNewsletter,
onOpenNewsletter,
}: RowProps) {
const readPercentage = (item.readEmails / item.value) * 100;
const archivedEmails = item.value - item.inboxEmails;
Expand Down Expand Up @@ -117,19 +117,12 @@ export function BulkUnsubscribeRowMobile({
variant={
item.status === NewsletterStatus.UNSUBSCRIBED ? "red" : "default"
}
disabled={!item.lastUnsubscribeLink}
asChild={!!item.lastUnsubscribeLink}
>
<Link
className={
hasUnsubscribeAccess
? undefined
: "pointer-events-none opacity-50"
}
href={
hasUnsubscribeAccess
? (cleanUnsubscribeLink(item.lastUnsubscribeLink ?? "#") ??
"#")
hasUnsubscribeAccess && item.lastUnsubscribeLink
? cleanUnsubscribeLink(item.lastUnsubscribeLink) || "#"
: "#"
}
target="_blank"
Expand Down Expand Up @@ -159,7 +152,7 @@ export function BulkUnsubscribeRowMobile({
<Button
size="sm"
variant="secondary"
onClick={() => setOpenedNewsletter(item)}
onClick={() => onOpenNewsletter(item)}
>
<MoreVerticalIcon className="mr-2 size-4" />
More
Expand Down
59 changes: 35 additions & 24 deletions apps/web/app/(app)/bulk-unsubscribe/BulkUnsubscribeSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React, { useState } from "react";
import { useSession } from "next-auth/react";
import useSWR from "swr";
import { usePostHog } from "posthog-js/react";
import { FilterIcon } from "lucide-react";
import { Title } from "@tremor/react";
import type { DateRange } from "react-day-picker";
Expand All @@ -25,7 +26,6 @@ import {
import BulkUnsubscribeSummary from "@/app/(app)/bulk-unsubscribe/BulkUnsubscribeSummary";
import { useStatLoader } from "@/providers/StatLoaderProvider";
import { usePremiumModal } from "@/app/(app)/premium/PremiumModal";
import { Toggle } from "@/components/Toggle";
import { useLabels } from "@/hooks/useLabels";
import {
BulkUnsubscribeMobile,
Expand All @@ -37,6 +37,7 @@ import {
} from "@/app/(app)/bulk-unsubscribe/BulkUnsubscribeDesktop";
import { Card } from "@/components/ui/card";
import { ShortcutTooltip } from "@/app/(app)/bulk-unsubscribe/ShortcutTooltip";
import { SearchBar } from "@/app/(app)/bulk-unsubscribe/SearchBar";

type Newsletter = NewsletterStatsResponse["newsletters"][number];

Expand All @@ -58,15 +59,14 @@ export function BulkUnsubscribeSection({

const { typesArray } = useEmailsToIncludeFilter();
const { filtersArray, filters, setFilters } = useNewsletterFilter();
const [includeMissingUnsubscribe, setIncludeMissingUnsubscribe] =
useState(false);
const posthog = usePostHog();

const params: NewsletterStatsQuery = {
types: typesArray,
filters: filtersArray,
orderBy: sortColumn,
limit: 100,
includeMissingUnsubscribe,
includeMissingUnsubscribe: true,
...getDateRangeParams(dateRange),
};
const urlParams = new URLSearchParams(params as any);
Expand All @@ -83,20 +83,27 @@ export function BulkUnsubscribeSection({
const { expanded, extra } = useExpanded();
const [openedNewsletter, setOpenedNewsletter] = React.useState<Newsletter>();

const onOpenNewsletter = (newsletter: Newsletter) => {
setOpenedNewsletter(newsletter);
posthog?.capture("Clicked Expand Sender");
};

const [selectedRow, setSelectedRow] = React.useState<
Newsletter | undefined
>();

useBulkUnsubscribeShortcuts({
newsletters: data?.newsletters,
selectedRow,
setOpenedNewsletter,
onOpenNewsletter,
setSelectedRow,
refetchPremium,
hasUnsubscribeAccess,
mutate,
});

const [search, setSearch] = useState("");

const { isLoading: isStatsLoading } = useStatLoader();

const { userLabels } = useLabels();
Expand All @@ -108,20 +115,29 @@ export function BulkUnsubscribeSection({
: BulkUnsubscribeRowDesktop;

const tableRows = data?.newsletters
.filter(
search
? (item) =>
item.name.toLowerCase().includes(search.toLowerCase()) ||
item.lastUnsubscribeLink
?.toLowerCase()
.includes(search.toLowerCase())
: Boolean,
)
.slice(0, expanded ? undefined : 50)
.map((item) => (
<RowComponent
key={item.name}
item={item}
userEmail={userEmail}
setOpenedNewsletter={setOpenedNewsletter}
onOpenNewsletter={onOpenNewsletter}
userGmailLabels={userLabels}
mutate={mutate}
selected={selectedRow?.name === item.name}
onSelectRow={() => {
setSelectedRow(item);
}}
onDoubleClick={() => setOpenedNewsletter(item)}
onDoubleClick={() => onOpenNewsletter(item)}
hasUnsubscribeAccess={hasUnsubscribeAccess}
refetchPremium={refetchPremium}
openPremiumModal={openModal}
Expand All @@ -132,21 +148,16 @@ export function BulkUnsubscribeSection({
<>
{!isMobile && <BulkUnsubscribeSummary />}
<Card className="mt-0 p-0 md:mt-4">
<div className="items-center justify-between px-2 pt-2 sm:px-6 sm:pt-6 md:flex">
<Title className="hidden md:block">Bulk Unsubscribe</Title>
<div className="mt-2 flex flex-wrap items-center justify-end gap-1 sm:gap-2 md:mt-0 lg:flex-nowrap">
<div className="items-center justify-between px-2 pt-2 sm:px-6 sm:pt-4 md:flex">
<Title className="hidden md:block">
Bulk unsubscribe from emails
</Title>
<div className="mt-2 flex flex-wrap items-center justify-end gap-1 md:mt-0 lg:flex-nowrap">
<div className="hidden md:block">
<ShortcutTooltip />
</div>

<Toggle
label="Missing unsubscribe"
name="missing-unsubscribe"
enabled={includeMissingUnsubscribe}
onChange={() => {
setIncludeMissingUnsubscribe(!includeMissingUnsubscribe);
}}
/>
<SearchBar onSearch={setSearch} />

<DetailedStatsFilter
label="Filter"
Expand All @@ -163,21 +174,21 @@ export function BulkUnsubscribeSection({
}),
},
{
label: "Auto Archived",
checked: filters.autoArchived,
label: "Unsubscribed",
checked: filters.unsubscribed,
setChecked: () =>
setFilters({
...filters,
["autoArchived"]: !filters.autoArchived,
["unsubscribed"]: !filters.unsubscribed,
}),
},
{
label: "Unsubscribed",
checked: filters.unsubscribed,
label: "Auto Archived",
checked: filters.autoArchived,
setChecked: () =>
setFilters({
...filters,
["unsubscribed"]: !filters.unsubscribed,
["autoArchived"]: !filters.autoArchived,
}),
},
{
Expand Down
64 changes: 64 additions & 0 deletions apps/web/app/(app)/bulk-unsubscribe/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import { z } from "zod";
import { SearchIcon } from "lucide-react";
import { useCallback, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import throttle from "lodash/throttle";
import { Input } from "@/components/Input";
import { Button } from "@/components/ui/button";

const searchSchema = z.object({ search: z.string() });

export function SearchBar({
onSearch,
}: {
onSearch: (search: string) => void;
}) {
const [showSearch, setShowSearch] = useState(false);

const {
register,
formState: { errors },
watch,
} = useForm<z.infer<typeof searchSchema>>({
resolver: zodResolver(searchSchema),
defaultValues: { search: "" },
});

const throttledSearch = useCallback(
throttle((value: string) => {
onSearch(value.trim());
}, 300),
[onSearch],
);

watch((data) => {
if (data.search !== undefined) {
throttledSearch(data.search);
}
});
return (
<>
<Button
variant="ghost"
size="icon"
onClick={() => setShowSearch(!showSearch)}
>
<SearchIcon className="size-5" />
</Button>
{showSearch && (
<form>
<Input
type="text"
name="search"
placeholder="Search"
registerProps={register("search", { required: true })}
error={errors.search}
/>
</form>
)}
</>
);
}
4 changes: 2 additions & 2 deletions apps/web/app/(app)/bulk-unsubscribe/ShortcutTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export function ShortcutTooltip() {
</div>
}
>
<Button size="icon" variant="link">
<SquareSlashIcon className="h-5 w-5" />
<Button size="icon" variant="ghost">
<SquareSlashIcon className="size-5" />
</Button>
</Tooltip>
);
Expand Down
Loading