diff --git a/app/components/ConversionOnboarding.tsx b/app/components/ConversionOnboarding.tsx index 6d976b2..41128c0 100644 --- a/app/components/ConversionOnboarding.tsx +++ b/app/components/ConversionOnboarding.tsx @@ -10,11 +10,9 @@ import { runAllAnalysisOnboarding, type OnboardingRunAllProgress } from "@/lib/r import { useGenotype } from "./UserDataUpload"; import { useResults } from "./ResultsContext"; import LLMCommentaryModal from "./LLMCommentaryModal"; -import NilAIConsentModal from "./NilAIConsentModal"; + import { callLLM, getLLMDescription } from "@/lib/llm-client"; import { - trackAIConsentDeclined, - trackAIConsentGiven, trackLLMQuestionAsked, trackOnboardingAction, trackOnboardingCompleted, @@ -55,7 +53,6 @@ interface ConversionOnboardingProps { mode?: FlowMode; } -const CONSENT_STORAGE_KEY = "nilai_llm_consent_accepted"; const SAMPLE_DATA_URL = "/api/sample-genotype"; const GUIDE_23ANDME_URL = "https://monadicdna.com/guide/23andme"; const GUIDE_ANCESTRY_URL = "https://monadicdna.com/guide/ancestry"; @@ -228,8 +225,6 @@ export default function ConversionOnboarding({ const [mounted, setMounted] = useState(false); const [currentStep, setCurrentStep] = useState("intro"); const [completionPath, setCompletionPath] = useState("own_dna"); - const [showConsentModal, setShowConsentModal] = useState(false); - const [hasConsent, setHasConsent] = useState(false); const [traitCandidates, setTraitCandidates] = useState([]); const [selectedTraitIds, setSelectedTraitIds] = useState([]); const [previewResponses, setPreviewResponses] = useState([]); @@ -242,7 +237,6 @@ export default function ConversionOnboarding({ const [detailResult, setDetailResult] = useState(null); const [expandedTraitId, setExpandedTraitId] = useState(null); const [commentaryResult, setCommentaryResult] = useState(null); - const [pendingQuestion, setPendingQuestion] = useState(null); const [activeQuestion, setActiveQuestion] = useState(null); const [runAllProgress, setRunAllProgress] = useState({ phase: "downloading", @@ -260,9 +254,6 @@ export default function ConversionOnboarding({ useEffect(() => { setMounted(true); - if (typeof window !== "undefined") { - setHasConsent(localStorage.getItem(CONSENT_STORAGE_KEY) === "true"); - } }, []); useEffect(() => { @@ -283,9 +274,7 @@ export default function ConversionOnboarding({ setDetailResult(null); setExpandedTraitId(null); setCommentaryResult(null); - setPendingQuestion(null); setActiveQuestion(null); - setShowConsentModal(false); setRunAllProgress({ phase: "downloading", loaded: 0, @@ -309,11 +298,6 @@ export default function ConversionOnboarding({ } }, [completionPath, currentStep, isOpen, mode, mounted]); - useEffect(() => { - if (typeof window === "undefined" || commentaryResult) return; - setHasConsent(localStorage.getItem(CONSENT_STORAGE_KEY) === "true"); - }, [commentaryResult]); - const selectedTraitResults = useMemo( () => traitCandidates.filter((result) => selectedTraitIds.includes(result.studyId)), [selectedTraitIds, traitCandidates] @@ -577,7 +561,6 @@ RESPONSE STRUCTURE: setResponseError(null); setResponsesLoading(false); setDetailResult(null); - setPendingQuestion(null); setActiveQuestion(null); setCurrentStep("responses"); @@ -601,26 +584,6 @@ RESPONSE STRUCTURE: /> )} - { - localStorage.setItem(CONSENT_STORAGE_KEY, "true"); - setHasConsent(true); - setShowConsentModal(false); - trackAIConsentGiven(); - if (pendingQuestion) { - const nextQuestion = pendingQuestion; - setPendingQuestion(null); - void generateSecureResponses(nextQuestion); - return; - } - }} - onDecline={() => { - setPendingQuestion(null); - setShowConsentModal(false); - trackAIConsentDeclined(); - }} - />
@@ -1096,12 +1059,6 @@ RESPONSE STRUCTURE: return; } - if (!hasConsent) { - setPendingQuestion(question); - setShowConsentModal(true); - return; - } - void generateSecureResponses(question); }} disabled={responsesLoading && activeQuestion !== question} diff --git a/app/components/LLMChatInline.tsx b/app/components/LLMChatInline.tsx index ee4aea1..453200b 100644 --- a/app/components/LLMChatInline.tsx +++ b/app/components/LLMChatInline.tsx @@ -1,14 +1,17 @@ "use client"; import { useEffect, useState, useRef } from "react"; +import { useRouter } from "next/navigation"; import { SavedResult } from "@/lib/results-manager"; -import NilAIConsentModal from "./NilAIConsentModal"; import { useResults } from "./ResultsContext"; import { useCustomization } from "./CustomizationContext"; +import { useAuth } from "./AuthProvider"; +import { hasValidPromoAccess } from "@/lib/promo-access"; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { callLLM, callLLMStream, getLLMDescription, MessageContentPart } from "@/lib/llm-client"; -import { trackLLMQuestionAsked, trackAIConsentGiven, trackAIConsentDeclined, trackAIConsentModalShown, trackExampleQuestionClicked, trackFollowupQuestionClicked } from "@/lib/analytics"; +import { trackLLMQuestionAsked, trackExampleQuestionClicked, trackFollowupQuestionClicked } from "@/lib/analytics"; +import { runResearchPipeline, type ResearchAngle } from "@/lib/research-service"; type AttachmentType = 'text' | 'pdf' | 'csv' | 'tsv' | 'image'; @@ -29,9 +32,9 @@ type Message = { studiesUsed?: SavedResult[]; attachments?: Attachment[]; followupQuestions?: string[]; + researchMeta?: ResearchAngle[]; }; -const CONSENT_STORAGE_KEY = "nilai_llm_chat_consent_accepted"; const MAX_CONTEXT_RESULTS = 500; const MAX_TEXT_FILE_SIZE = 2 * 1024 * 1024; // 2MB for text/csv/tsv const MAX_PDF_FILE_SIZE = 5 * 1024 * 1024; // 5MB for PDF @@ -51,9 +54,12 @@ const EXAMPLE_QUESTIONS = [ export default function AIChatInline({ initialInput }: { initialInput?: string } = {}) { + const router = useRouter(); const resultsContext = useResults(); const { getTopResultsByRelevance } = resultsContext; const { customization, status: customizationStatus } = useCustomization(); + const { isAuthenticated, hasActiveSubscription, openAuthModal } = useAuth(); + const [hasPromoAccess, setHasPromoAccess] = useState(false); const [mounted, setMounted] = useState(false); const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(""); @@ -61,63 +67,51 @@ export default function AIChatInline({ initialInput }: { initialInput?: string } useEffect(() => { if (!initialInput) return; setInputValue(initialInput); - const t = setTimeout(() => handleSendMessage(false, initialInput), 0); + const t = setTimeout(() => handleSendMessage(initialInput), 0); return () => clearTimeout(t); // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialInput]); const [isLoading, setIsLoading] = useState(false); const [loadingStatus, setLoadingStatus] = useState(""); const [error, setError] = useState(null); - const [showConsentModal, setShowConsentModal] = useState(false); - const [hasConsent, setHasConsent] = useState(false); const [expandedMessageIndex, setExpandedMessageIndex] = useState(null); const [attachedFiles, setAttachedFiles] = useState([]); const [attachmentError, setAttachmentError] = useState(null); const [expandedAttachmentIndex, setExpandedAttachmentIndex] = useState(null); + const [expandedResearchIndex, setExpandedResearchIndex] = useState(null); const inputRef = useRef(null); const fileInputRef = useRef(null); useEffect(() => { setMounted(true); - - // Check consent - const consent = localStorage.getItem(CONSENT_STORAGE_KEY); - if (consent === 'true') { - setHasConsent(true); - } }, []); - // Determine if this is the first message or a follow-up - const isFirstMessage = messages.length === 0; - useEffect(() => { - if (typeof window !== "undefined") { - const consent = localStorage.getItem(CONSENT_STORAGE_KEY); - setHasConsent(consent === "true"); - } + const refresh = () => setHasPromoAccess(hasValidPromoAccess()); + refresh(); + window.addEventListener('premiumAccessUpdated', refresh); + return () => window.removeEventListener('premiumAccessUpdated', refresh); }, []); + const hasPremiumAccess = hasActiveSubscription || hasPromoAccess; - // Removed auto-scroll so user doesn't have to scroll up to read responses - // Also removed auto-focus to prevent scrolling to bottom on tab load - - const handleConsentAccept = () => { - if (typeof window !== "undefined") { - localStorage.setItem(CONSENT_STORAGE_KEY, "true"); - setHasConsent(true); - setShowConsentModal(false); - trackAIConsentGiven(); - void handleSendMessage(true); + const requirePremium = (): boolean => { + if (!hasPremiumAccess && !hasValidPromoAccess()) { + if (!isAuthenticated) { openAuthModal(); return false; } + router.push('/subscribe'); + return false; } + return true; }; - const handleConsentDecline = () => { - setShowConsentModal(false); - trackAIConsentDeclined(); - }; + // Determine if this is the first message or a follow-up + const isFirstMessage = messages.length === 0; + // Removed auto-scroll so user doesn't have to scroll up to read responses + // Also removed auto-focus to prevent scrolling to bottom on tab load + const handleExampleClick = (question: string) => { setInputValue(question); inputRef.current?.focus(); @@ -126,7 +120,7 @@ export default function AIChatInline({ initialInput }: { initialInput?: string } const handleFollowupClick = (question: string) => { trackFollowupQuestionClicked(); - void handleSendMessage(false, question); + void handleSendMessage(question); }; const handleCopyMessage = async (content: string) => { @@ -307,17 +301,52 @@ export default function AIChatInline({ initialInput }: { initialInput?: string } return parts; }; - const handleSendMessage = async (skipConsentCheck = false, queryOverride?: string) => { + const buildUserContext = (): string => { + if (!customization) return ''; + const parts: string[] = []; + if (customization.ethnicities.length > 0) { + parts.push(`Ethnicities: ${customization.ethnicities.join(', ')}`); + } + if (customization.countriesOfOrigin.length > 0) { + parts.push(`Countries of ancestral origin: ${customization.countriesOfOrigin.join(', ')}`); + } + if (customization.genderAtBirth) { + parts.push(`Gender assigned at birth: ${customization.genderAtBirth}`); + } + if (customization.age) { + parts.push(`Age: ${customization.age}`); + } + if (customization.smokingHistory) { + const smokingLabel = customization.smokingHistory === 'still-smoking' ? 'Currently smoking' : + customization.smokingHistory === 'past-smoker' ? 'Former smoker' : + 'Never smoked'; + parts.push(`Smoking history: ${smokingLabel}`); + } + if (customization.alcoholUse) { + const alcoholLabel = customization.alcoholUse.charAt(0).toUpperCase() + customization.alcoholUse.slice(1); + parts.push(`Alcohol use: ${alcoholLabel}`); + } + if (customization.medications && customization.medications.length > 0) { + parts.push(`Current medications/supplements: ${customization.medications.join(', ')}`); + } + if (customization.diet) { + const dietLabel = customization.diet === 'regular' ? 'Regular diet (no restrictions)' : + customization.diet.charAt(0).toUpperCase() + customization.diet.slice(1) + ' diet'; + parts.push(`Dietary preferences: ${dietLabel}`); + } + if (customization.personalConditions && customization.personalConditions.length > 0) { + parts.push(`Personal medical history: ${customization.personalConditions.join(', ')}`); + } + if (customization.familyConditions && customization.familyConditions.length > 0) { + parts.push(`Family medical history: ${customization.familyConditions.join(', ')}`); + } + return parts.join('\n'); + }; + + const handleSendMessage = async (queryOverride?: string) => { const query = (queryOverride ?? inputValue).trim(); if (!query) return; - // Check consent before sending first message - if (!skipConsentCheck && !hasConsent) { - setShowConsentModal(true); - trackAIConsentModalShown(); - return; - } - setInputValue(""); setIsLoading(true); setError(null); @@ -371,55 +400,10 @@ export default function AIChatInline({ initialInput }: { initialInput?: string } console.log(`[LLM Chat] Including ${relevantResults.length} results in LLM context`); - let userContext = ''; - if (customization) { - const parts = []; - if (customization.ethnicities.length > 0) { - parts.push(`Ethnicities: ${customization.ethnicities.join(', ')}`); - } - if (customization.countriesOfOrigin.length > 0) { - parts.push(`Countries of ancestral origin: ${customization.countriesOfOrigin.join(', ')}`); - } - if (customization.genderAtBirth) { - parts.push(`Gender assigned at birth: ${customization.genderAtBirth}`); - } - if (customization.age) { - parts.push(`Age: ${customization.age}`); - } - if (customization.smokingHistory) { - const smokingLabel = customization.smokingHistory === 'still-smoking' ? 'Currently smoking' : - customization.smokingHistory === 'past-smoker' ? 'Former smoker' : - 'Never smoked'; - parts.push(`Smoking history: ${smokingLabel}`); - } - if (customization.alcoholUse) { - const alcoholLabel = customization.alcoholUse.charAt(0).toUpperCase() + customization.alcoholUse.slice(1); - parts.push(`Alcohol use: ${alcoholLabel}`); - } - if (customization.medications && customization.medications.length > 0) { - parts.push(`Current medications/supplements: ${customization.medications.join(', ')}`); - } - if (customization.diet) { - const dietLabel = customization.diet === 'regular' ? 'Regular diet (no restrictions)' : - customization.diet.charAt(0).toUpperCase() + customization.diet.slice(1) + ' diet'; - parts.push(`Dietary preferences: ${dietLabel}`); - } - if (customization.personalConditions && customization.personalConditions.length > 0) { - parts.push(`Personal medical history: ${customization.personalConditions.join(', ')}`); - } - if (customization.familyConditions && customization.familyConditions.length > 0) { - parts.push(`Family medical history: ${customization.familyConditions.join(', ')}`); - } - - if (parts.length > 0) { - userContext = ` - -USER BACKGROUND (CONFIDENTIAL - USE TO PERSONALIZE INTERPRETATION): -${parts.join('\n')} - -Consider how this user's background, lifestyle factors (smoking, alcohol, diet), and current medications may affect their risk profile and the applicability of these study findings.`; - } - } + const userContextSummary = buildUserContext(); + const userContext = userContextSummary + ? `\n\nUSER BACKGROUND (CONFIDENTIAL - USE TO PERSONALIZE INTERPRETATION):\n${userContextSummary}\n\nConsider how this user's background, lifestyle factors (smoking, alcohol, diet), and current medications may affect their risk profile and the applicability of these study findings.` + : ''; const llmDescription = getLLMDescription(); @@ -707,6 +691,84 @@ Write questions from the user's perspective — as if the user is asking you. No } }; + const handleResearch = async () => { + const query = inputValue.trim(); + if (!query || isLoading) return; + if (!requirePremium()) return; + + setInputValue(''); + setIsLoading(true); + setError(null); + setAttachmentError(null); + + trackLLMQuestionAsked({ isFollowUp: messages.length > 0 }); + + const userMessage: Message = { role: 'user', content: query, timestamp: new Date() }; + const assistantMessage: Message = { role: 'assistant', content: '', timestamp: new Date() }; + setMessages(prev => [...prev, userMessage, assistantMessage]); + + try { + const llmDescription = getLLMDescription(); + const customizationSummary = buildUserContext(); + + let capturedAngles: ResearchAngle[] | undefined; + + const stream = runResearchPipeline( + query, + customizationSummary, + resultsContext.savedResults.length, + getTopResultsByRelevance, + setLoadingStatus, + llmDescription, + (angles) => { capturedAngles = angles; }, + ); + + let accumulatedContent = ''; + let isFirstChunk = true; + + for await (const chunk of stream) { + if (isFirstChunk) { + setIsLoading(false); + setLoadingStatus(''); + isFirstChunk = false; + } + accumulatedContent += chunk; + setMessages(prev => { + const updated = [...prev]; + updated[updated.length - 1] = { ...updated[updated.length - 1], content: accumulatedContent }; + return updated; + }); + } + + if (!accumulatedContent) throw new Error('No response from research pipeline.'); + + const followupMatch = accumulatedContent.split(/\n+FOLLOWUP:\n/); + const displayContent = followupMatch[0].trim(); + const followupQuestions = followupMatch[1] + ? followupMatch[1].split('\n').filter(l => l.startsWith('- ')).map(l => l.slice(2).trim()).filter(Boolean) + : []; + + setMessages(prev => { + const updated = [...prev]; + updated[updated.length - 1] = { ...updated[updated.length - 1], content: displayContent, followupQuestions, researchMeta: capturedAngles }; + return updated; + }); + } catch (err) { + console.error('[Research] Error:', err); + let errorMessage = err instanceof Error ? err.message : 'Research failed.'; + if (errorMessage.includes('429')) errorMessage = 'Rate limit exceeded. Please wait and try again.'; + setError(errorMessage); + setMessages(prev => { + const lastMsg = prev[prev.length - 1]; + if (lastMsg?.role === 'assistant' && !lastMsg.content) return prev.slice(0, -1); + return prev; + }); + } finally { + setIsLoading(false); + setLoadingStatus(''); + } + }; + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); @@ -884,13 +946,6 @@ Write questions from the user's perspective — as if the user is asking you. No return ( <> - {showConsentModal && ( - - )}
{messages.length > 0 && (
@@ -908,7 +963,7 @@ Write questions from the user's perspective — as if the user is asking you. No
{messages.length === 0 && (
- {} +

Try asking something about your results:

    {EXAMPLE_QUESTIONS.map((question) => ( @@ -986,6 +1041,28 @@ Write questions from the user's perspective — as if the user is asking you. No )}
)} + {message.role === 'assistant' && message.researchMeta && message.researchMeta.length > 0 && ( +
+ + {expandedResearchIndex === idx && ( +
+ {message.researchMeta.map((angle, aidx) => ( +
+
{angle.keyword}
+
+ {angle.resultsCount} studies searched +
+
+ ))} +
+ )} +
+ )} {message.role === 'assistant' && isLastAssistantMessage && !isLoading && (message.followupQuestions?.length ?? 0) > 0 && (
Try asking:
@@ -1156,6 +1233,15 @@ Write questions from the user's perspective — as if the user is asking you. No > Attach +
+
+

+ Send — quick answer. Research — explores 10 genetic angles in depth. +

+
diff --git a/app/components/LLMCommentaryModal.tsx b/app/components/LLMCommentaryModal.tsx index c57d183..435cb83 100644 --- a/app/components/LLMCommentaryModal.tsx +++ b/app/components/LLMCommentaryModal.tsx @@ -3,12 +3,12 @@ import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { SavedResult } from "@/lib/results-manager"; -import NilAIConsentModal from "./NilAIConsentModal"; + import StudyQualityIndicators from "./StudyQualityIndicators"; import { useResults } from "./ResultsContext"; import { useCustomization } from "./CustomizationContext"; import { callLLM, getLLMDescription } from "@/lib/llm-client"; -import { trackAIAnalysisRun } from "@/lib/analytics"; + type LLMCommentaryModalProps = { isOpen: boolean; @@ -16,11 +16,8 @@ type LLMCommentaryModalProps = { currentResult: SavedResult; allResults: SavedResult[]; // Deprecated - will use SQL query instead skipPersonalizationPrompt?: boolean; - skipConsent?: boolean; }; -const CONSENT_STORAGE_KEY = "nilai_llm_consent_accepted"; - // Helper function to format risk scores consistently function formatRiskScore(score: number, level: string, effectType?: 'OR' | 'beta'): string { if (level === 'neutral') return effectType === 'beta' ? 'baseline' : '1.0x'; @@ -36,7 +33,6 @@ export default function LLMCommentaryModal({ currentResult, allResults, // Deprecated parameter skipPersonalizationPrompt = false, - skipConsent = false, }: LLMCommentaryModalProps) { const resultsContext = useResults(); const { getTopResultsByRelevance } = resultsContext; @@ -45,9 +41,7 @@ export default function LLMCommentaryModal({ const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [delegationStatus, setDelegationStatus] = useState(""); - const [showConsentModal, setShowConsentModal] = useState(false); const [showPersonalizationPrompt, setShowPersonalizationPrompt] = useState(false); - const [hasConsent, setHasConsent] = useState(false); const [studyMetadata, setStudyMetadata] = useState(null); const [loadingPhase, setLoadingPhase] = useState<'query' | 'metadata' | 'token' | 'llm' | 'done'>('query'); const [resultsCount, setResultsCount] = useState(0); @@ -56,52 +50,17 @@ export default function LLMCommentaryModal({ const [hasCustomization, setHasCustomization] = useState(false); const [usedSemanticSearch, setUsedSemanticSearch] = useState(false); - useEffect(() => { - // Check if user has previously consented - if (typeof window !== "undefined") { - const consent = localStorage.getItem(CONSENT_STORAGE_KEY); - setHasConsent(consent === "true"); - } - }, []); - useEffect(() => { if (isOpen) { - if (skipConsent) { + if (skipPersonalizationPrompt || customizationStatus === 'unlocked') { setShowPersonalizationPrompt(false); - setShowConsentModal(false); fetchCommentary(); - return; - } - - if (skipPersonalizationPrompt) { - setShowPersonalizationPrompt(false); - setShowConsentModal(true); - return; - } - - if (customizationStatus === 'not-set' || customizationStatus === 'locked') { - setShowPersonalizationPrompt(true); } else { - setShowConsentModal(true); + setShowPersonalizationPrompt(true); } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isOpen, customizationStatus, skipPersonalizationPrompt, skipConsent]); - - const handleConsentAccept = () => { - if (typeof window !== "undefined") { - localStorage.setItem(CONSENT_STORAGE_KEY, "true"); - setHasConsent(true); - setShowConsentModal(false); - trackAIAnalysisRun(); - fetchCommentary(); - } - }; - - const handleConsentDecline = () => { - setShowConsentModal(false); - onClose(); - }; + }, [isOpen, customizationStatus, skipPersonalizationPrompt]); const fetchCommentary = async () => { console.log('[fetchCommentary] Starting...'); @@ -733,7 +692,7 @@ Keep your response concise (400-600 words), educational, and reassuring where ap const handlePersonalizationPromptContinue = () => { setShowPersonalizationPrompt(false); - setShowConsentModal(true); + fetchCommentary(); }; if (!isOpen) return null; @@ -798,13 +757,7 @@ Keep your response concise (400-600 words), educational, and reassuring where ap : null; } - const modalContent = showConsentModal ? ( - - ) : ( + const modalContent = (
- - Analyze - Premium - + Analyze
diff --git a/app/components/PremiumFeatureHeader.tsx b/app/components/PremiumFeatureHeader.tsx index aac1dbb..8653c38 100644 --- a/app/components/PremiumFeatureHeader.tsx +++ b/app/components/PremiumFeatureHeader.tsx @@ -9,11 +9,15 @@ import { trackOverviewReportTabViewed } from "@/lib/analytics"; type PremiumFeatureHeaderProps = { featureName: string; description: string; + gateTitle?: string; // overrides default "featureName is a premium tab" / "Premium subscription required" + gateDescription?: string; // overrides default "Subscribe for $4.99/month to access featureName." }; export default function PremiumFeatureHeader({ featureName, description, + gateTitle, + gateDescription, }: PremiumFeatureHeaderProps) { const { isAuthenticated, @@ -60,7 +64,7 @@ export default function PremiumFeatureHeader({ {!isAuthenticated && !hasPromoAccess ? (
- {featureName} is a premium tab + {gateTitle ?? `${featureName} is a premium tab`} {description}
+ {sampleProgressText && ( +

+ {sampleProgressText} +

+ )} + {sampleError && ( +

{sampleError}

+ )} +

+ No DNA file needed. Your data never leaves your device.{' '} + trackGetStartedClicked("schedule_video_call")} + > + Need help? Book a free call. + +

+
+
{featureCopy.map((item) => (

{item.href ? ( - - {item.label} - + item.external ? ( + + {item.label} + + ) : ( + + {item.label} + + ) ) : ( item.label )} @@ -175,45 +221,6 @@ export default function LandingClient() {

))}
- - {error &&

{error}

} - -
-
- - - No DNA file? Load an example to explore the app. - -
- {sampleProgressText && ( -

- {sampleProgressText} -

- )} - {sampleError && ( -

{sampleError}

- )} -
- -

- New to the app?{' '} - trackGetStartedClicked("schedule_video_call")} - > - Book a free help call. - -

diff --git a/app/overview-report/page.tsx b/app/overview-report/page.tsx index 0523073..a675d42 100644 --- a/app/overview-report/page.tsx +++ b/app/overview-report/page.tsx @@ -43,6 +43,7 @@ export default function OverviewReportPage() { trackOverviewReportViewed(); }, []); + useEffect(() => { if (!hasCompletedTour(overviewReportTour.id)) { setTourOpen(true); @@ -67,7 +68,7 @@ export default function OverviewReportPage() { }; const handleGenerateHealthReport = () => { - if (!hasResults || !requirePremium()) return; + if (!hasResults) return; setShowHealthReportModal(true); }; @@ -84,10 +85,12 @@ export default function OverviewReportPage() { return (
-
+