Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions app/components/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { DynamicContextProvider, DynamicWidget, useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
import { createContext, useContext, useEffect, useState, useCallback, ReactNode } from 'react';
import { trackUserLoggedIn } from '@/lib/analytics';

interface SubscriptionData {
isActive: boolean;
Expand Down Expand Up @@ -148,6 +149,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {

const handleAuthStateChange = useCallback((isAuth: boolean, dynamicUser: any) => {
console.log('[AuthProvider] Auth state changed:', { isAuth, hasUser: !!dynamicUser, userAddress: dynamicUser?.verifiedCredentials?.[0]?.address });

const wasAuthenticated = isAuthenticated;
setIsAuthenticated(isAuth);
setUser(dynamicUser);

Expand All @@ -157,14 +160,20 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setSubscriptionData(null);
setCheckingSubscription(false);
} else if (dynamicUser && isDynamicInitialized) {
// User logged in and Dynamic is initialized, check subscription
// User logged in
const walletAddress = dynamicUser?.verifiedCredentials?.[0]?.address;
if (walletAddress) {
console.log('[AuthProvider] User logged in, checking subscription:', walletAddress);

// Track login event (only on new login, not on page refresh)
if (!wasAuthenticated) {
trackUserLoggedIn();
}

checkSubscription(walletAddress);
}
}
}, [isDynamicInitialized, checkSubscription]);
}, [isDynamicInitialized, checkSubscription, isAuthenticated]);

// If Dynamic is not enabled (no environment ID), render children with default auth context
if (!isDynamicEnabled) {
Expand Down
5 changes: 5 additions & 0 deletions app/components/CustomizationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useState, useEffect } from "react";
import { createPortal } from "react-dom";
import { useCustomization, UserCustomization } from "./CustomizationContext";
import { trackPersonalizationUpdated } from "@/lib/analytics";

type CustomizationModalProps = {
isOpen: boolean;
Expand Down Expand Up @@ -137,6 +138,10 @@ export default function CustomizationModal({ isOpen, onClose }: CustomizationMod
};

await saveCustomization(data, password);

// Track personalization update
trackPersonalizationUpdated();

// Don't close - let user decide with buttons
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to save customization');
Expand Down
7 changes: 1 addition & 6 deletions app/components/DisclaimerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { trackModalOpen, trackModalClose, trackDisclaimerView } from "@/lib/analytics";

type DisclaimerModalProps = {
isOpen: boolean;
Expand All @@ -21,8 +20,7 @@ export default function DisclaimerModal({ isOpen, onClose, type, onAccept }: Dis

useEffect(() => {
if (isOpen) {
trackModalOpen(type === 'initial' ? 'disclaimer_initial' : 'disclaimer_result');
trackDisclaimerView();
// Removed granular modal tracking - no longer needed
} else {
setHasScrolledToBottom(false);
}
Expand Down Expand Up @@ -99,7 +97,6 @@ export default function DisclaimerModal({ isOpen, onClose, type, onAccept }: Dis
<button
className="disclaimer-button primary"
onClick={() => {
trackModalClose('disclaimer_initial', 'accept');
if (onAccept) {
onAccept();
} else {
Expand Down Expand Up @@ -150,7 +147,6 @@ export default function DisclaimerModal({ isOpen, onClose, type, onAccept }: Dis
<button
className="disclaimer-button secondary"
onClick={() => {
trackModalClose('disclaimer_result', 'decline');
onClose();
}}
>
Expand All @@ -159,7 +155,6 @@ export default function DisclaimerModal({ isOpen, onClose, type, onAccept }: Dis
<button
className="disclaimer-button primary"
onClick={() => {
trackModalClose('disclaimer_result', 'accept');
(onAccept || onClose)();
}}
disabled={!hasScrolledToBottom}
Expand Down
4 changes: 4 additions & 0 deletions app/components/LLMChatInline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { callLLM, getLLMDescription } from "@/lib/llm-client";
import { RobotIcon } from "./Icons";
import { trackLLMQuestionAsked } from "@/lib/analytics";

type Message = {
role: 'user' | 'assistant';
Expand Down Expand Up @@ -180,6 +181,9 @@ export default function AIChatInline() {
setIsLoading(true);
setError(null);

// Track LLM question
trackLLMQuestionAsked();

try {
let relevantResults: SavedResult[] = [];

Expand Down
14 changes: 12 additions & 2 deletions app/components/LLMConfigModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
saveLLMConfig,
isConfigValid,
} from "@/lib/llm-config";
import { trackAIProviderSwitched } from "@/lib/analytics";

type LLMConfigModalProps = {
isOpen: boolean;
Expand All @@ -19,11 +20,14 @@ type LLMConfigModalProps = {
export default function LLMConfigModal({ isOpen, onClose, onSave }: LLMConfigModalProps) {
const [config, setConfig] = useState<LLMConfig>(getLLMConfig());
const [showApiKey, setShowApiKey] = useState(false);
const [initialProvider, setInitialProvider] = useState<LLMProvider>(getLLMConfig().provider);

useEffect(() => {
if (isOpen) {
// Reload config when modal opens
setConfig(getLLMConfig());
// Reload config when modal opens and track initial provider
const currentConfig = getLLMConfig();
setConfig(currentConfig);
setInitialProvider(currentConfig.provider);
}
}, [isOpen]);

Expand All @@ -35,6 +39,12 @@ export default function LLMConfigModal({ isOpen, onClose, onSave }: LLMConfigMod
}

saveLLMConfig(config);

// Track if provider was changed
if (config.provider !== initialProvider) {
trackAIProviderSwitched(config.provider);
}

onSave?.();
onClose();
};
Expand Down
11 changes: 2 additions & 9 deletions app/components/NilAIConsentModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { useState, useEffect } from "react";
import { trackModalOpen, trackAIConsentGiven, trackAIConsentDeclined } from "@/lib/analytics";
import { useEffect } from "react";

type NilAIConsentModalProps = {
isOpen: boolean;
Expand All @@ -14,11 +13,7 @@ export default function NilAIConsentModal({
onAccept,
onDecline,
}: NilAIConsentModalProps) {
useEffect(() => {
if (isOpen) {
trackModalOpen('ai_consent');
}
}, [isOpen]);
// Removed modal tracking - not needed for simplified analytics

if (!isOpen) return null;

Expand Down Expand Up @@ -93,7 +88,6 @@ export default function NilAIConsentModal({
<button
className="secondary"
onClick={() => {
trackAIConsentDeclined();
onDecline();
}}
>
Expand All @@ -102,7 +96,6 @@ export default function NilAIConsentModal({
<button
className="primary"
onClick={() => {
trackAIConsentGiven();
onAccept();
}}
>
Expand Down
3 changes: 3 additions & 0 deletions app/components/OverviewReportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useCustomization } from "./CustomizationContext";
import { useAuth } from "./AuthProvider";
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { trackOverviewReportGenerated } from "@/lib/analytics";

type GenerationPhase = 'idle' | 'map' | 'reduce' | 'complete' | 'error';

Expand Down Expand Up @@ -90,6 +91,8 @@ export default function OverviewReportModal({ isOpen, onClose }: OverviewReportM
if (update.phase === 'complete') {
clearInterval(timerInterval);
setIsGenerating(false);
// Track overview report generation
trackOverviewReportGenerated(savedResults.length);
} else if (update.phase === 'error') {
clearInterval(timerInterval);
setIsGenerating(false);
Expand Down
12 changes: 12 additions & 0 deletions app/components/PaymentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { parseUnits, encodeFunctionData, createPublicClient, http, formatUnits }
import { mainnet, base, arbitrum, optimism, polygon } from 'viem/chains';
import { verifyPromoCode } from '@/lib/prime-verification';
import StripeSubscriptionForm from './StripeSubscriptionForm';
import { trackSubscribedWithPromoCode, trackSubscribedWithCreditCard, trackSubscribedWithStablecoin } from '@/lib/analytics';

interface PaymentModalProps {
isOpen: boolean;
Expand Down Expand Up @@ -143,6 +144,9 @@ export default function PaymentModal({ isOpen, onClose, onSuccess }: PaymentModa
localStorage.setItem('promo_access', JSON.stringify({ code: promoCode, granted: Date.now() }));
setPromoMessage({ type: 'success', text: result.message });

// Track promo code subscription
trackSubscribedWithPromoCode(promoCode);

// Success - trigger callback and close modal
setTimeout(() => {
onSuccess();
Expand All @@ -161,6 +165,10 @@ export default function PaymentModal({ isOpen, onClose, onSuccess }: PaymentModa
// Show success message
setStep('card-success');

// Track credit card subscription
const durationDays = Math.round((parseFloat(amount || '0') / 4.99) * 30);
trackSubscribedWithCreditCard(durationDays);

// Close modal after 3 seconds and trigger success callback
setTimeout(() => {
onClose();
Expand Down Expand Up @@ -261,6 +269,10 @@ export default function PaymentModal({ isOpen, onClose, onSuccess }: PaymentModa
data: transferData,
});

// Track stablecoin subscription
const durationDays = Math.round((parseFloat(amount || '0') / 4.99) * 30);
trackSubscribedWithStablecoin(durationDays);

// Success
setTimeout(() => {
onSuccess();
Expand Down
3 changes: 3 additions & 0 deletions app/components/PremiumPaywall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import { useAuth } from './AuthProvider';
import { verifyPromoCode } from '@/lib/prime-verification';
import { trackPremiumSectionViewed } from '@/lib/analytics';

// Lazy load PaymentModal to reduce initial bundle size (viem + @dynamic-labs = ~86MB)
const PaymentModal = dynamic(() => import('./PaymentModal'), {
Expand Down Expand Up @@ -46,6 +47,8 @@ export function PremiumPaywall({ children }: PremiumPaywallProps) {
useEffect(() => {
const handleOpenPaymentModal = () => {
setShowPaymentModal(true);
// Track when user views premium section
trackPremiumSectionViewed();
};
window.addEventListener('openPaymentModal', handleOpenPaymentModal);
return () => window.removeEventListener('openPaymentModal', handleOpenPaymentModal);
Expand Down
7 changes: 7 additions & 0 deletions app/components/ResultsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
selectAndSaveResultsFile,
markResultsUsed,
} from "@/lib/dev-mode";
import { trackResultsFileLoaded, trackResultsFileSaved } from "@/lib/analytics";

type ResultsContextType = {
savedResults: SavedResult[];
Expand Down Expand Up @@ -154,6 +155,9 @@ export function ResultsProvider({ children }: { children: ReactNode }) {

// Regular save
ResultsManager.saveResultsToFile(session);

// Track results file save
trackResultsFileSaved(savedResults.length);
};

const loadFromFile = async (currentFileHash?: string | null) => {
Expand Down Expand Up @@ -185,6 +189,9 @@ export function ResultsProvider({ children }: { children: ReactNode }) {
markResultsUsed();
}

// Track results file load
trackResultsFileLoaded(session.results.length);

// Call the callback if it exists
if (onResultsLoaded) {
onResultsLoaded();
Expand Down
10 changes: 2 additions & 8 deletions app/components/TermsAcceptanceModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState, useEffect } from "react";
import { trackModalOpen, trackTermsAcceptance } from "@/lib/analytics";
import { useState } from "react";
import { trackTermsAcceptance } from "@/lib/analytics";

type TermsAcceptanceModalProps = {
isOpen: boolean;
Expand All @@ -11,12 +11,6 @@ type TermsAcceptanceModalProps = {
export default function TermsAcceptanceModal({ isOpen, onAccept }: TermsAcceptanceModalProps) {
const [dontShowAgain, setDontShowAgain] = useState(false);

useEffect(() => {
if (isOpen) {
trackModalOpen('terms_acceptance');
}
}, [isOpen]);

if (!isOpen) return null;

const handleAccept = () => {
Expand Down
17 changes: 3 additions & 14 deletions app/components/UserDataUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import { useState, useRef, createContext, useContext, useCallback, useEffect } f
import { GenotypeData, detectAndParseGenotypeFile, validateFileSize, validateFileFormat } from "@/lib/genotype-parser";
import { calculateFileHash } from "@/lib/file-hash";
import {
trackFileUploadStart,
trackFileUploadSuccess,
trackFileUploadError,
trackFileCleared,
trackGenotypeFileLoaded,
} from "@/lib/analytics";
import {
isDevModeEnabled,
Expand Down Expand Up @@ -79,9 +77,6 @@ export function GenotypeProvider({ children }: { children: React.ReactNode }) {
setIsLoading(true);
setError(null);

// Track upload start
trackFileUploadStart(file.size, fileExtension);

try {
// Validate file size (50MB limit)
if (!validateFileSize(file, 50)) {
Expand Down Expand Up @@ -112,8 +107,8 @@ export function GenotypeProvider({ children }: { children: React.ReactNode }) {

const parseDuration = performance.now() - startTime;

// Track successful upload
trackFileUploadSuccess(file.size, genotypeMap.size, parseDuration);
// Track successful genotype file load
trackGenotypeFileLoaded(file.size, genotypeMap.size);

setGenotypeData(genotypeMap);
setFileHash(hash);
Expand All @@ -131,9 +126,6 @@ export function GenotypeProvider({ children }: { children: React.ReactNode }) {
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Upload failed';
setError(errorMessage);

// Track upload error
trackFileUploadError(errorMessage, file.size);
} finally {
setIsLoading(false);
}
Expand All @@ -144,9 +136,6 @@ export function GenotypeProvider({ children }: { children: React.ReactNode }) {
setError(null);
setFileHash(null);
setOriginalFileName(null);

// Track file cleared
trackFileCleared();
};

const setOnDataLoadedCallback = useCallback((cb: (() => void) | null) => {
Expand Down
2 changes: 0 additions & 2 deletions app/components/VariantChips.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { useGenotype } from "./UserDataUpload";
import { trackVariantClick } from "@/lib/analytics";
import { CheckIcon } from "./Icons";

type VariantChipsProps = {
Expand All @@ -28,7 +27,6 @@ export default function VariantChips({ snps, riskAllele }: VariantChipsProps) {
target="_blank"
rel="noreferrer"
title={userHasData ? `${riskAllele} - You have data for this variant` : riskAllele || undefined}
onClick={() => trackVariantClick(riskSnpId)}
aria-label={userHasData ? `${riskAllele} - You have data for this variant` : riskAllele || riskSnpId}
>
{riskAllele}
Expand Down
Loading
Loading