This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: "not in collection" card filter

+68 -6
+6 -3
src/webapp/app/(dashboard)/profile/[handle]/(withHeader)/cards/page.tsx
··· 6 6 CardFiltersSortSelect, 7 7 CardFiltersViewToggle, 8 8 CardFiltersTypeFilter, 9 + CardFiltersUncollectedToggle, 9 10 } from '@/features/cards/components/cardFilters/CardFilters'; 10 11 import { CardSortField, UrlType } from '@semble/types'; 11 12 import { Fragment } from 'react'; ··· 13 14 14 15 interface Props { 15 16 params: Promise<{ handle: string }>; 16 - searchParams: Promise<{ sort?: string; type?: string }>; 17 + searchParams: Promise<{ sort?: string; type?: string; uncollected?: string }>; 17 18 } 18 19 19 20 export default async function Page(props: Props) { 20 - const [{ handle }, { sort, type }] = await Promise.all([ 21 + const [{ handle }, { sort, type, uncollected }] = await Promise.all([ 21 22 props.params, 22 23 props.searchParams, 23 24 ]); 24 25 25 26 const resolvedSort = (sort as CardSortField) ?? CardSortField.UPDATED_AT; 26 27 const resolvedType = (type as UrlType) ?? null; 28 + const resolvedUncollected = uncollected === 'true'; 27 29 28 - const suspenseKey = `${resolvedSort}-${resolvedType}`; 30 + const suspenseKey = `${resolvedSort}-${resolvedType}-${resolvedUncollected}`; 29 31 30 32 return ( 31 33 <Fragment> ··· 35 37 <CardFiltersSortSelect /> 36 38 <CardFiltersViewToggle /> 37 39 <CardFiltersTypeFilter /> 40 + <CardFiltersUncollectedToggle /> 38 41 </CardFiltersRoot> 39 42 <LinkButton 40 43 href={`/search/cards?handle=${handle}`}
+45 -1
src/webapp/features/cards/components/cardFilters/CardFilters.tsx
··· 14 14 import { upperFirst } from '@mantine/hooks'; 15 15 import { CardSortField, UrlType } from '@semble/types'; 16 16 import { getUrlTypeIcon } from '@/lib/utils/icon'; 17 - import { MdFilterList } from 'react-icons/md'; 17 + import { MdFilterList, MdOutlineInbox } from 'react-icons/md'; 18 18 import { BsGrid, BsListUl } from 'react-icons/bs'; 19 19 import { CiGrid2H } from 'react-icons/ci'; 20 20 import { useUserSettings } from '@/features/settings/lib/queries/useUserSettings'; ··· 204 204 } 205 205 206 206 // view toggle 207 + // uncollected toggle 208 + export function CardFiltersUncollectedToggle() { 209 + const ctx = useFilterContext(); 210 + const [, startTransition] = useTransition(); 211 + 212 + const uncollectedFromUrl = ctx.searchParams.get('uncollected') === 'true'; 213 + 214 + const [optimisticUncollected, setOptimisticUncollected] = 215 + useOptimistic<boolean>(uncollectedFromUrl); 216 + 217 + const onChange = () => { 218 + const next = !optimisticUncollected; 219 + 220 + startTransition(() => { 221 + setOptimisticUncollected(next); 222 + 223 + const params = new URLSearchParams(ctx.searchParams.toString()); 224 + if (next) { 225 + params.set('uncollected', 'true'); 226 + } else { 227 + params.delete('uncollected'); 228 + } 229 + 230 + ctx.router.replace(`?${params.toString()}`, { scroll: false }); 231 + }); 232 + }; 233 + 234 + return ( 235 + <Fragment> 236 + <Menu.Label>Status</Menu.Label> 237 + <Menu.Item 238 + leftSection={<MdOutlineInbox />} 239 + rightSection={optimisticUncollected && <IoMdCheckmark />} 240 + onClick={onChange} 241 + closeMenuOnClick={false} 242 + > 243 + Not in collection 244 + </Menu.Item> 245 + </Fragment> 246 + ); 247 + } 248 + 249 + // view toggle 207 250 export function CardFiltersViewToggle() { 208 251 const { settings, updateSetting } = useUserSettings(); 209 252 ··· 242 285 Root: CardFiltersRoot, 243 286 SortSelect: CardFiltersSortSelect, 244 287 TypeFilter: CardFiltersTypeFilter, 288 + UncollectedToggle: CardFiltersUncollectedToggle, 245 289 ViewToggle: CardFiltersViewToggle, 246 290 };
+9 -1
src/webapp/features/cards/containers/cardsContainerContent/CardsContainerContent.tsx
··· 27 27 28 28 const searchParams = useSearchParams(); 29 29 const selectedUrlType = searchParams.get('type') as UrlType; 30 + const uncollected = searchParams.get('uncollected') === 'true'; 30 31 31 32 const sortBy = 32 33 (searchParams.get('sort') as CardSortField) ?? CardSortField.UPDATED_AT; ··· 43 44 sortBy: sortBy, 44 45 sortOrder: getCardsSortParams(sortBy).sortOrder, 45 46 urlType: selectedUrlType, 47 + uncollected, 46 48 }); 47 49 48 50 const allCards = data?.pages.flatMap((page) => page.cards ?? []) ?? []; ··· 59 61 return ( 60 62 <Container px="xs" py={'xl'} size="xl"> 61 63 <ProfileEmptyTab 62 - message={selectedUrlType ? `No ${selectedUrlType} cards` : 'No cards'} 64 + message={ 65 + uncollected 66 + ? 'No unsorted cards' 67 + : selectedUrlType 68 + ? `No ${selectedUrlType} cards` 69 + : 'No cards' 70 + } 63 71 icon={FaRegNoteSticky} 64 72 /> 65 73 </Container>
+2
src/webapp/features/cards/lib/cardKeys.ts
··· 14 14 sortBy?: CardSortField, 15 15 sortOrder?: SortOrder, 16 16 urlType?: UrlType, 17 + uncollected?: boolean, 17 18 ) => [ 18 19 ...cardKeys.all(), 19 20 'infinite', ··· 22 23 sortBy, 23 24 sortOrder, 24 25 urlType, 26 + uncollected, 25 27 ], 26 28 urlMetadata: (url: string, options?: { includeStats?: boolean }) => 27 29 [...cardKeys.all(), 'metadata', url, options] as const,
+2
src/webapp/features/cards/lib/dal.ts
··· 14 14 cardSortBy?: CardSortField; 15 15 cardSortOrder?: SortOrder; 16 16 urlType?: UrlType; 17 + uncollected?: boolean; 17 18 } 18 19 19 20 export const getUrlMetadata = cache(async (params: GetUrlMetadataParams) => { ··· 89 90 sortBy: params?.cardSortBy, 90 91 sortOrder: params?.cardSortOrder, 91 92 urlType: params?.urlType, 93 + uncollected: params?.uncollected, 92 94 }); 93 95 94 96 return response;
+3
src/webapp/features/cards/lib/queries/useCards.tsx
··· 9 9 sortBy?: CardSortField; 10 10 sortOrder?: SortOrder; 11 11 urlType?: UrlType; 12 + uncollected?: boolean; 12 13 } 13 14 14 15 export default function useCards(props: Props) { ··· 21 22 props.sortBy, 22 23 props.sortOrder, 23 24 props.urlType, 25 + props.uncollected, 24 26 ), 25 27 initialPageParam: 1, 26 28 queryFn: ({ pageParam = 1 }) => { ··· 30 32 cardSortBy: props.sortBy, 31 33 cardSortOrder: props.sortOrder, 32 34 urlType: props.urlType, 35 + uncollected: props.uncollected, 33 36 }); 34 37 }, 35 38 getNextPageParam: (lastPage) => {
+1 -1
src/webapp/next-env.d.ts
··· 1 1 /// <reference types="next" /> 2 2 /// <reference types="next/image-types/global" /> 3 - import "./.next/types/routes.d.ts"; 3 + import "./.next/dev/types/routes.d.ts"; 4 4 5 5 // NOTE: This file should not be edited 6 6 // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.