This repository has no description
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';
14
15export default function useAddCard(
16 analyticsContext?: CardSaveAnalyticsContext,
17) {
18 const queryClient = useQueryClient();
19
20 const mutation = useMutation({
21 mutationFn: (newCard: {
22 url: string;
23 note?: string;
24 collectionIds?: string[];
25 viaCardId?: string;
26 }) => {
27 return addUrlToLibrary(newCard.url, {
28 note: newCard.note,
29 collectionIds: newCard.collectionIds,
30 viaCardId: newCard.viaCardId,
31 });
32 },
33
34 // Do things that are absolutely necessary and logic related (like query invalidation) in the useMutation callbacks
35 // Do UI related things like redirects or showing toast notifications in mutate callbacks. If the user navigated away from the current screen before the mutation finished, those will purposefully not fire
36 // https://tkdodo.eu/blog/mastering-mutations-in-react-query#some-callbacks-might-not-fire
37 onSuccess: (_data, variables) => {
38 queryClient.invalidateQueries({ queryKey: cardKeys.all() });
39 queryClient.invalidateQueries({ queryKey: noteKeys.all() });
40 queryClient.invalidateQueries({ queryKey: feedKeys.all() });
41 queryClient.invalidateQueries({ queryKey: sembleKeys.all() });
42 queryClient.invalidateQueries({ queryKey: collectionKeys.mine() });
43 queryClient.invalidateQueries({ queryKey: collectionKeys.infinite() });
44 queryClient.invalidateQueries({ queryKey: collectionKeys.all() });
45 queryClient.invalidateQueries({
46 queryKey: collectionKeys.bySembleUrl(variables.url),
47 });
48 // Invalidate URL metadata with stats to update tab counts
49 queryClient.invalidateQueries({
50 queryKey: cardKeys.urlMetadata(variables.url, { includeStats: true }),
51 });
52
53 // invalidate each collection query individually
54 variables.collectionIds?.forEach((id) => {
55 queryClient.invalidateQueries({
56 queryKey: collectionKeys.collection(id),
57 });
58 queryClient.invalidateQueries({
59 queryKey: collectionKeys.infinite(id),
60 });
61 });
62
63 // Track card save event in PostHog
64 if (shouldCaptureAnalytics() && analyticsContext) {
65 const eventProperties: CardSaveEventProperties = {
66 save_source: analyticsContext.saveSource,
67 is_new_card: true,
68 has_note: !!variables.note,
69 collection_count: variables.collectionIds?.length || 0,
70 active_filters: analyticsContext.activeFilters
71 ? {
72 url_type: analyticsContext.activeFilters.urlType,
73 sort: analyticsContext.activeFilters.sort,
74 search_query: analyticsContext.activeFilters.searchQuery,
75 profile_filter: analyticsContext.activeFilters.profileFilter,
76 }
77 : undefined,
78 via_card_id: variables.viaCardId,
79 page_path: analyticsContext.pagePath,
80 };
81
82 posthog.capture('card_saved', eventProperties);
83
84 // Clear super properties after capture
85 posthog.unregister('original_save_source');
86 posthog.unregister('original_active_filters');
87 }
88 },
89 });
90
91 return mutation;
92}