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
50 changes: 36 additions & 14 deletions app/browse/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import {
trackQueryRun,
trackExploreTabViewed,
trackSearchModeChanged,
trackBrowsePageViewed,
trackBrowseFiltersExpanded,
trackBrowseSearchChanged,
trackStudyOpened,
trackRunAllCtaClicked,
} from "@/lib/analytics";

// Note: Metadata must be exported from layout.tsx or a server component
Expand Down Expand Up @@ -310,13 +315,7 @@ function ExplorePage() {
useEffect(() => {
if (mounted) {
trackExploreTabViewed();
}
}, [mounted]);

// Auto-show guided tour on first visit
useEffect(() => {
if (mounted && !hasCompletedTour(exploreTour.id)) {
setTourOpen(true);
trackBrowsePageViewed();
}
}, [mounted]);

Expand All @@ -340,7 +339,7 @@ function ExplorePage() {
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [sectionCollapsed, setSectionCollapsed] = useState(true);
const [sectionCollapsed, setSectionCollapsed] = useState(false);
const [showTermsModal, setShowTermsModal] = useState(false);
const [isRunningAll, setIsRunningAll] = useState(false);
const [runAllProgress, setRunAllProgress] = useState({ current: 0, total: 0 });
Expand Down Expand Up @@ -415,6 +414,7 @@ function ExplorePage() {
startTransition(() => {
updateFilter("search", value);
});
trackBrowseSearchChanged(value);
}
}, [filters.search, updateFilter]);

Expand Down Expand Up @@ -577,6 +577,15 @@ function ExplorePage() {
userInitiatedSearchRef.current = false;
};

const SEARCH_CHIPS = ['sleep', 'cholesterol', 'caffeine', "Alzheimer's", 'diabetes'] as const;

const handleChipClick = (term: string) => {
userInitiatedSearchRef.current = true;
startTransition(() => updateFilter("search", term));
setSectionCollapsed(false);
trackBrowseSearchChanged(term);
};


const handleColumnSort = (sortKey: SortOption) => {
const newDirection = filters.sort === sortKey
Expand Down Expand Up @@ -608,6 +617,7 @@ function ExplorePage() {
};

const handleRunAll = () => {
trackRunAllCtaClicked();
if (!genotypeData || genotypeData.size === 0) {
trackRunAllFailed("explore", "no_genotype_data");
alert("No SNPs found in your genetic data");
Expand Down Expand Up @@ -782,13 +792,22 @@ function ExplorePage() {
<p>Filter genetic association studies by various criteria.</p>
</>
)}
{sectionCollapsed && <h3>Study Filters</h3>}
{sectionCollapsed && (
<>
<h3>Study Filters</h3>
<div className="search-chips">
{SEARCH_CHIPS.map(term => (
<button key={term} className="search-chip" type="button" onClick={() => handleChipClick(term)}>
{term}
</button>
))}
</div>
</>
)}
</div>
<div className="hero-controls">
{!sectionCollapsed && (
<button className="tour-trigger-link" type="button" onClick={() => setTourOpen(true)}>
Take the tour
</button>
<button className="tour-help-button" type="button" onClick={() => setTourOpen(true)} title="Show tour" aria-label="Show tour">?</button>
)}
{!sectionCollapsed && (
<button className="reset-button" type="button" onClick={resetFilters}>
Expand All @@ -798,7 +817,10 @@ function ExplorePage() {
<button
className="collapse-button"
type="button"
onClick={() => setSectionCollapsed(!sectionCollapsed)}
onClick={() => {
if (sectionCollapsed) trackBrowseFiltersExpanded();
setSectionCollapsed(!sectionCollapsed);
}}
title={sectionCollapsed ? "Expand" : "Collapse"}
>
{sectionCollapsed ? "↓" : "↑"}
Expand Down Expand Up @@ -1083,7 +1105,7 @@ function ExplorePage() {
<tr key={`${study.id}-${index}`} className={study.isLowQuality ? "low-quality" : undefined}>
<td data-label="Study">
<div className="study-title">
<Link href={`/study/${study.id}`} style={{ textDecoration: "none", color: "inherit" }}>
<Link href={`/study/${study.id}`} style={{ textDecoration: "none", color: "inherit" }} onClick={() => trackStudyOpened(study.id)}>
{study.study ?? "Untitled study"}
</Link>
</div>
Expand Down
4 changes: 1 addition & 3 deletions app/components/MenuBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -551,9 +551,7 @@ export default function MenuBar() {

</div>
<div className="menu-explain-row">
<button className="menu-explain-link" onClick={() => setMenuTourOpen(true)}>
Explain these buttons
</button>
<button className="tour-help-button" type="button" onClick={() => setMenuTourOpen(true)} title="Show tour" aria-label="Show tour">?</button>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/PaymentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ export default function PaymentModal({ isOpen = true, onClose, onSuccess, displa

<div className="modal-header">
<h2>Subscribe to Premium</h2>
<p>Get DNA Chat and Overview Report</p>
<p>Research in DNA Chat + all premium reports</p>
</div>

{step === 'choice' && (
Expand Down
4 changes: 2 additions & 2 deletions app/components/StripeSubscriptionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ function SubscriptionForm({ clientSecret, walletAddress, couponCode, discount, i
<div className="features-list">
<div className="feature-item">
<span className="feature-icon">✓</span>
<span>DNA Chat Assistant</span>
<span>Research in DNA Chat</span>
</div>
<div className="feature-item">
<span className="feature-icon">✓</span>
<span>Overview Report</span>
<span>All premium reports (Healthspan, Top Traits, Overview)</span>
</div>
</div>

Expand Down
29 changes: 25 additions & 4 deletions app/components/UserDataUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,31 @@ export function GenotypeProvider({ children }: { children: React.ReactNode }) {
}

if (!validateFileFormat(file)) {
throw new Error('Unsupported file type. Please upload a .txt, .tsv, or .csv file exported from 23andMe, AncestryDNA, MyHeritage, FTDNA, LivingDNA, or a compatible provider.');
throw new Error('Unsupported file type. Please upload a .txt, .tsv, .csv, or .gz file exported from 23andMe, AncestryDNA, MyHeritage, FTDNA, LivingDNA, or a compatible provider.');
}

const fileContent = await file.text();
let fileContent: string;
if (fileExtension === 'gz') {
const buffer = await file.arrayBuffer();
const ds = new DecompressionStream('gzip');
const writer = ds.writable.getWriter();
writer.write(buffer);
writer.close();
const chunks: Uint8Array[] = [];
const reader = ds.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value) chunks.push(value);
}
const total = chunks.reduce((n, c) => n + c.length, 0);
const combined = new Uint8Array(total);
let offset = 0;
for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.length; }
fileContent = new TextDecoder().decode(combined);
} else {
fileContent = await file.text();
}
const hash = calculateFileHash(fileContent);

const parseResult = detectAndParseGenotypeFile(fileContent);
Expand Down Expand Up @@ -199,7 +220,7 @@ export default function UserDataUpload() {
<input
ref={fileInputRef}
type="file"
accept=".txt,.tsv,.csv"
accept=".txt,.tsv,.csv,.gz"
onChange={handleFileSelect}
className="genotype-file-input"
id="genotype-upload"
Expand All @@ -208,7 +229,7 @@ export default function UserDataUpload() {
<label htmlFor="genotype-upload" className={`genotype-upload-label ${isLoading ? 'loading' : ''}`}>
{isLoading ? 'Analyzing your genetic map...' : 'Choose File to Upload'}
</label>
<p className="upload-format-hint">23andMe, AncestryDNA, MyHeritage, FTDNA, LivingDNA, and more</p>
<p className="upload-format-hint">23andMe, AncestryDNA, MyHeritage, FTDNA, LivingDNA, and more. Compressed .gz files supported.</p>
{error && (
<div className="genotype-error">
{error}
Expand Down
49 changes: 0 additions & 49 deletions app/components/tours/tourContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,6 @@ export const exploreTour: TourContent = {
selector: '[data-tour="run-all-button"]',
placement: "bottom",
},
{
name: "your_result",
title: "Your personal result",
body: "After analyzing, the Your Result column shows how your genotype compares to each study. Click any row to see the details.",
selector: '[data-tour="your-result-header"]',
placement: "bottom",
},
],
};

Expand All @@ -73,27 +66,6 @@ export const dnaChatTour: TourContent = {
body: "DNA Chat is an anonymous and confidential LLM chat that uses your genetic results as context. Ask anything about your traits, sleep, diet, or risks.",
},
myDataStep,
{
name: "prompts",
title: "Try a suggested prompt",
body: "Not sure what to ask? Tap any of these suggestions to fill the chat box. They're a great starting point.",
selector: ".example-questions",
placement: "top",
},
{
name: "input",
title: "Or type your own",
body: "Type any question about your DNA here. The chat keeps your context across follow-up questions.",
selector: "textarea.chat-input",
placement: "top",
},
{
name: "send",
title: "Send your question",
body: "Click here to send. The first answer pulls in your most relevant results as context and follow-ups continue the conversation.",
selector: ".chat-send-button",
placement: "top",
},
{
name: "attach",
title: "Upload lab reports and documents",
Expand Down Expand Up @@ -161,13 +133,6 @@ export const menuBarTour: TourContent = {
selector: '[data-tour="my-data-button"]',
placement: "bottom",
},
{
name: "results",
title: "Results",
body: "Save your analysis results to a file or load a previous session. Use this to pick up where you left off or share results between devices.",
selector: '[data-tour="results-button"]',
placement: "bottom",
},
{
name: "run_all",
title: "Run All",
Expand Down Expand Up @@ -196,19 +161,5 @@ export const menuBarTour: TourContent = {
selector: '[data-tour="cache-button"]',
placement: "bottom",
},
{
name: "help",
title: "Help",
body: "Reopen the onboarding tour or find answers to common questions here.",
selector: '[data-tour="help-button"]',
placement: "bottom",
},
{
name: "theme",
title: "Light / Dark mode",
body: "Toggle between light and dark themes. Your preference is saved and applied on every visit.",
selector: '[data-tour="theme-button"]',
placement: "bottom",
},
],
};
16 changes: 9 additions & 7 deletions app/dna-chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,15 @@ export default function DNAChatPage() {
)}

<div className="dna-chat-inline-wrapper">
<button
className="dna-chat-tour-button"
type="button"
onClick={() => setTourOpen(true)}
>
Show me how to use this
</button>
<div style={{ textAlign: "right", marginBottom: "0.4rem" }}>
<button
className="tour-help-button"
type="button"
onClick={() => setTourOpen(true)}
title="Show tour"
aria-label="Show tour"
>?</button>
</div>
<LLMChatInline initialInput={initialChatInput} />
</div>
</section>
Expand Down
Loading
Loading