diff --git a/src/webapp/features/analytics/types.ts b/src/webapp/features/analytics/types.ts new file mode 100644 index 00000000..0172a023 --- /dev/null +++ b/src/webapp/features/analytics/types.ts @@ -0,0 +1,36 @@ +export enum CardSaveSource { + PROFILE = 'profile', + SEMBLE_PAGE = 'semble_page', + COLLECTION = 'collection', + FEED = 'feed', + SIMILAR_CARDS = 'similar_cards', + NOTIFICATIONS = 'notifications', + SEARCH_RESULTS = 'search_results', + ADD_CARD_DRAWER = 'add_card_drawer', +} + +export interface CardSaveAnalyticsContext { + saveSource: CardSaveSource; + activeFilters?: { + urlType?: string; + sort?: string; + searchQuery?: string; + profileFilter?: string; + }; + pagePath?: string; +} + +export interface CardSaveEventProperties { + save_source: CardSaveSource; + is_new_card: boolean; + has_note: boolean; + collection_count: number; + active_filters?: { + url_type?: string; + sort?: string; + search_query?: string; + profile_filter?: string; + }; + via_card_id?: string; + page_path?: string; +} diff --git a/src/webapp/features/analytics/utils.ts b/src/webapp/features/analytics/utils.ts new file mode 100644 index 00000000..52ae9710 --- /dev/null +++ b/src/webapp/features/analytics/utils.ts @@ -0,0 +1,11 @@ +import posthog from 'posthog-js'; + +/** + * Check if analytics should be captured + * Centralizes the logic for when to track events + */ +export function shouldCaptureAnalytics(): boolean { + return ( + process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' && posthog.__loaded + ); +} diff --git a/src/webapp/features/cards/components/addCardDrawer/AddCardDrawer.tsx b/src/webapp/features/cards/components/addCardDrawer/AddCardDrawer.tsx index 6d44a2f6..8b08e94c 100644 --- a/src/webapp/features/cards/components/addCardDrawer/AddCardDrawer.tsx +++ b/src/webapp/features/cards/components/addCardDrawer/AddCardDrawer.tsx @@ -29,7 +29,8 @@ import { isMarginUri, getMarginUrl } from '@/lib/utils/margin'; import MarginLogo from '@/components/MarginLogo'; import { Collection, CollectionAccessType } from '@semble/types'; import { FaSeedling } from 'react-icons/fa6'; -import posthog from 'posthog-js'; +import { CardSaveSource } from '@/features/analytics/types'; +import { usePathname } from 'next/navigation'; interface Props { isOpen: boolean; @@ -39,6 +40,7 @@ interface Props { } export default function AddCardDrawer(props: Props) { + const pathname = usePathname(); const [collectionSelectorOpened, { toggle: toggleCollectionSelector }] = useDisclosure(false); const initialCollections = props.selectedCollection @@ -51,7 +53,10 @@ export default function AddCardDrawer(props: Props) { const myCollections = collections?.pages.flatMap((page) => page.collections ?? []) ?? []; - const addCard = useAddCard(); + const addCard = useAddCard({ + saveSource: CardSaveSource.ADD_CARD_DRAWER, + pagePath: pathname, + }); const form = useForm({ initialValues: { @@ -73,7 +78,6 @@ export default function AddCardDrawer(props: Props) { const handleAddCard = (e: React.FormEvent) => { e.preventDefault(); track('add new card'); - posthog.capture('add_new_card'); addCard.mutate( { diff --git a/src/webapp/features/cards/components/addCardToModal/AddCardToModal.tsx b/src/webapp/features/cards/components/addCardToModal/AddCardToModal.tsx index fa63c176..d3cb94a5 100644 --- a/src/webapp/features/cards/components/addCardToModal/AddCardToModal.tsx +++ b/src/webapp/features/cards/components/addCardToModal/AddCardToModal.tsx @@ -5,6 +5,7 @@ import { Suspense } from 'react'; import CollectionSelectorSkeleton from '@/features/collections/components/collectionSelector/Skeleton.CollectionSelector'; import AddCardToModalContent from './AddCardToModalContent'; import CardToBeAddedPreview from '../cardToBeAddedPreview/CardToBeAddedPreview'; +import { CardSaveAnalyticsContext } from '@/features/analytics/types'; interface Props { isOpen: boolean; @@ -16,6 +17,7 @@ interface Props { urlLibraryCount: number; viaCardId?: string; cardContent?: UrlCard['cardContent']; + analyticsContext?: CardSaveAnalyticsContext; } export default function AddCardToModal(props: Props) { @@ -63,6 +65,7 @@ export default function AddCardToModal(props: Props) { cardContent={props.cardContent} note={props.note} viaCardId={props.viaCardId} + analyticsContext={props.analyticsContext} /> diff --git a/src/webapp/features/cards/components/addCardToModal/AddCardToModalContent.tsx b/src/webapp/features/cards/components/addCardToModal/AddCardToModalContent.tsx index 13bb9b35..f5cf0f70 100644 --- a/src/webapp/features/cards/components/addCardToModal/AddCardToModalContent.tsx +++ b/src/webapp/features/cards/components/addCardToModal/AddCardToModalContent.tsx @@ -12,7 +12,7 @@ import useUpdateCardAssociations from '@/features/cards/lib/mutations/useUpdateC import useAddCard from '@/features/cards/lib/mutations/useAddCard'; import { track } from '@vercel/analytics'; import AddCardActions from '../addCardActions/AddCardActions'; -import posthog from 'posthog-js'; +import { CardSaveAnalyticsContext } from '@/features/analytics/types'; interface Props { onClose: () => void; @@ -21,6 +21,7 @@ interface Props { note?: string; cardContent?: UrlCard['cardContent']; viaCardId?: string; + analyticsContext?: CardSaveAnalyticsContext; } export default function AddCardToModalContent(props: Props) { @@ -29,8 +30,10 @@ export default function AddCardToModalContent(props: Props) { const [note, setNote] = useState(isMyCard ? props.note : ''); const { data, error } = useMyCollections(); - const addCard = useAddCard(); - const updateCardAssociations = useUpdateCardAssociations(); + const addCard = useAddCard(props.analyticsContext); + const updateCardAssociations = useUpdateCardAssociations( + props.analyticsContext, + ); if (error) { return ; @@ -51,7 +54,6 @@ export default function AddCardToModalContent(props: Props) { const handleUpdateCard = (e: React.FormEvent) => { e.preventDefault(); track('add or update existing card'); - posthog.capture('add or update existing card'); const trimmedNote = note?.trimEnd() === '' ? undefined : note; @@ -100,6 +102,7 @@ export default function AddCardToModalContent(props: Props) { note?: string; addToCollectionIds?: string[]; removeFromCollectionIds?: string[]; + addToLibrary?: boolean; } = { cardId: cardStatus.data.card.id }; if (hasNoteChanged) updatedCardPayload.note = trimmedNote; @@ -110,6 +113,9 @@ export default function AddCardToModalContent(props: Props) { (c) => c.id, ); + // Track as a card save if we're adding collections or a note (indicates user is saving/organizing the card) + updatedCardPayload.addToLibrary = hasAdded || hasNoteChanged; + updateCardAssociations.mutate( { ...updatedCardPayload, diff --git a/src/webapp/features/cards/components/urlCard/UrlCard.tsx b/src/webapp/features/cards/components/urlCard/UrlCard.tsx index 3c323e18..e7a0be8e 100644 --- a/src/webapp/features/cards/components/urlCard/UrlCard.tsx +++ b/src/webapp/features/cards/components/urlCard/UrlCard.tsx @@ -11,6 +11,7 @@ import styles from './UrlCard.module.css'; import { useUserSettings } from '@/features/settings/lib/queries/useUserSettings'; import UrlCardDebugView from '../UrlCardDebugView/UrlCardDebugView'; import Link from 'next/link'; +import { CardSaveAnalyticsContext } from '@/features/analytics/types'; interface Props { id: string; @@ -25,6 +26,7 @@ interface Props { cardAuthor?: User; viaCardId?: string; showAuthor?: boolean; + analyticsContext?: CardSaveAnalyticsContext; } export default function UrlCard(props: Props) { @@ -133,6 +135,7 @@ export default function UrlCard(props: Props) { urlLibraryCount={props.urlLibraryCount} urlIsInLibrary={props.urlIsInLibrary ?? false} viaCardId={props.viaCardId} + analyticsContext={props.analyticsContext} /> diff --git a/src/webapp/features/cards/components/urlCardActions/UrlCardActions.tsx b/src/webapp/features/cards/components/urlCardActions/UrlCardActions.tsx index c4fda860..5db9f808 100644 --- a/src/webapp/features/cards/components/urlCardActions/UrlCardActions.tsx +++ b/src/webapp/features/cards/components/urlCardActions/UrlCardActions.tsx @@ -20,6 +20,7 @@ import { useAuth } from '@/hooks/useAuth'; import { IoMdCheckmark } from 'react-icons/io'; import { notifications } from '@mantine/notifications'; import { BiCopy } from 'react-icons/bi'; +import { CardSaveAnalyticsContext } from '@/features/analytics/types'; interface Props { id: string; @@ -32,6 +33,7 @@ interface Props { urlLibraryCount: number; urlIsInLibrary: boolean; viaCardId?: string; + analyticsContext?: CardSaveAnalyticsContext; } export default function UrlCardActions(props: Props) { @@ -194,6 +196,7 @@ export default function UrlCardActions(props: Props) { isInYourLibrary={props.urlIsInLibrary} urlLibraryCount={props.urlLibraryCount} viaCardId={props.viaCardId} + analyticsContext={props.analyticsContext} /> = { }; export default function CardsContainerContent(props: Props) { + const pathname = usePathname(); const { desktopOpened } = useNavbarContext(); const { settings } = useUserSettings(); @@ -97,6 +99,14 @@ export default function CardsContainerContent(props: Props) { urlLibraryCount={card.urlLibraryCount} urlIsInLibrary={card.urlInLibrary} viaCardId={card.id} + analyticsContext={{ + saveSource: CardSaveSource.PROFILE, + activeFilters: { + urlType: selectedUrlType, + sort: sortBy, + }, + pagePath: pathname, + }} /> ))} diff --git a/src/webapp/features/cards/lib/mutations/useAddCard.tsx b/src/webapp/features/cards/lib/mutations/useAddCard.tsx index b18f9493..66d24cd6 100644 --- a/src/webapp/features/cards/lib/mutations/useAddCard.tsx +++ b/src/webapp/features/cards/lib/mutations/useAddCard.tsx @@ -5,8 +5,16 @@ import { collectionKeys } from '@/features/collections/lib/collectionKeys'; import { feedKeys } from '@/features/feeds/lib/feedKeys'; import { noteKeys } from '@/features/notes/lib/noteKeys'; import { sembleKeys } from '@/features/semble/lib/sembleKeys'; +import posthog from 'posthog-js'; +import { + CardSaveAnalyticsContext, + CardSaveEventProperties, +} from '@/features/analytics/types'; +import { shouldCaptureAnalytics } from '@/features/analytics/utils'; -export default function useAddCard() { +export default function useAddCard( + analyticsContext?: CardSaveAnalyticsContext, +) { const queryClient = useQueryClient(); const mutation = useMutation({ @@ -47,6 +55,28 @@ export default function useAddCard() { queryKey: collectionKeys.infinite(id), }); }); + + // Track card save event in PostHog + if (shouldCaptureAnalytics() && analyticsContext) { + const eventProperties: CardSaveEventProperties = { + save_source: analyticsContext.saveSource, + is_new_card: true, + has_note: !!variables.note, + collection_count: variables.collectionIds?.length || 0, + active_filters: analyticsContext.activeFilters + ? { + url_type: analyticsContext.activeFilters.urlType, + sort: analyticsContext.activeFilters.sort, + search_query: analyticsContext.activeFilters.searchQuery, + profile_filter: analyticsContext.activeFilters.profileFilter, + } + : undefined, + via_card_id: variables.viaCardId, + page_path: analyticsContext.pagePath, + }; + + posthog.capture('card_saved', eventProperties); + } }, }); diff --git a/src/webapp/features/cards/lib/mutations/useUpdateCardAssociations.tsx b/src/webapp/features/cards/lib/mutations/useUpdateCardAssociations.tsx index 9083b953..0503cd5b 100644 --- a/src/webapp/features/cards/lib/mutations/useUpdateCardAssociations.tsx +++ b/src/webapp/features/cards/lib/mutations/useUpdateCardAssociations.tsx @@ -5,8 +5,16 @@ import { collectionKeys } from '@/features/collections/lib/collectionKeys'; import { noteKeys } from '@/features/notes/lib/noteKeys'; import { sembleKeys } from '@/features/semble/lib/sembleKeys'; import { feedKeys } from '@/features/feeds/lib/feedKeys'; +import posthog from 'posthog-js'; +import { + CardSaveAnalyticsContext, + CardSaveEventProperties, +} from '@/features/analytics/types'; +import { shouldCaptureAnalytics } from '@/features/analytics/utils'; -export default function useUpdateCardAssociations() { +export default function useUpdateCardAssociations( + analyticsContext?: CardSaveAnalyticsContext, +) { const client = createSembleClient(); const queryClient = useQueryClient(); @@ -18,6 +26,7 @@ export default function useUpdateCardAssociations() { addToCollectionIds?: string[]; removeFromCollectionIds?: string[]; viaCardId?: string; + addToLibrary?: boolean; }) => { return client.updateUrlCardAssociations({ cardId: updatedCard.cardId, @@ -47,6 +56,32 @@ export default function useUpdateCardAssociations() { queryKey: collectionKeys.collection(id), }); }); + + // Track card save event in PostHog (only when adding to library) + if ( + shouldCaptureAnalytics() && + analyticsContext && + variables.addToLibrary + ) { + const eventProperties: CardSaveEventProperties = { + save_source: analyticsContext.saveSource, + is_new_card: false, + has_note: !!variables.note, + collection_count: variables.addToCollectionIds?.length || 0, + active_filters: analyticsContext.activeFilters + ? { + url_type: analyticsContext.activeFilters.urlType, + sort: analyticsContext.activeFilters.sort, + search_query: analyticsContext.activeFilters.searchQuery, + profile_filter: analyticsContext.activeFilters.profileFilter, + } + : undefined, + via_card_id: variables.viaCardId, + page_path: analyticsContext.pagePath, + }; + + posthog.capture('card_saved', eventProperties); + } }, }); diff --git a/src/webapp/features/collections/containers/collectionContainerContent/CollectionContainerContent.tsx b/src/webapp/features/collections/containers/collectionContainerContent/CollectionContainerContent.tsx index d31524c3..e3f1eb47 100644 --- a/src/webapp/features/collections/containers/collectionContainerContent/CollectionContainerContent.tsx +++ b/src/webapp/features/collections/containers/collectionContainerContent/CollectionContainerContent.tsx @@ -11,7 +11,8 @@ import UrlCard from '@/features/cards/components/urlCard/UrlCard'; import AddCardDrawer from '@/features/cards/components/addCardDrawer/AddCardDrawer'; import Link from 'next/link'; import { FiPlus } from 'react-icons/fi'; -import { useSearchParams } from 'next/navigation'; +import { useSearchParams, usePathname } from 'next/navigation'; +import { CardSaveSource } from '@/features/analytics/types'; interface Props { rkey: string; @@ -27,6 +28,7 @@ const sortOrderMap: Record = { }; export default function CollectionContainerContent(props: Props) { + const pathname = usePathname(); const { desktopOpened } = useNavbarContext(); const [showAddDrawer, setShowAddDrawer] = useState(false); const { user } = useAuth(); @@ -84,6 +86,14 @@ export default function CollectionContainerContent(props: Props) { currentCollection={firstPage} viaCardId={card.id} showAuthor={props.handle !== card.author.handle} + analyticsContext={{ + saveSource: CardSaveSource.COLLECTION, + activeFilters: { + urlType: selectedUrlType, + sort: sortBy, + }, + pagePath: pathname, + }} /> ))} diff --git a/src/webapp/features/feeds/components/feedItem/FeedItem.tsx b/src/webapp/features/feeds/components/feedItem/FeedItem.tsx index e75acb06..a222dddf 100644 --- a/src/webapp/features/feeds/components/feedItem/FeedItem.tsx +++ b/src/webapp/features/feeds/components/feedItem/FeedItem.tsx @@ -2,9 +2,11 @@ import type { FeedItem as FeedItemType } from '@/api-client'; import { Stack } from '@mantine/core'; import UrlCard from '@/features/cards/components/urlCard/UrlCard'; import FeedActivityStatus from '../feedActivityStatus/FeedActivityStatus'; +import { CardSaveAnalyticsContext } from '@/features/analytics/types'; interface Props { item: FeedItemType; + analyticsContext?: CardSaveAnalyticsContext; } export default function FeedItem(props: Props) { @@ -27,6 +29,7 @@ export default function FeedItem(props: Props) { urlIsInLibrary={props.item.card.urlInLibrary} authorHandle={props.item.user.handle} viaCardId={props.item.card.id} + analyticsContext={props.analyticsContext} /> ); diff --git a/src/webapp/features/feeds/containers/myFeedContainer/MyFeedContainer.tsx b/src/webapp/features/feeds/containers/myFeedContainer/MyFeedContainer.tsx index ad395226..4a8339a5 100644 --- a/src/webapp/features/feeds/containers/myFeedContainer/MyFeedContainer.tsx +++ b/src/webapp/features/feeds/containers/myFeedContainer/MyFeedContainer.tsx @@ -9,10 +9,12 @@ import MyFeedContainerError from './Error.MyFeedContainer'; import InfiniteScroll from '@/components/contentDisplay/infiniteScroll/InfiniteScroll'; import RefetchButton from '@/components/navigation/refetchButton/RefetchButton'; import { UrlType, ActivitySource } from '@semble/types'; -import { useSearchParams, useRouter } from 'next/navigation'; +import { useSearchParams, useRouter, usePathname } from 'next/navigation'; +import { CardSaveSource } from '@/features/analytics/types'; export default function MyFeedContainer() { const router = useRouter(); + const pathname = usePathname(); const searchParams = useSearchParams(); const selectedUrlType = searchParams.get('type') as UrlType; const selectedSource = searchParams.get('source') as ActivitySource; @@ -81,7 +83,17 @@ export default function MyFeedContainer() { {allActivities.map((item) => ( - + ))} diff --git a/src/webapp/features/notifications/components/notificationItem/NotificationItem.tsx b/src/webapp/features/notifications/components/notificationItem/NotificationItem.tsx index b78c3a69..96e28835 100644 --- a/src/webapp/features/notifications/components/notificationItem/NotificationItem.tsx +++ b/src/webapp/features/notifications/components/notificationItem/NotificationItem.tsx @@ -5,9 +5,11 @@ import UrlCard from '@/features/cards/components/urlCard/UrlCard'; import NotificationActivityStatus from '../notificationActivityStatus/NotificationActivityStatus'; import FollowButton from '@/features/follows/components/followButton/FollowButton'; import { useRouter } from 'next/navigation'; +import { CardSaveAnalyticsContext } from '@/features/analytics/types'; interface Props { item: NotificationItemType; + analyticsContext?: CardSaveAnalyticsContext; } export default function NotificationItem(props: Props) { @@ -69,6 +71,7 @@ export default function NotificationItem(props: Props) { urlIsInLibrary={props.item.card.urlInLibrary} authorHandle={props.item.user.handle} viaCardId={props.item.card.id} + analyticsContext={props.analyticsContext} /> )} diff --git a/src/webapp/features/notifications/containers/notificationsContainer/NotificationsContainer.tsx b/src/webapp/features/notifications/containers/notificationsContainer/NotificationsContainer.tsx index 7c0b2cb8..881805f1 100644 --- a/src/webapp/features/notifications/containers/notificationsContainer/NotificationsContainer.tsx +++ b/src/webapp/features/notifications/containers/notificationsContainer/NotificationsContainer.tsx @@ -21,8 +21,11 @@ import { useEffect, useRef } from 'react'; import useMarkAllNotificationsAsRead from '../../lib/mutations/useMarkAllNotificationsAsRead'; import { IoCheckmarkDoneSharp } from 'react-icons/io5'; import useUnreadNotificationCount from '../../lib/queries/useUnreadNotificationCount'; +import { CardSaveSource } from '@/features/analytics/types'; +import { usePathname } from 'next/navigation'; export default function NotificationsContainer() { + const pathname = usePathname(); const { data, error, @@ -117,7 +120,14 @@ export default function NotificationsContainer() { {allNotifications.map((item) => ( - + ))} diff --git a/src/webapp/features/profile/containers/profileContainer/ProfileContainer.tsx b/src/webapp/features/profile/containers/profileContainer/ProfileContainer.tsx index 2d33d2a7..6065f301 100644 --- a/src/webapp/features/profile/containers/profileContainer/ProfileContainer.tsx +++ b/src/webapp/features/profile/containers/profileContainer/ProfileContainer.tsx @@ -18,12 +18,15 @@ import ProfileEmptyTab from '../../components/profileEmptyTab/ProfileEmptyTab'; import { BiCollection } from 'react-icons/bi'; import { FaRegNoteSticky } from 'react-icons/fa6'; import { useNavbarContext } from '@/providers/navbar'; +import { CardSaveSource } from '@/features/analytics/types'; +import { usePathname } from 'next/navigation'; interface Props { handle: string; } export default function ProfileContainer(props: Props) { + const pathname = usePathname(); const { data: collectionsData } = useCollections({ limit: 4, didOrHandle: props.handle, @@ -81,6 +84,10 @@ export default function ProfileContainer(props: Props) { urlLibraryCount={card.urlLibraryCount} urlIsInLibrary={card.urlInLibrary} viaCardId={card.id} + analyticsContext={{ + saveSource: CardSaveSource.PROFILE, + pagePath: pathname, + }} /> ))} diff --git a/src/webapp/features/search/containers/cardSearchResultsContainer/CardSearchResultsContainer.tsx b/src/webapp/features/search/containers/cardSearchResultsContainer/CardSearchResultsContainer.tsx index e74dc905..a641e805 100644 --- a/src/webapp/features/search/containers/cardSearchResultsContainer/CardSearchResultsContainer.tsx +++ b/src/webapp/features/search/containers/cardSearchResultsContainer/CardSearchResultsContainer.tsx @@ -10,6 +10,8 @@ import SimilarUrlCard from '@/features/semble/components/similarUrlCard/SimilarU import SearchResultsContainerError from '../searchResultsContainer/Error.SearchResultsContainer'; import SearchQueryAlert from '../../components/searchQueryAlert/SearchQueryAlert'; import { SearchFilters } from '../../components/searchFilters/SearchFilters'; +import { CardSaveSource } from '@/features/analytics/types'; +import { usePathname } from 'next/navigation'; interface Props { query: string; @@ -18,6 +20,7 @@ interface Props { } export default function CardSearchResultsContainer(props: Props) { + const pathname = usePathname(); const { data, error, @@ -59,7 +62,18 @@ export default function CardSearchResultsContainer(props: Props) { {allUrls.map((urlView) => ( - + ))} diff --git a/src/webapp/features/semble/components/sembleActions/SembleActions.tsx b/src/webapp/features/semble/components/sembleActions/SembleActions.tsx index 0c04107c..1a39a305 100644 --- a/src/webapp/features/semble/components/sembleActions/SembleActions.tsx +++ b/src/webapp/features/semble/components/sembleActions/SembleActions.tsx @@ -10,7 +10,8 @@ import { IoMdCheckmark } from 'react-icons/io'; import { MdIosShare } from 'react-icons/md'; import useSembleLibraries from '../../lib/queries/useSembleLibraries'; import { track } from '@vercel/analytics'; -import posthog from 'posthog-js'; +import { CardSaveSource } from '@/features/analytics/types'; +import { usePathname } from 'next/navigation'; interface Props { url: string; @@ -18,6 +19,7 @@ interface Props { } export default function SembleActions(props: Props) { + const pathname = usePathname(); const cardStatus = useGetCardFromMyLibrary({ url: props.url }); const isInYourLibrary = cardStatus.data.card?.urlInLibrary; const [showAddToModal, setShowAddToModal] = useState(false); @@ -76,9 +78,6 @@ export default function SembleActions(props: Props) { track( `Semble: ${isInYourLibrary ? 'update card' : 'add to library'}`, ); - posthog.capture( - `Semble: ${isInYourLibrary ? 'update card' : 'add to library'}`, - ); }} > {isInYourLibrary ? 'Update card' : 'Add to library'} @@ -95,6 +94,10 @@ export default function SembleActions(props: Props) { isInYourLibrary={cardStatus.data.card?.urlInLibrary} urlLibraryCount={urlLibraryCount} viaCardId={props.viaCardId} + analyticsContext={{ + saveSource: CardSaveSource.SEMBLE_PAGE, + pagePath: pathname, + }} /> ); diff --git a/src/webapp/features/semble/components/similarUrlCard/SimilarUrlCard.tsx b/src/webapp/features/semble/components/similarUrlCard/SimilarUrlCard.tsx index 62bd075d..8862bd85 100644 --- a/src/webapp/features/semble/components/similarUrlCard/SimilarUrlCard.tsx +++ b/src/webapp/features/semble/components/similarUrlCard/SimilarUrlCard.tsx @@ -2,9 +2,11 @@ import type { UrlView } from '@/api-client'; import UrlCard from '@/features/cards/components/urlCard/UrlCard'; +import { CardSaveAnalyticsContext } from '@/features/analytics/types'; interface Props { urlView: UrlView; + analyticsContext?: CardSaveAnalyticsContext; } export default function SimilarUrlCard(props: Props) { @@ -15,6 +17,7 @@ export default function SimilarUrlCard(props: Props) { cardContent={props.urlView.metadata} urlLibraryCount={props.urlView.urlLibraryCount} urlIsInLibrary={props.urlView.urlInLibrary ?? false} + analyticsContext={props.analyticsContext} /> ); } diff --git a/src/webapp/features/semble/containers/sembleSimilarCardsContainer/SembleSimilarCardsContainer.tsx b/src/webapp/features/semble/containers/sembleSimilarCardsContainer/SembleSimilarCardsContainer.tsx index 6d38241f..d33070c0 100644 --- a/src/webapp/features/semble/containers/sembleSimilarCardsContainer/SembleSimilarCardsContainer.tsx +++ b/src/webapp/features/semble/containers/sembleSimilarCardsContainer/SembleSimilarCardsContainer.tsx @@ -8,14 +8,16 @@ import SimilarUrlCard from '../../components/similarUrlCard/SimilarUrlCard'; import SembleEmptyTab from '../../components/sembleEmptyTab/SembleEmptyTab'; import { BiLink } from 'react-icons/bi'; import { CardFilters } from '@/features/cards/components/cardFilters/CardFilters'; -import { useSearchParams } from 'next/navigation'; +import { useSearchParams, usePathname } from 'next/navigation'; import { UrlType } from '@semble/types'; +import { CardSaveSource } from '@/features/analytics/types'; interface Props { url: string; } export default function SembleSimilarCardsContainer(props: Props) { + const pathname = usePathname(); const searchParams = useSearchParams(); const selectedUrlType = searchParams.get('type') as UrlType; @@ -58,7 +60,16 @@ export default function SembleSimilarCardsContainer(props: Props) { {allSimilarUrls.map((urlView) => ( - + ))} diff --git a/src/webapp/hooks/useAuth.tsx b/src/webapp/hooks/useAuth.tsx index 0167daa8..c93aeaff 100644 --- a/src/webapp/hooks/useAuth.tsx +++ b/src/webapp/hooks/useAuth.tsx @@ -9,6 +9,7 @@ import { verifySessionOnClient } from '@/lib/auth/dal'; import { usePathname } from 'next/navigation'; import posthog from 'posthog-js'; import { isInternalUser, isEarlyTester } from '@/lib/userLists'; +import { shouldCaptureAnalytics } from '@/features/analytics/utils'; const ENABLE_AUTH_LOGGING = true; @@ -37,10 +38,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { } // Reset PostHog user identity - if ( - process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' && - posthog.__loaded - ) { + if (shouldCaptureAnalytics()) { posthog.reset(); if (ENABLE_AUTH_LOGGING) { console.log('[useAuth] PostHog user identity reset'); @@ -72,22 +70,14 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { const user = query.data; - // Only identify in production and when user is available - if ( - process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' && - user && - posthog.__loaded - ) { + // Only identify when analytics is enabled and user is available + if (shouldCaptureAnalytics() && user) { posthog.identify(user.id, { name: user.name, handle: user.handle, is_internal: isInternalUser(user.handle), is_early_tester: isEarlyTester(user.handle), }); - - if (ENABLE_AUTH_LOGGING) { - console.log('[useAuth] User identified in PostHog:', user.id); - } } }, [query.data]); diff --git a/src/webapp/lib/userLists.ts b/src/webapp/lib/userLists.ts index 1cd1a8c0..304e93ff 100644 --- a/src/webapp/lib/userLists.ts +++ b/src/webapp/lib/userLists.ts @@ -9,6 +9,10 @@ export const INTERNAL_HANDLES = [ 'cosmik.network', 'semble.so', 'jasmine-pyz.bsky.social', + 'cosmiktesting.bsky.social', + 'sembot.bsky.social', + 'alice.bsky.social', + 'bob.bsky.social', ]; export const EARLY_TESTER_HANDLES = [ diff --git a/src/webapp/providers/index.tsx b/src/webapp/providers/index.tsx index ede0392d..97a1495e 100644 --- a/src/webapp/providers/index.tsx +++ b/src/webapp/providers/index.tsx @@ -27,8 +27,10 @@ export default function Providers(props: Props) { posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_API_KEY!, { api_host: process.env.NEXT_PUBLIC_POSTHOG_API_HOST, - defaults: '2026-01-30', disable_session_recording: true, + autocapture: false, + capture_pageview: false, + capture_pageleave: false, }); }, []);