diff options
| author | Fuwn <[email protected]> | 2026-02-08 00:06:53 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 00:06:53 -0800 |
| commit | 2f3acb221af8cac85d3653ce8e7c7c30eb73ea77 (patch) | |
| tree | 92ceab132fb4b6583cfaff46f5ac1adaed0a7d9e /apps | |
| parent | fix: enforce same-origin on all service worker cache routes (diff) | |
| download | asa.news-2f3acb221af8cac85d3653ce8e7c7c30eb73ea77.tar.xz asa.news-2f3acb221af8cac85d3653ce8e7c7c30eb73ea77.zip | |
feat: display folders above ungrouped feeds in sidebar, add delete-all-custom-feeds to danger zone
Diffstat (limited to 'apps')
3 files changed, 107 insertions, 39 deletions
diff --git a/apps/web/app/reader/_components/sidebar-content.tsx b/apps/web/app/reader/_components/sidebar-content.tsx index 4b58934..844f8b3 100644 --- a/apps/web/app/reader/_components/sidebar-content.tsx +++ b/apps/web/app/reader/_components/sidebar-content.tsx @@ -214,45 +214,6 @@ export function SidebarContent() { </div> )} - {ungroupedSubscriptions.length > 0 && ( - <div className="mt-3 space-y-0.5"> - {ungroupedSubscriptions.map((subscription) => ( - <Link - key={subscription.subscriptionIdentifier} - href={`/reader?feed=${subscription.feedIdentifier}`} - data-sidebar-nav-item - {...((unreadCounts?.[subscription.feedIdentifier] ?? 0) > 0 ? { "data-has-unreads": "" } : {})} - onClick={closeSidebarOnMobile} - className={classNames( - NAVIGATION_LINK_CLASS, - "flex items-center truncate pl-4 text-[0.85em]", - activeFeedIdentifier === subscription.feedIdentifier && - ACTIVE_LINK_CLASS, - sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) - )} - > - {showFeedFavicons && ( - <FeedFavicon feedUrl={subscription.feedUrl} /> - )} - <span className={classNames("truncate", showFeedFavicons && "ml-2")}> - {displayNameForSubscription(subscription)} - </span> - {subscription.feedType === "podcast" && ( - <span className="ml-1 shrink-0 text-text-dim" title="podcast">♫</span> - )} - {subscription.consecutiveFailures > 0 && ( - <span className="ml-1 shrink-0 text-status-warning" title={subscription.lastFetchError ?? "feed error"}> - [!] - </span> - )} - <UnreadBadge - count={unreadCounts?.[subscription.feedIdentifier] ?? 0} - /> - </Link> - ))} - </div> - )} - {folders.map((folder) => { const isExpanded = expandedFolderIdentifiers.includes( folder.folderIdentifier @@ -347,6 +308,45 @@ export function SidebarContent() { ) })} + {ungroupedSubscriptions.length > 0 && ( + <div className="mt-3 space-y-0.5"> + {ungroupedSubscriptions.map((subscription) => ( + <Link + key={subscription.subscriptionIdentifier} + href={`/reader?feed=${subscription.feedIdentifier}`} + data-sidebar-nav-item + {...((unreadCounts?.[subscription.feedIdentifier] ?? 0) > 0 ? { "data-has-unreads": "" } : {})} + onClick={closeSidebarOnMobile} + className={classNames( + NAVIGATION_LINK_CLASS, + "flex items-center truncate pl-4 text-[0.85em]", + activeFeedIdentifier === subscription.feedIdentifier && + ACTIVE_LINK_CLASS, + sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) + )} + > + {showFeedFavicons && ( + <FeedFavicon feedUrl={subscription.feedUrl} /> + )} + <span className={classNames("truncate", showFeedFavicons && "ml-2")}> + {displayNameForSubscription(subscription)} + </span> + {subscription.feedType === "podcast" && ( + <span className="ml-1 shrink-0 text-text-dim" title="podcast">♫</span> + )} + {subscription.consecutiveFailures > 0 && ( + <span className="ml-1 shrink-0 text-status-warning" title={subscription.lastFetchError ?? "feed error"}> + [!] + </span> + )} + <UnreadBadge + count={unreadCounts?.[subscription.feedIdentifier] ?? 0} + /> + </Link> + ))} + </div> + )} + <div className="mt-3 space-y-0.5"> <button type="button" diff --git a/apps/web/app/reader/settings/_components/danger-zone-settings.tsx b/apps/web/app/reader/settings/_components/danger-zone-settings.tsx index 3525426..eb6c008 100644 --- a/apps/web/app/reader/settings/_components/danger-zone-settings.tsx +++ b/apps/web/app/reader/settings/_components/danger-zone-settings.tsx @@ -5,14 +5,17 @@ import { useRouter } from "next/navigation" import { useMutation } from "@tanstack/react-query" import { useUnsubscribeAll } from "@/lib/queries/use-subscription-mutations" import { useDeleteAllFolders } from "@/lib/queries/use-folder-mutations" +import { useDeleteAllCustomFeeds } from "@/lib/queries/use-custom-feed-mutations" import { notify } from "@/lib/notify" export function DangerZoneSettings() { const router = useRouter() const unsubscribeAll = useUnsubscribeAll() const deleteAllFolders = useDeleteAllFolders() + const deleteAllCustomFeeds = useDeleteAllCustomFeeds() const [showDeleteSubsConfirm, setShowDeleteSubsConfirm] = useState(false) const [showDeleteFoldersConfirm, setShowDeleteFoldersConfirm] = useState(false) + const [showDeleteCustomFeedsConfirm, setShowDeleteCustomFeedsConfirm] = useState(false) const [showDeleteAccountConfirm, setShowDeleteAccountConfirm] = useState(false) const [deleteConfirmText, setDeleteConfirmText] = useState("") @@ -105,6 +108,41 @@ export function DangerZoneSettings() { )} </div> + <div className="mb-6"> + <h3 className="mb-2 text-text-primary">delete all custom feeds</h3> + <p className="mb-2 text-text-dim"> + remove all custom feeds (saved searches). + </p> + {showDeleteCustomFeedsConfirm ? ( + <div className="flex items-center gap-2"> + <span className="text-status-error">are you sure?</span> + <button + onClick={() => { + deleteAllCustomFeeds.mutate() + setShowDeleteCustomFeedsConfirm(false) + }} + disabled={deleteAllCustomFeeds.isPending} + className="border border-status-error px-3 py-1 text-status-error transition-colors hover:bg-status-error hover:text-background-primary disabled:opacity-50" + > + yes, delete all + </button> + <button + onClick={() => setShowDeleteCustomFeedsConfirm(false)} + className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary" + > + cancel + </button> + </div> + ) : ( + <button + onClick={() => setShowDeleteCustomFeedsConfirm(true)} + className="border border-border px-3 py-1 text-text-secondary transition-colors hover:border-status-error hover:text-status-error" + > + delete all custom feeds + </button> + )} + </div> + <div> <h3 className="mb-2 text-text-primary">delete account</h3> <p className="mb-2 text-text-dim"> diff --git a/apps/web/lib/queries/use-custom-feed-mutations.ts b/apps/web/lib/queries/use-custom-feed-mutations.ts index 0afa19c..66c2a19 100644 --- a/apps/web/lib/queries/use-custom-feed-mutations.ts +++ b/apps/web/lib/queries/use-custom-feed-mutations.ts @@ -98,6 +98,36 @@ export function useUpdateCustomFeed() { }) } +export function useDeleteAllCustomFeeds() { + const supabaseClient = createSupabaseBrowserClient() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async () => { + const { + data: { user }, + } = await supabaseClient.auth.getUser() + + if (!user) throw new Error("not authenticated") + + const { error } = await supabaseClient + .from("custom_feeds") + .delete() + .eq("user_id", user.id) + + if (error) throw error + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.customFeeds.all }) + queryClient.invalidateQueries({ queryKey: queryKeys.userProfile.all }) + notify("all custom feeds deleted") + }, + onError: (error: Error) => { + notify("failed to delete custom feeds: " + error.message) + }, + }) +} + export function useDeleteCustomFeed() { const supabaseClient = createSupabaseBrowserClient() const queryClient = useQueryClient() |