This repository has no description
0

Configure Feed

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

semble / src / webapp / features / cards / lib / mutations / useAddCard.tsx
4.9 kB 130 lines
1import { useMutation, useQueryClient } from '@tanstack/react-query'; 2import { addUrlToLibrary } from '../dal'; 3import { cardKeys } from '../cardKeys'; 4import { collectionKeys } from '@/features/collections/lib/collectionKeys'; 5import { feedKeys } from '@/features/feeds/lib/feedKeys'; 6import { noteKeys } from '@/features/notes/lib/noteKeys'; 7import { sembleKeys } from '@/features/semble/lib/sembleKeys'; 8import { connectionKeys } from '@/features/connections/lib/connectionKeys'; 9import { profileKeys } from '@/features/profile/lib/profileKeys'; 10import posthog from 'posthog-js'; 11import { 12 CardSaveAnalyticsContext, 13 CardSaveEventProperties, 14} from '@/features/analytics/types'; 15import { shouldCaptureAnalytics } from '@/features/analytics/utils'; 16import { notifications } from '@mantine/notifications'; 17import { BsCheck, BsExclamation } from 'react-icons/bs'; 18 19export default function useAddCard( 20 analyticsContext?: CardSaveAnalyticsContext, 21) { 22 const queryClient = useQueryClient(); 23 24 const mutation = useMutation({ 25 mutationFn: async (newCard: { 26 url: string; 27 note?: string; 28 collectionIds?: string[]; 29 viaCardId?: string; 30 notificationId?: string; 31 }) => { 32 return addUrlToLibrary(newCard.url, { 33 note: newCard.note, 34 collectionIds: newCard.collectionIds, 35 viaCardId: newCard.viaCardId, 36 }); 37 }, 38 39 // Generally, do UI things (redirects, toasts) in mutate-level callbacks so they 40 // don't fire if the user navigated away. But loading toasts that need .update() 41 // must be handled here — Suspense re-renders can unmount the caller and drop 42 // mutate-level callbacks, leaving the loading toast stuck forever. 43 // https://tkdodo.eu/blog/mastering-mutations-in-react-query#some-callbacks-might-not-fire 44 onSuccess: (_data, variables) => { 45 if (variables.notificationId) { 46 notifications.update({ 47 id: variables.notificationId, 48 color: 'green', 49 title: 'Success!', 50 message: 'Card added', 51 position: 'top-center', 52 loading: false, 53 autoClose: 2000, 54 icon: <BsCheck />, 55 }); 56 } 57 58 queryClient.invalidateQueries({ queryKey: cardKeys.all() }); 59 queryClient.invalidateQueries({ queryKey: noteKeys.all() }); 60 queryClient.invalidateQueries({ queryKey: feedKeys.all() }); 61 queryClient.invalidateQueries({ queryKey: sembleKeys.all() }); 62 queryClient.invalidateQueries({ queryKey: collectionKeys.mine() }); 63 queryClient.invalidateQueries({ queryKey: collectionKeys.infinite() }); 64 queryClient.invalidateQueries({ queryKey: collectionKeys.all() }); 65 queryClient.invalidateQueries({ queryKey: connectionKeys.all() }); 66 queryClient.invalidateQueries({ queryKey: profileKeys.all() }); 67 queryClient.invalidateQueries({ 68 queryKey: collectionKeys.bySembleUrl(variables.url), 69 }); 70 // Invalidate URL metadata with stats to update tab counts 71 queryClient.invalidateQueries({ 72 queryKey: cardKeys.urlMetadata(variables.url, { includeStats: true }), 73 }); 74 75 // invalidate each collection query individually 76 variables.collectionIds?.forEach((id) => { 77 queryClient.invalidateQueries({ 78 queryKey: collectionKeys.collection(id), 79 }); 80 queryClient.invalidateQueries({ 81 queryKey: collectionKeys.infinite(id), 82 }); 83 }); 84 85 // Track card save event in PostHog 86 if (shouldCaptureAnalytics() && analyticsContext) { 87 const eventProperties: CardSaveEventProperties = { 88 save_source: analyticsContext.saveSource, 89 is_new_card: true, 90 has_note: !!variables.note, 91 collection_count: variables.collectionIds?.length || 0, 92 active_filters: analyticsContext.activeFilters 93 ? { 94 url_type: analyticsContext.activeFilters.urlType, 95 sort: analyticsContext.activeFilters.sort, 96 search_query: analyticsContext.activeFilters.searchQuery, 97 profile_filter: analyticsContext.activeFilters.profileFilter, 98 } 99 : undefined, 100 via_card_id: variables.viaCardId, 101 page_path: analyticsContext.pagePath, 102 }; 103 104 posthog.capture('card_saved', eventProperties); 105 106 // Clear super properties after capture 107 posthog.unregister('original_save_source'); 108 posthog.unregister('original_active_filters'); 109 } 110 }, 111 112 onError: (_error, variables) => { 113 if (variables.notificationId) { 114 notifications.update({ 115 id: variables.notificationId, 116 color: 'red', 117 title: 'Error', 118 message: 'Could not add card', 119 position: 'top-center', 120 loading: false, 121 autoClose: 5000, 122 withCloseButton: true, 123 icon: <BsExclamation />, 124 }); 125 } 126 }, 127 }); 128 129 return mutation; 130}