From a10ee6964bc0f9bb08a8feef19a9b06a03a0f60c Mon Sep 17 00:00:00 2001 From: pdelfan Date: Mon, 20 Apr 2026 16:53:37 -0700 Subject: [PATCH] feat: "not in collection" card filter --- .../[handle]/(withHeader)/cards/page.tsx | 9 ++-- .../components/cardFilters/CardFilters.tsx | 46 ++++++++++++++++++- .../CardsContainerContent.tsx | 10 +++- src/webapp/features/cards/lib/cardKeys.ts | 2 + src/webapp/features/cards/lib/dal.ts | 2 + .../features/cards/lib/queries/useCards.tsx | 3 ++ src/webapp/next-env.d.ts | 2 +- 7 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/webapp/app/(dashboard)/profile/[handle]/(withHeader)/cards/page.tsx b/src/webapp/app/(dashboard)/profile/[handle]/(withHeader)/cards/page.tsx index c851f5a3..cf34ba5d 100644 --- a/src/webapp/app/(dashboard)/profile/[handle]/(withHeader)/cards/page.tsx +++ b/src/webapp/app/(dashboard)/profile/[handle]/(withHeader)/cards/page.tsx @@ -6,6 +6,7 @@ import { CardFiltersSortSelect, CardFiltersViewToggle, CardFiltersTypeFilter, + CardFiltersUncollectedToggle, } from '@/features/cards/components/cardFilters/CardFilters'; import { CardSortField, UrlType } from '@semble/types'; import { Fragment } from 'react'; @@ -13,19 +14,20 @@ import { LinkButton } from '@/components/link/MantineLink'; interface Props { params: Promise<{ handle: string }>; - searchParams: Promise<{ sort?: string; type?: string }>; + searchParams: Promise<{ sort?: string; type?: string; uncollected?: string }>; } export default async function Page(props: Props) { - const [{ handle }, { sort, type }] = await Promise.all([ + const [{ handle }, { sort, type, uncollected }] = await Promise.all([ props.params, props.searchParams, ]); const resolvedSort = (sort as CardSortField) ?? CardSortField.UPDATED_AT; const resolvedType = (type as UrlType) ?? null; + const resolvedUncollected = uncollected === 'true'; - const suspenseKey = `${resolvedSort}-${resolvedType}`; + const suspenseKey = `${resolvedSort}-${resolvedType}-${resolvedUncollected}`; return ( @@ -35,6 +37,7 @@ export default async function Page(props: Props) { + (uncollectedFromUrl); + + const onChange = () => { + const next = !optimisticUncollected; + + startTransition(() => { + setOptimisticUncollected(next); + + const params = new URLSearchParams(ctx.searchParams.toString()); + if (next) { + params.set('uncollected', 'true'); + } else { + params.delete('uncollected'); + } + + ctx.router.replace(`?${params.toString()}`, { scroll: false }); + }); + }; + + return ( + + Status + } + rightSection={optimisticUncollected && } + onClick={onChange} + closeMenuOnClick={false} + > + Not in collection + + + ); +} + // view toggle export function CardFiltersViewToggle() { const { settings, updateSetting } = useUserSettings(); @@ -242,5 +285,6 @@ export const CardFilters = { Root: CardFiltersRoot, SortSelect: CardFiltersSortSelect, TypeFilter: CardFiltersTypeFilter, + UncollectedToggle: CardFiltersUncollectedToggle, ViewToggle: CardFiltersViewToggle, }; diff --git a/src/webapp/features/cards/containers/cardsContainerContent/CardsContainerContent.tsx b/src/webapp/features/cards/containers/cardsContainerContent/CardsContainerContent.tsx index fd3bd88c..a3085639 100644 --- a/src/webapp/features/cards/containers/cardsContainerContent/CardsContainerContent.tsx +++ b/src/webapp/features/cards/containers/cardsContainerContent/CardsContainerContent.tsx @@ -27,6 +27,7 @@ export default function CardsContainerContent(props: Props) { const searchParams = useSearchParams(); const selectedUrlType = searchParams.get('type') as UrlType; + const uncollected = searchParams.get('uncollected') === 'true'; const sortBy = (searchParams.get('sort') as CardSortField) ?? CardSortField.UPDATED_AT; @@ -43,6 +44,7 @@ export default function CardsContainerContent(props: Props) { sortBy: sortBy, sortOrder: getCardsSortParams(sortBy).sortOrder, urlType: selectedUrlType, + uncollected, }); const allCards = data?.pages.flatMap((page) => page.cards ?? []) ?? []; @@ -59,7 +61,13 @@ export default function CardsContainerContent(props: Props) { return ( diff --git a/src/webapp/features/cards/lib/cardKeys.ts b/src/webapp/features/cards/lib/cardKeys.ts index 67b1e853..f36348d0 100644 --- a/src/webapp/features/cards/lib/cardKeys.ts +++ b/src/webapp/features/cards/lib/cardKeys.ts @@ -14,6 +14,7 @@ export const cardKeys = { sortBy?: CardSortField, sortOrder?: SortOrder, urlType?: UrlType, + uncollected?: boolean, ) => [ ...cardKeys.all(), 'infinite', @@ -22,6 +23,7 @@ export const cardKeys = { sortBy, sortOrder, urlType, + uncollected, ], urlMetadata: (url: string, options?: { includeStats?: boolean }) => [...cardKeys.all(), 'metadata', url, options] as const, diff --git a/src/webapp/features/cards/lib/dal.ts b/src/webapp/features/cards/lib/dal.ts index 46b8fd9d..8057febe 100644 --- a/src/webapp/features/cards/lib/dal.ts +++ b/src/webapp/features/cards/lib/dal.ts @@ -14,6 +14,7 @@ interface PageParams { cardSortBy?: CardSortField; cardSortOrder?: SortOrder; urlType?: UrlType; + uncollected?: boolean; } export const getUrlMetadata = cache(async (params: GetUrlMetadataParams) => { @@ -89,6 +90,7 @@ export const getUrlCards = cache( sortBy: params?.cardSortBy, sortOrder: params?.cardSortOrder, urlType: params?.urlType, + uncollected: params?.uncollected, }); return response; diff --git a/src/webapp/features/cards/lib/queries/useCards.tsx b/src/webapp/features/cards/lib/queries/useCards.tsx index ba80961b..ca6f56fc 100644 --- a/src/webapp/features/cards/lib/queries/useCards.tsx +++ b/src/webapp/features/cards/lib/queries/useCards.tsx @@ -9,6 +9,7 @@ interface Props { sortBy?: CardSortField; sortOrder?: SortOrder; urlType?: UrlType; + uncollected?: boolean; } export default function useCards(props: Props) { @@ -21,6 +22,7 @@ export default function useCards(props: Props) { props.sortBy, props.sortOrder, props.urlType, + props.uncollected, ), initialPageParam: 1, queryFn: ({ pageParam = 1 }) => { @@ -30,6 +32,7 @@ export default function useCards(props: Props) { cardSortBy: props.sortBy, cardSortOrder: props.sortOrder, urlType: props.urlType, + uncollected: props.uncollected, }); }, getNextPageParam: (lastPage) => { diff --git a/src/webapp/next-env.d.ts b/src/webapp/next-env.d.ts index 9edff1c7..c4b7818f 100644 --- a/src/webapp/next-env.d.ts +++ b/src/webapp/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. -- 2.51.2