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