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 @@ -25,13 +25,15 @@ import {
export function BulkUnsubscribeDesktop({
tableRows,
sortColumn,
setSortColumn,
sortDirection,
onSort,
isAllSelected,
onToggleSelectAll,
}: {
tableRows?: React.ReactNode;
sortColumn: "emails" | "unread" | "unarchived";
setSortColumn: (sortColumn: "emails" | "unread" | "unarchived") => void;
sortDirection: "asc" | "desc";
onSort: (column: "emails" | "unread" | "unarchived") => void;
isAllSelected: boolean;
onToggleSelectAll: () => void;
}) {
Expand All @@ -48,23 +50,32 @@ export function BulkUnsubscribeDesktop({
<TableHead>
<HeaderButton
sorted={sortColumn === "emails"}
onClick={() => setSortColumn("emails")}
sortDirection={
sortColumn === "emails" ? sortDirection : undefined
}
onClick={() => onSort("emails")}
>
Emails
</HeaderButton>
</TableHead>
<TableHead>
<HeaderButton
sorted={sortColumn === "unread"}
onClick={() => setSortColumn("unread")}
sortDirection={
sortColumn === "unread" ? sortDirection : undefined
}
onClick={() => onSort("unread")}
>
Read
</HeaderButton>
</TableHead>
<TableHead>
<HeaderButton
sorted={sortColumn === "unarchived"}
onClick={() => setSortColumn("unarchived")}
sortDirection={
sortColumn === "unarchived" ? sortDirection : undefined
}
onClick={() => onSort("unarchived")}
>
Archived
</HeaderButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,33 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import useSWR from "swr";
import { subDays } from "date-fns/subDays";
import { ChevronDown } from "lucide-react";
import { usePostHog } from "posthog-js/react";
import { ArchiveIcon, FilterIcon } from "lucide-react";
import {
ArchiveIcon,
BadgeCheckIcon,
CheckIcon,
ChevronsDownIcon,
ChevronsUpIcon,
InboxIcon,
ListIcon,
MailMinusIcon,
} from "lucide-react";
import type { DateRange } from "react-day-picker";
import { LoadingContent } from "@/components/LoadingContent";
import { Skeleton } from "@/components/ui/skeleton";
import type {
NewsletterStatsQuery,
NewsletterStatsResponse,
} from "@/app/api/user/stats/newsletters/route";
import { useExpanded } from "@/app/(app)/[emailAccountId]/stats/useExpanded";
import { getDateRangeParams } from "@/app/(app)/[emailAccountId]/stats/params";
import { NewsletterModal } from "@/app/(app)/[emailAccountId]/stats/NewsletterModal";
import { useEmailsToIncludeFilter } from "@/app/(app)/[emailAccountId]/stats/EmailsToIncludeFilter";
import { DetailedStatsFilter } from "@/app/(app)/[emailAccountId]/stats/DetailedStatsFilter";
import { usePremium } from "@/components/PremiumAlert";
import {
useNewsletterFilter,
useBulkUnsubscribeShortcuts,
type NewsletterFilterType,
} from "@/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks";
import { useStatLoader } from "@/providers/StatLoaderProvider";
import { usePremiumModal } from "@/app/(app)/premium/PremiumModal";
Expand All @@ -39,7 +48,6 @@ import { useToggleSelect } from "@/hooks/useToggleSelect";
import { BulkActions } from "@/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions";
import { ArchiveProgress } from "@/app/(app)/[emailAccountId]/bulk-unsubscribe/ArchiveProgress";
import { ClientOnly } from "@/components/ClientOnly";
import { Toggle } from "@/components/Toggle";
import { useAccount } from "@/providers/EmailAccountProvider";
import { useWindowSize } from "usehooks-ts";
import { LoadStatsButton } from "@/app/(app)/[emailAccountId]/stats/LoadStatsButton";
Expand All @@ -49,6 +57,46 @@ import { TextLink } from "@/components/Typography";
import { DismissibleVideoCard } from "@/components/VideoCard";
import { ActionBar } from "@/app/(app)/[emailAccountId]/stats/ActionBar";
import { DatePickerWithRange } from "@/components/DatePickerWithRange";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

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

const filterOptions: {
label: string;
value: NewsletterFilterType;
icon: React.ReactNode;
separatorAfter?: boolean;
}[] = [
{ label: "All", value: "all", icon: <ListIcon className="size-4" /> },
{
label: "Unhandled",
value: "unhandled",
icon: <InboxIcon className="size-4" />,
separatorAfter: true,
},
{
label: "Unsubscribed",
value: "unsubscribed",
icon: <MailMinusIcon className="size-4" />,
},
{
label: "Skip Inbox",
value: "autoArchived",
icon: <ArchiveIcon className="size-4" />,
},
{
label: "Approved",
value: "approved",
icon: <BadgeCheckIcon className="size-4" />,
},
];

const selectOptions = [
{ label: "Last week", value: "7" },
Expand All @@ -59,8 +107,6 @@ const selectOptions = [
];
const defaultSelected = selectOptions[2];

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

export function BulkUnsubscribe() {
const windowSize = useWindowSize();
const isMobile = windowSize.width < 768;
Expand Down Expand Up @@ -104,19 +150,35 @@ export function BulkUnsubscribe() {
const [sortColumn, setSortColumn] = useState<
"emails" | "unread" | "unarchived"
>("emails");
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");

const handleSort = useCallback(
(column: "emails" | "unread" | "unarchived") => {
if (sortColumn === column) {
// Toggle direction if clicking the same column
setSortDirection((prev) => (prev === "desc" ? "asc" : "desc"));
} else {
// Set new column with default desc direction
setSortColumn(column);
setSortDirection("desc");
}
},
[sortColumn],
);

const { typesArray } = useEmailsToIncludeFilter();
const { filtersArray, filters, setFilters } = useNewsletterFilter();
const { filtersArray, filter, setFilter } = useNewsletterFilter();
const posthog = usePostHog();

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

const { expanded, extra } = useExpanded();
const [expanded, setExpanded] = useState(false);

const params: NewsletterStatsQuery = {
types: typesArray,
filters: filtersArray,
orderBy: sortColumn,
orderDirection: sortDirection,
limit: expanded ? 500 : 50,
includeMissingUnsubscribe: true,
...getDateRangeParams(dateRange),
Expand Down Expand Up @@ -203,11 +265,7 @@ export function BulkUnsubscribe() {
);
});

const onlyUnhandled =
filters.unhandled &&
!filters.autoArchived &&
!filters.unsubscribed &&
!filters.approved;
const selectedFilter = filterOptions.find((opt) => opt.value === filter);

return (
<PageWrapper>
Expand Down Expand Up @@ -247,96 +305,42 @@ export function BulkUnsubscribe() {

<div className="items-center justify-between flex mt-4 flex-wrap">
<ActionBar rightContent={<LoadStatsButton />}>
<div className="flex items-center justify-end gap-1">
<div className="">
<Toggle
name="show-unhandled"
label="Only unhandled"
enabled={onlyUnhandled}
onChange={() =>
setFilters(
onlyUnhandled
? {
unhandled: true,
autoArchived: true,
unsubscribed: true,
approved: true,
}
: {
unhandled: true,
autoArchived: false,
unsubscribed: false,
approved: false,
},
)
}
/>
</div>
</div>
<SearchBar onSearch={setSearch} />
<DetailedStatsFilter
label="Filter"
icon={<FilterIcon className="mr-2 h-4 w-4" />}
keepOpenOnSelect
columns={[
{
label: "All",
separatorAfter: true,
checked:
filters.approved &&
filters.autoArchived &&
filters.unsubscribed &&
filters.unhandled,
setChecked: () =>
setFilters({
approved: true,
autoArchived: true,
unsubscribed: true,
unhandled: true,
}),
},
{
label: "Unhandled",
checked: filters.unhandled,
setChecked: () =>
setFilters({
...filters,
unhandled: !filters.unhandled,
}),
},
{
label: "Unsubscribed",
checked: filters.unsubscribed,
setChecked: () =>
setFilters({
...filters,
unsubscribed: !filters.unsubscribed,
}),
},
{
label: "Skip Inbox",
checked: filters.autoArchived,
setChecked: () =>
setFilters({
...filters,
autoArchived: !filters.autoArchived,
}),
},
{
label: "Approved",
checked: filters.approved,
setChecked: () =>
setFilters({ ...filters, approved: !filters.approved }),
},
]}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="h-10">
{selectedFilter?.icon}
<span className="ml-2">{selectedFilter?.label ?? "All"}</span>
<ChevronDown className="ml-2 h-4 w-4 text-gray-400" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[170px]">
{filterOptions.map((option) => (
<div key={option.value}>
<DropdownMenuItem
onClick={() => setFilter(option.value)}
className="flex items-center justify-between"
>
<span className="flex items-center gap-2">
{option.icon}
{option.label}
</span>
{filter === option.value && (
<CheckIcon className="h-4 w-4 text-primary" />
)}
</DropdownMenuItem>
{option.separatorAfter && <DropdownMenuSeparator />}
</div>
))}
</DropdownMenuContent>
</DropdownMenu>
<DatePickerWithRange
dateRange={dateRange}
onSetDateRange={setDateRange}
selectOptions={selectOptions}
dateDropdown={dateDropdown}
onSetDateDropdown={onSetDateDropdown}
/>
<SearchBar onSearch={setSearch} />
</ActionBar>
</div>

Expand Down Expand Up @@ -370,18 +374,40 @@ export function BulkUnsubscribe() {
) : (
<BulkUnsubscribeDesktop
sortColumn={sortColumn}
setSortColumn={setSortColumn}
sortDirection={sortDirection}
onSort={handleSort}
tableRows={tableRows}
isAllSelected={isAllSelected}
onToggleSelectAll={onToggleSelectAll}
/>
)}
<div className="mt-2 px-6 pb-6">{extra}</div>
{/* Only show expand/collapse when there might be more results */}
{(expanded || (rows && rows.length >= 50)) && (
<div className="mt-2 px-6 pb-6">
<Button
variant="outline"
size="sm"
onClick={() => setExpanded(!expanded)}
className="w-full"
>
{expanded ? (
<>
<ChevronsUpIcon className="h-4 w-4" />
<span className="ml-2">Show less</span>
</>
) : (
<>
<ChevronsDownIcon className="h-4 w-4" />
<span className="ml-2">Show more</span>
</>
)}
</Button>
</div>
)}
</>
) : (
<p className="max-w-prose space-y-4 p-4 text-muted-foreground">
No emails found. To see more, adjust the filter options or click
the "Load More" button.
<p className="space-y-4 p-4 text-muted-foreground">
No emails found. Adjust the filters, or click "Load More".
</p>
)}
</LoadingContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function SearchBar({
<SearchIcon className="size-5" />
</Button>
{showSearch && (
<form>
<form onSubmit={(e) => e.preventDefault()}>
<Input
type="text"
name="search"
Expand Down
Loading
Loading