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
116 changes: 116 additions & 0 deletions apps/web/app/(app)/bulk-unsubscribe/BulkActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { usePostHog } from "posthog-js/react";
import {
useBulkUnsubscribe,
useBulkApprove,
useBulkAutoArchive,
useBulkArchive,
useBulkDelete,
} from "@/app/(app)/bulk-unsubscribe/hooks";
import { PremiumTooltip, usePremium } from "@/components/PremiumAlert";
import { ButtonLoader } from "@/components/Loading";
import { Button } from "@/components/ui/button";
import { usePremiumModal } from "@/app/(app)/premium/PremiumModal";

export function BulkActions({
selected,
mutate,
}: {
selected: Map<string, boolean>;
mutate: () => Promise<any>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using any type.

The use of any type for the mutate function should be avoided. Specify a more specific type to enable better type checking and maintainability.

Apply this diff to replace any with a more specific type:

-  mutate: () => Promise<any>;
+  mutate: () => Promise<void>;
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mutate: () => Promise<any>;
mutate: () => Promise<void>;
Tools
Biome

[error] 16-16: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

}) {
Comment on lines +14 to +20
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using any type.

The use of any type for the mutate function should be avoided. Specify a more specific type to enable better type checking and maintainability.

Apply this diff to replace any with a more specific type:

-  mutate: () => Promise<any>;
+  mutate: () => Promise<void>;
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function BulkActions({
selected,
mutate,
}: {
selected: Map<string, boolean>;
mutate: () => Promise<any>;
}) {
export function BulkActions({
selected,
mutate,
}: {
selected: Map<string, boolean>;
mutate: () => Promise<void>;
}) {
Tools
Biome

[error] 17-17: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

const posthog = usePostHog();
const { hasUnsubscribeAccess, mutate: refetchPremium } = usePremium();
const { PremiumModal, openModal } = usePremiumModal();

const { bulkUnsubscribeLoading, onBulkUnsubscribe } = useBulkUnsubscribe({
hasUnsubscribeAccess,
mutate,
posthog,
refetchPremium,
});

const { bulkApproveLoading, onBulkApprove } = useBulkApprove({
mutate,
posthog,
});

const { bulkAutoArchiveLoading, onBulkAutoArchive } = useBulkAutoArchive({
hasUnsubscribeAccess,
mutate,
posthog,
refetchPremium,
});

const { onBulkArchive } = useBulkArchive({ mutate, posthog });

const { onBulkDelete } = useBulkDelete({ mutate, posthog });

const getSelectedValues = () =>
Array.from(selected.entries())
.filter(([_, value]) => value)
.map(([name, value]) => ({
name,
value,
}));

return (
<>
<PremiumTooltip showTooltip={!hasUnsubscribeAccess} openModal={openModal}>
<div className="flex items-center space-x-1.5">
<div>
<Button
size="sm"
variant="outline"
onClick={() => onBulkUnsubscribe(getSelectedValues())}
disabled={bulkUnsubscribeLoading}
>
{bulkUnsubscribeLoading && <ButtonLoader />}
Unsubscribe
</Button>
</div>
<div>
<Button
size="sm"
variant="outline"
onClick={() => onBulkAutoArchive(getSelectedValues())}
disabled={bulkAutoArchiveLoading}
>
{bulkAutoArchiveLoading && <ButtonLoader />}
Auto Archive
</Button>
</div>
<div>
<Button
size="sm"
variant="outline"
onClick={() => onBulkApprove(getSelectedValues())}
disabled={bulkApproveLoading}
>
{bulkApproveLoading && <ButtonLoader />}
Approve
</Button>
</div>
<div>
<Button
size="sm"
variant="outline"
onClick={() => onBulkArchive(getSelectedValues())}
>
Archive All
</Button>
</div>
<div>
<Button
size="sm"
variant="outline"
onClick={() => onBulkDelete(getSelectedValues())}
>
Delete All
</Button>
</div>
</div>
</PremiumTooltip>
<PremiumModal />
</>
);
}
28 changes: 24 additions & 4 deletions apps/web/app/(app)/bulk-unsubscribe/BulkUnsubscribeDesktop.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import React from "react";
import { ProgressBar } from "@tremor/react";
import {
Table,
TableBody,
Expand All @@ -11,20 +12,31 @@ import {
} from "@/components/ui/table";
import { ActionCell, HeaderButton } from "@/app/(app)/bulk-unsubscribe/common";
import { RowProps } from "@/app/(app)/bulk-unsubscribe/types";
import { ProgressBar } from "@tremor/react";
import { Checkbox } from "@/components/Checkbox";

export function BulkUnsubscribeDesktop(props: {
tableRows?: React.ReactNode;
sortColumn: "emails" | "unread" | "unarchived";
setSortColumn: (sortColumn: "emails" | "unread" | "unarchived") => void;
isAllSelected: boolean;
onToggleSelectAll: () => void;
}) {
const { tableRows, sortColumn, setSortColumn } = props;
const {
tableRows,
sortColumn,
setSortColumn,
isAllSelected,
onToggleSelectAll,
} = props;

return (
<Table>
<TableHeader>
<TableRow>
<TableHead className="pl-6">
<TableHead className="pr-0">
<Checkbox checked={isAllSelected} onChange={onToggleSelectAll} />
</TableHead>
<TableHead>
<span className="text-sm font-medium">From</span>
</TableHead>
<TableHead>
Expand Down Expand Up @@ -71,6 +83,8 @@ export function BulkUnsubscribeRowDesktop({
userGmailLabels,
openPremiumModal,
userEmail,
onToggleSelect,
checked,
}: RowProps) {
const readPercentage = (item.readEmails / item.value) * 100;
const archivedEmails = item.value - item.inboxEmails;
Expand All @@ -85,7 +99,13 @@ export function BulkUnsubscribeRowDesktop({
onMouseEnter={onSelectRow}
onDoubleClick={onDoubleClick}
>
<TableCell className="max-w-[250px] truncate pl-6 min-[1550px]:max-w-[300px] min-[1650px]:max-w-none">
<TableCell className="pr-0">
<Checkbox
checked={checked}
onChange={() => onToggleSelect?.(item.name)}
/>
</TableCell>
<TableCell className="max-w-[250px] truncate min-[1550px]:max-w-[300px] min-[1650px]:max-w-[400px]">
{item.name}
</TableCell>
<TableCell>{item.value}</TableCell>
Expand Down
10 changes: 5 additions & 5 deletions apps/web/app/(app)/bulk-unsubscribe/BulkUnsubscribeMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import React from "react";
import Link from "next/link";
import {
useUnsubscribeButton,
useUnsubscribe,
useApproveButton,
useArchiveAllButton,
} from "@/app/(app)/bulk-unsubscribe/common";
useArchiveAll,
} from "@/app/(app)/bulk-unsubscribe/hooks";
import {
Card,
CardContent,
Expand Down Expand Up @@ -58,14 +58,14 @@ export function BulkUnsubscribeRowMobile({
mutate,
posthog,
});
const { unsubscribeLoading, onUnsubscribe } = useUnsubscribeButton({
const { unsubscribeLoading, onUnsubscribe } = useUnsubscribe({
item,
hasUnsubscribeAccess,
mutate,
posthog,
refetchPremium,
});
const { archiveAllLoading, onArchiveAll } = useArchiveAllButton({
const { archiveAllLoading, onArchiveAll } = useArchiveAll({
item,
posthog,
});
Expand Down
41 changes: 28 additions & 13 deletions apps/web/app/(app)/bulk-unsubscribe/BulkUnsubscribeSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { usePremium } from "@/components/PremiumAlert";
import {
useNewsletterFilter,
useBulkUnsubscribeShortcuts,
} from "@/app/(app)/bulk-unsubscribe/common";
} from "@/app/(app)/bulk-unsubscribe/hooks";
import BulkUnsubscribeSummary from "@/app/(app)/bulk-unsubscribe/BulkUnsubscribeSummary";
import { useStatLoader } from "@/providers/StatLoaderProvider";
import { usePremiumModal } from "@/app/(app)/premium/PremiumModal";
Expand All @@ -38,6 +38,8 @@ import {
import { Card } from "@/components/ui/card";
import { ShortcutTooltip } from "@/app/(app)/bulk-unsubscribe/ShortcutTooltip";
import { SearchBar } from "@/app/(app)/bulk-unsubscribe/SearchBar";
import { useToggleSelect } from "@/hooks/useToggleSelect";
import { BulkActions } from "@/app/(app)/bulk-unsubscribe/BulkActions";

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

Expand Down Expand Up @@ -114,7 +116,7 @@ export function BulkUnsubscribeSection({
? BulkUnsubscribeRowMobile
: BulkUnsubscribeRowDesktop;

const tableRows = data?.newsletters
const rows = data?.newsletters
.filter(
search
? (item) =>
Expand All @@ -124,8 +126,13 @@ export function BulkUnsubscribeSection({
.includes(search.toLowerCase())
: Boolean,
)
.slice(0, expanded ? undefined : 50)
.map((item) => (
.slice(0, expanded ? undefined : 50);

const { selected, isAllSelected, onToggleSelect, onToggleSelectAll } =
useToggleSelect(rows?.map((item) => ({ id: item.name })) || []);

const tableRows = rows?.map((item) => {
return (
<RowComponent
key={item.name}
item={item}
Expand All @@ -134,24 +141,30 @@ export function BulkUnsubscribeSection({
userGmailLabels={userLabels}
mutate={mutate}
selected={selectedRow?.name === item.name}
onSelectRow={() => {
setSelectedRow(item);
}}
onSelectRow={() => setSelectedRow(item)}
onDoubleClick={() => onOpenNewsletter(item)}
hasUnsubscribeAccess={hasUnsubscribeAccess}
refetchPremium={refetchPremium}
openPremiumModal={openModal}
checked={selected.get(item.name) || false}
onToggleSelect={onToggleSelect}
/>
));
);
});

return (
<>
{!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-4 md:flex">
<Title className="hidden md:block">
Bulk unsubscribe from emails
</Title>
<Card className="mt-0 md:mt-4">
<div className="items-center justify-between px-2 pt-2 sm:px-4 md:flex">
{Array.from(selected.values()).filter(Boolean).length > 0 ? (
<BulkActions selected={selected} mutate={mutate} />
) : (
<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 />
Expand Down Expand Up @@ -223,6 +236,8 @@ export function BulkUnsubscribeSection({
sortColumn={sortColumn}
setSortColumn={setSortColumn}
tableRows={tableRows}
isAllSelected={isAllSelected}
onToggleSelectAll={onToggleSelectAll}
/>
)}
<div className="mt-2 px-6 pb-6">{extra}</div>
Expand Down
Loading