From 5cb5969aed4bca313bd4e55e4fda3399f8135e95 Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 10:41:57 -0500 Subject: [PATCH 01/10] Chnaged menu bar to icon-driven navigation. --- app/components/MenuBar.tsx | 243 ++++++++++++------------------ app/components/MenuDropdowns.tsx | 251 +++++++++++++++++++++++++++++++ app/globals.css | 128 ++++++++++++++++ 3 files changed, 478 insertions(+), 144 deletions(-) create mode 100644 app/components/MenuDropdowns.tsx diff --git a/app/components/MenuBar.tsx b/app/components/MenuBar.tsx index 152a45a..2235c96 100644 --- a/app/components/MenuBar.tsx +++ b/app/components/MenuBar.tsx @@ -6,9 +6,8 @@ import { useResults } from "./ResultsContext"; import { useCustomization } from "./CustomizationContext"; import CustomizationModal from "./CustomizationModal"; import LLMConfigModal from "./LLMConfigModal"; -import { FileIcon, SaveIcon, TrashIcon, MessageIcon, ClockIcon } from "./Icons"; +import { MyDataDropdown, ResultsDropdown, CacheDropdown, HelpDropdown } from "./MenuDropdowns"; import { AuthButton, useAuth } from "./AuthProvider"; -import { getLLMConfig, getProviderDisplayName } from "@/lib/llm-config"; export default function MenuBar() { const { isUploaded, genotypeData, fileHash } = useGenotype(); @@ -18,9 +17,12 @@ export default function MenuBar() { const [isLoadingFile, setIsLoadingFile] = useState(false); const [showCustomizationModal, setShowCustomizationModal] = useState(false); const [showLLMConfigModal, setShowLLMConfigModal] = useState(false); + const [showMyDataDropdown, setShowMyDataDropdown] = useState(false); + const [showResultsDropdown, setShowResultsDropdown] = useState(false); + const [showCacheDropdown, setShowCacheDropdown] = useState(false); + const [showHelpDropdown, setShowHelpDropdown] = useState(false); const [theme, setTheme] = useState<"light" | "dark">("dark"); const [cacheInfo, setCacheInfo] = useState<{ studies: number; sizeMB: number } | null>(null); - const [llmProvider, setLlmProvider] = useState(''); const [showSubscriptionMenu, setShowSubscriptionMenu] = useState(false); useEffect(() => { @@ -48,10 +50,6 @@ export default function MenuBar() { document.documentElement.setAttribute("data-theme", initialTheme); document.documentElement.style.colorScheme = initialTheme; - // Load LLM config - const config = getLLMConfig(); - setLlmProvider(getProviderDisplayName(config.provider)); - // Load cache info const loadCacheInfo = async () => { const { gwasDB } = await import('@/lib/gwas-db'); @@ -90,17 +88,6 @@ export default function MenuBar() { } }; - const getCustomizationIcon = () => { - switch (customizationStatus) { - case 'not-set': - return '⚙️'; - case 'locked': - return '🔒'; - case 'unlocked': - return '🔓'; - } - }; - const getCustomizationTooltip = () => { switch (customizationStatus) { case 'not-set': @@ -112,10 +99,24 @@ export default function MenuBar() { } }; - const handleLLMConfigSave = () => { - // Reload LLM provider display name - const config = getLLMConfig(); - setLlmProvider(getProviderDisplayName(config.provider)); + const handleClearCache = async () => { + if (!cacheInfo) return; + + const confirmed = window.confirm( + `Clear cached GWAS Catalog data?\n\n` + + `${cacheInfo.studies.toLocaleString()} studies (${cacheInfo.sizeMB} MB)\n\n` + + `Data will be re-downloaded on next Run All.` + ); + if (confirmed) { + try { + const { gwasDB } = await import('@/lib/gwas-db'); + await gwasDB.clearDatabase(); + setCacheInfo(null); + alert('✓ Cache cleared successfully!'); + } catch { + alert('Failed to clear cache. Please try again.'); + } + } }; return ( @@ -127,7 +128,33 @@ export default function MenuBar() { setShowLLMConfigModal(false)} - onSave={handleLLMConfigSave} + onSave={() => {}} + /> + setShowMyDataDropdown(false)} + isUploaded={isUploaded} + genotypeData={genotypeData} + UserDataUploadComponent={UserDataUpload} + /> + setShowResultsDropdown(false)} + savedResults={savedResults} + onLoadFromFile={handleLoadFromFile} + onSaveToFile={() => saveToFile(genotypeData?.size, fileHash || undefined)} + onClearResults={clearResults} + isLoadingFile={isLoadingFile} + /> + setShowCacheDropdown(false)} + cacheInfo={cacheInfo} + onClearCache={handleClearCache} + /> + setShowHelpDropdown(false)} />
@@ -146,151 +173,79 @@ export default function MenuBar() {
-
- {isUploaded && genotypeData && ( - - {genotypeData.size.toLocaleString()} variants loaded - - )} - -
+ {/* Icon-based Navigation */} +
+ -
-
- {savedResults.length > 0 && ( - - {savedResults.length} result{savedResults.length !== 1 ? 's' : ''} cached - - )} -
- + - - + {savedResults.length} )} -
-
- -
+ -
-
- -
- -
- {cacheInfo && ( - <> - - {cacheInfo.studies.toLocaleString()} studies cached ({cacheInfo.sizeMB} MB) - - - - )} - - setShowCacheDropdown(!showCacheDropdown)} + title="View and manage cached GWAS data" > - Feedback - + 💎 + Cache +
+ + +
+ {isAuthenticated && hasActiveSubscription && subscriptionData && ( <>
@@ -334,7 +289,7 @@ export default function MenuBar() { } else { alert('Failed to cancel subscription: ' + (result.error || 'Unknown error')); } - } catch (error) { + } catch { alert('Failed to cancel subscription. Please try again.'); } } diff --git a/app/components/MenuDropdowns.tsx b/app/components/MenuDropdowns.tsx new file mode 100644 index 0000000..c3e7420 --- /dev/null +++ b/app/components/MenuDropdowns.tsx @@ -0,0 +1,251 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { FileIcon, SaveIcon, TrashIcon, MessageIcon, ClockIcon } from "./Icons"; + +// My Data Dropdown +export function MyDataDropdown({ + isOpen, + onClose, + isUploaded, + genotypeData, + UserDataUploadComponent, +}: { + isOpen: boolean; + onClose: () => void; + isUploaded: boolean; + genotypeData: { size: number } | null; + UserDataUploadComponent: React.ComponentType; +}) { + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + } + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+
+

My Data

+ {isUploaded && genotypeData && ( +

{genotypeData.size.toLocaleString()} variants loaded

+ )} + +
+
+ ); +} + +// Results Dropdown +export function ResultsDropdown({ + isOpen, + onClose, + savedResults, + onLoadFromFile, + onSaveToFile, + onClearResults, + isLoadingFile, +}: { + isOpen: boolean; + onClose: () => void; + savedResults: unknown[]; + onLoadFromFile: () => void; + onSaveToFile: () => void; + onClearResults: () => void; + isLoadingFile: boolean; +}) { + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + } + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+
+

Results

+ {savedResults.length > 0 && ( +

+ {savedResults.length} result{savedResults.length !== 1 ? "s" : ""} cached +

+ )} +
+ + {savedResults.length > 0 && ( + <> + + + + )} +
+
+
+ ); +} + +// Cache Dropdown +export function CacheDropdown({ + isOpen, + onClose, + cacheInfo, + onClearCache, +}: { + isOpen: boolean; + onClose: () => void; + cacheInfo: { studies: number; sizeMB: number } | null; + onClearCache: () => void; +}) { + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + } + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+
+

Cache

+ {cacheInfo ? ( + <> +

+ {cacheInfo.studies.toLocaleString()} studies cached ({cacheInfo.sizeMB} MB) +

+
+ +
+ + ) : ( +

No cached data

+ )} +
+
+ ); +} + +// Help Dropdown +export function HelpDropdown({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + } + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+
+

Help & Feedback

+ +
+
+ ); +} diff --git a/app/globals.css b/app/globals.css index b146769..4221dd5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -132,6 +132,134 @@ body { box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2); } +/* Icon-based Menu Navigation */ +.menu-icons { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.menu-icon-button { + background: var(--surface-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 0.5rem 0.75rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + min-width: 70px; + position: relative; +} + +.menu-icon-button .icon { + font-size: 1.5rem; + line-height: 1; +} + +.menu-icon-button .label { + font-size: 0.75rem; + font-weight: 500; + color: var(--text-secondary); + line-height: 1; +} + +.menu-icon-button .badge { + position: absolute; + top: 0.25rem; + right: 0.25rem; + background: var(--accent-blue); + color: white; + font-size: 0.65rem; + font-weight: 600; + padding: 0.1rem 0.35rem; + border-radius: 10px; + min-width: 18px; + text-align: center; +} + +.menu-icon-button:hover { + background: rgba(59, 130, 246, 0.1); + border-color: var(--accent-blue); + transform: translateY(-2px); +} + +.menu-icon-button:hover .label { + color: var(--text-primary); +} + +.menu-icon-button.not-set { + border-color: rgba(245, 158, 11, 0.3); +} + +.menu-icon-button.locked { + border-color: rgba(239, 68, 68, 0.3); +} + +.menu-icon-button.unlocked { + border-color: rgba(16, 185, 129, 0.3); +} + +/* Menu Dropdowns */ +.menu-dropdown { + position: fixed; + top: 5rem; + right: 2rem; + background: var(--secondary-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1.5rem; + min-width: 300px; + max-width: 400px; + backdrop-filter: blur(20px); + box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.3); + z-index: 1000; + animation: slideDown 0.2s ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.dropdown-content h3 { + margin: 0 0 1rem 0; + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); +} + +.dropdown-content .stat-display { + font-size: 0.9rem; + color: var(--text-secondary); + margin-bottom: 1rem; + padding: 0.5rem; + background: rgba(59, 130, 246, 0.05); + border: 1px solid rgba(59, 130, 246, 0.15); + border-radius: 6px; + text-align: center; +} + +.dropdown-actions { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.dropdown-actions .control-button, +.dropdown-actions a { + width: 100%; + justify-content: center; +} + .app-title { margin: 0; font-size: 1.5rem; From 13b026a72e86f846119f36eab0d3111ead108a68 Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 10:49:09 -0500 Subject: [PATCH 02/10] Chnages emoji icons to dark SVGs. --- app/components/Icons.tsx | 88 ++++++++++++++++++++++++++++++++++++++ app/components/MenuBar.tsx | 25 ++++++++--- app/globals.css | 15 ++++++- 3 files changed, 121 insertions(+), 7 deletions(-) diff --git a/app/components/Icons.tsx b/app/components/Icons.tsx index d7fe1ea..bfa2523 100644 --- a/app/components/Icons.tsx +++ b/app/components/Icons.tsx @@ -255,3 +255,91 @@ export function AIIcon({ className = "", size = 16 }: IconProps) { ); } + +export function MicroscopeIcon({ className = "", size = 20 }: IconProps) { + return ( + + + + + + + + + + ); +} + +export function SparklesIcon({ className = "", size = 20 }: IconProps) { + return ( + + + + + + ); +} + +export function CacheIcon({ className = "", size = 20 }: IconProps) { + return ( + + + + + + ); +} + +export function HelpCircleIcon({ className = "", size = 20 }: IconProps) { + return ( + + + + + + ); +} + +export function FolderIcon({ className = "", size = 20 }: IconProps) { + return ( + + + + + ); +} diff --git a/app/components/MenuBar.tsx b/app/components/MenuBar.tsx index 2235c96..c9f4220 100644 --- a/app/components/MenuBar.tsx +++ b/app/components/MenuBar.tsx @@ -7,6 +7,7 @@ import { useCustomization } from "./CustomizationContext"; import CustomizationModal from "./CustomizationModal"; import LLMConfigModal from "./LLMConfigModal"; import { MyDataDropdown, ResultsDropdown, CacheDropdown, HelpDropdown } from "./MenuDropdowns"; +import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon } from "./Icons"; import { AuthButton, useAuth } from "./AuthProvider"; export default function MenuBar() { @@ -180,7 +181,9 @@ export default function MenuBar() { onClick={() => setShowMyDataDropdown(!showMyDataDropdown)} title="Upload and manage your genetic data" > - 🧬 + + + My Data @@ -189,7 +192,9 @@ export default function MenuBar() { onClick={() => setShowResultsDropdown(!showResultsDropdown)} title="Load, export, and manage results" > - 🗄️ + + + Results {savedResults.length > 0 && ( {savedResults.length} @@ -201,7 +206,9 @@ export default function MenuBar() { onClick={() => setShowCustomizationModal(true)} title={getCustomizationTooltip()} > - 🔬 + + + Personalize @@ -210,7 +217,9 @@ export default function MenuBar() { onClick={() => setShowLLMConfigModal(true)} title="Configure LLM provider and model" > - + + + LLM @@ -219,7 +228,9 @@ export default function MenuBar() { onClick={() => setShowCacheDropdown(!showCacheDropdown)} title="View and manage cached GWAS data" > - 💎 + + + Cache @@ -228,7 +239,9 @@ export default function MenuBar() { onClick={() => setShowHelpDropdown(!showHelpDropdown)} title="Help and feedback" > - + + + Help
diff --git a/app/globals.css b/app/globals.css index 4221dd5..f553bdc 100644 --- a/app/globals.css +++ b/app/globals.css @@ -155,8 +155,16 @@ body { } .menu-icon-button .icon { - font-size: 1.5rem; + display: flex; + align-items: center; + justify-content: center; line-height: 1; + color: var(--text-secondary); + transition: color 0.2s ease; +} + +.menu-icon-button .icon svg { + display: block; } .menu-icon-button .label { @@ -164,6 +172,7 @@ body { font-weight: 500; color: var(--text-secondary); line-height: 1; + transition: color 0.2s ease; } .menu-icon-button .badge { @@ -190,6 +199,10 @@ body { color: var(--text-primary); } +.menu-icon-button:hover .icon { + color: var(--accent-blue); +} + .menu-icon-button.not-set { border-color: rgba(245, 158, 11, 0.3); } From 9c3c8dba87cdfb86fc430140b854566b9568e17a Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 10:53:49 -0500 Subject: [PATCH 03/10] Larger SVGs for clarity. --- app/components/Icons.tsx | 38 +++++++++++++++++++ app/components/MenuBar.tsx | 37 ++++++++++--------- app/globals.css | 76 +++++++++++++++++++++++++++++--------- 3 files changed, 115 insertions(+), 36 deletions(-) diff --git a/app/components/Icons.tsx b/app/components/Icons.tsx index bfa2523..1055c52 100644 --- a/app/components/Icons.tsx +++ b/app/components/Icons.tsx @@ -343,3 +343,41 @@ export function FolderIcon({ className = "", size = 20 }: IconProps) { ); } + +export function SunIcon({ className = "", size = 20 }: IconProps) { + return ( + + + + + + + + + + + + ); +} + +export function MoonIcon({ className = "", size = 20 }: IconProps) { + return ( + + + + ); +} diff --git a/app/components/MenuBar.tsx b/app/components/MenuBar.tsx index c9f4220..5db6bb0 100644 --- a/app/components/MenuBar.tsx +++ b/app/components/MenuBar.tsx @@ -7,7 +7,7 @@ import { useCustomization } from "./CustomizationContext"; import CustomizationModal from "./CustomizationModal"; import LLMConfigModal from "./LLMConfigModal"; import { MyDataDropdown, ResultsDropdown, CacheDropdown, HelpDropdown } from "./MenuDropdowns"; -import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon } from "./Icons"; +import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon } from "./Icons"; import { AuthButton, useAuth } from "./AuthProvider"; export default function MenuBar() { @@ -182,7 +182,7 @@ export default function MenuBar() { title="Upload and manage your genetic data" > - + My Data @@ -193,7 +193,7 @@ export default function MenuBar() { title="Load, export, and manage results" > - + Results {savedResults.length > 0 && ( @@ -207,7 +207,7 @@ export default function MenuBar() { title={getCustomizationTooltip()} > - + Personalize @@ -218,7 +218,7 @@ export default function MenuBar() { title="Configure LLM provider and model" > - + LLM @@ -229,7 +229,7 @@ export default function MenuBar() { title="View and manage cached GWAS data" > - + Cache @@ -240,22 +240,23 @@ export default function MenuBar() { title="Help and feedback" > - + Help -
- -
- + +
diff --git a/app/globals.css b/app/globals.css index f553bdc..5c52a4f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -141,17 +141,31 @@ body { .menu-icon-button { background: var(--surface-bg); - border: 1px solid var(--border-color); - border-radius: 8px; - padding: 0.5rem 0.75rem; + border: 2px solid var(--border-color); + border-radius: 12px; + padding: 0.75rem 1rem; cursor: pointer; - transition: all 0.2s ease; + transition: all 0.3s ease; display: flex; flex-direction: column; align-items: center; - gap: 0.25rem; - min-width: 70px; + gap: 0.5rem; + min-width: 85px; position: relative; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + /* Tarot card aesthetic - decorative double border */ + outline: 1px solid transparent; + outline-offset: -4px; +} + +.menu-icon-button::before { + content: ''; + position: absolute; + inset: 3px; + border: 1px solid var(--border-color); + border-radius: 8px; + pointer-events: none; + opacity: 0.3; } .menu-icon-button .icon { @@ -160,39 +174,52 @@ body { justify-content: center; line-height: 1; color: var(--text-secondary); - transition: color 0.2s ease; + transition: all 0.3s ease; + padding: 0.25rem; } .menu-icon-button .icon svg { display: block; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1)); } .menu-icon-button .label { - font-size: 0.75rem; - font-weight: 500; + font-size: 0.7rem; + font-weight: 600; color: var(--text-secondary); line-height: 1; - transition: color 0.2s ease; + transition: all 0.3s ease; + letter-spacing: 0.05em; + text-transform: uppercase; + font-family: 'Inter', sans-serif; } .menu-icon-button .badge { position: absolute; - top: 0.25rem; - right: 0.25rem; + top: -0.25rem; + right: -0.25rem; background: var(--accent-blue); color: white; font-size: 0.65rem; - font-weight: 600; - padding: 0.1rem 0.35rem; - border-radius: 10px; - min-width: 18px; + font-weight: 700; + padding: 0.15rem 0.4rem; + border-radius: 12px; + min-width: 20px; text-align: center; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border: 2px solid var(--primary-bg); } .menu-icon-button:hover { - background: rgba(59, 130, 246, 0.1); + background: rgba(59, 130, 246, 0.08); border-color: var(--accent-blue); - transform: translateY(-2px); + transform: translateY(-3px); + box-shadow: 0 4px 16px rgba(59, 130, 246, 0.2); +} + +.menu-icon-button:hover::before { + border-color: var(--accent-blue); + opacity: 0.5; } .menu-icon-button:hover .label { @@ -201,17 +228,30 @@ body { .menu-icon-button:hover .icon { color: var(--accent-blue); + transform: scale(1.05); } .menu-icon-button.not-set { + border-color: rgba(245, 158, 11, 0.4); +} + +.menu-icon-button.not-set::before { border-color: rgba(245, 158, 11, 0.3); } .menu-icon-button.locked { + border-color: rgba(239, 68, 68, 0.4); +} + +.menu-icon-button.locked::before { border-color: rgba(239, 68, 68, 0.3); } .menu-icon-button.unlocked { + border-color: rgba(16, 185, 129, 0.4); +} + +.menu-icon-button.unlocked::before { border-color: rgba(16, 185, 129, 0.3); } From 7d540b0557d96a89f187470f805c87a239556365 Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 11:06:48 -0500 Subject: [PATCH 04/10] Changed plan information into a menu bar icon. --- app/components/Icons.tsx | 37 +++++++++++ app/components/MenuBar.tsx | 127 ++++++++++++++++++++----------------- app/globals.css | 16 +++++ 3 files changed, 122 insertions(+), 58 deletions(-) diff --git a/app/components/Icons.tsx b/app/components/Icons.tsx index 1055c52..05823b4 100644 --- a/app/components/Icons.tsx +++ b/app/components/Icons.tsx @@ -381,3 +381,40 @@ export function MoonIcon({ className = "", size = 20 }: IconProps) { ); } + +export function CrownIcon({ className = "", size = 20 }: IconProps) { + return ( + + {/* Ornate crown */} + + + {/* Crown points */} + + + {/* Decorative gems on crown points */} + + + + + {/* Inner gem details */} + + + + + {/* Ornate band decoration */} + + + {/* Side decorations */} + + + + + ); +} diff --git a/app/components/MenuBar.tsx b/app/components/MenuBar.tsx index 5db6bb0..91190e2 100644 --- a/app/components/MenuBar.tsx +++ b/app/components/MenuBar.tsx @@ -7,7 +7,7 @@ import { useCustomization } from "./CustomizationContext"; import CustomizationModal from "./CustomizationModal"; import LLMConfigModal from "./LLMConfigModal"; import { MyDataDropdown, ResultsDropdown, CacheDropdown, HelpDropdown } from "./MenuDropdowns"; -import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon } from "./Icons"; +import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon, CrownIcon } from "./Icons"; import { AuthButton, useAuth } from "./AuthProvider"; export default function MenuBar() { @@ -260,66 +260,77 @@ export default function MenuBar() {
- {isAuthenticated && hasActiveSubscription && subscriptionData && ( - <> -
-
- - {showSubscriptionMenu && ( -
-
-

Premium Subscription

-

Expires: {subscriptionData.expiresAt ? new Date(subscriptionData.expiresAt).toLocaleDateString() : 'N/A'}

-

Days remaining: {subscriptionData.daysRemaining}

-
- -
- )} -
+ {/* Subscription/Plan Icon Button */} + + + {showSubscriptionMenu && hasActiveSubscription && subscriptionData && ( +
+
+

Premium Subscription

+

Expires: {subscriptionData.expiresAt ? new Date(subscriptionData.expiresAt).toLocaleDateString() : 'N/A'}

+

Days remaining: {subscriptionData.daysRemaining}

-
- + +
)} +
+
diff --git a/app/globals.css b/app/globals.css index 5c52a4f..6a9c18a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -255,6 +255,22 @@ body { border-color: rgba(16, 185, 129, 0.3); } +.menu-icon-button.subscribed { + border-color: rgba(16, 185, 129, 0.4); +} + +.menu-icon-button.subscribed::before { + border-color: rgba(16, 185, 129, 0.3); +} + +.menu-icon-button.not-subscribed { + border-color: rgba(239, 68, 68, 0.4); +} + +.menu-icon-button.not-subscribed::before { + border-color: rgba(239, 68, 68, 0.3); +} + /* Menu Dropdowns */ .menu-dropdown { position: fixed; From 01b19b9c9d2eba1058c8e791bfb3b6bacccfdd5b Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 11:10:06 -0500 Subject: [PATCH 05/10] Changed log in button into a menu bar icon. --- app/components/Icons.tsx | 31 +++++++++++++++++++++++++++++++ app/components/MenuBar.tsx | 27 ++++++++++++++++++++++++--- app/globals.css | 26 ++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/app/components/Icons.tsx b/app/components/Icons.tsx index 05823b4..0cbc9a1 100644 --- a/app/components/Icons.tsx +++ b/app/components/Icons.tsx @@ -418,3 +418,34 @@ export function CrownIcon({ className = "", size = 20 }: IconProps) { ); } + +export function UserIcon({ className = "", size = 20 }: IconProps) { + return ( + + {/* Ornate decorative outer circle */} + + + + {/* User head with ornate details */} + + + + {/* Ornate body/shoulders */} + + + + {/* Decorative corner accents */} + + + + + + ); +} diff --git a/app/components/MenuBar.tsx b/app/components/MenuBar.tsx index 91190e2..5ad9750 100644 --- a/app/components/MenuBar.tsx +++ b/app/components/MenuBar.tsx @@ -7,7 +7,7 @@ import { useCustomization } from "./CustomizationContext"; import CustomizationModal from "./CustomizationModal"; import LLMConfigModal from "./LLMConfigModal"; import { MyDataDropdown, ResultsDropdown, CacheDropdown, HelpDropdown } from "./MenuDropdowns"; -import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon, CrownIcon } from "./Icons"; +import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon, CrownIcon, UserIcon } from "./Icons"; import { AuthButton, useAuth } from "./AuthProvider"; export default function MenuBar() { @@ -331,8 +331,29 @@ export default function MenuBar() {
-
- + {/* Auth/Account Icon Button with integrated DynamicWidget */} +
+
+ + + + Account +
+ {/* DynamicWidget overlaid on top */} +
+ +
diff --git a/app/globals.css b/app/globals.css index 6a9c18a..3f8ef49 100644 --- a/app/globals.css +++ b/app/globals.css @@ -271,6 +271,32 @@ body { border-color: rgba(239, 68, 68, 0.3); } +.menu-icon-button.authenticated { + border-color: rgba(59, 130, 246, 0.4); +} + +.menu-icon-button.authenticated::before { + border-color: rgba(59, 130, 246, 0.3); +} + +.menu-icon-button.not-authenticated { + border-color: rgba(156, 163, 175, 0.4); +} + +.menu-icon-button.not-authenticated::before { + border-color: rgba(156, 163, 175, 0.3); +} + +/* Auth icon wrapper - allow click-through to DynamicWidget */ +.auth-icon-wrapper { + position: relative; + pointer-events: none; +} + +.auth-icon-wrapper > * { + pointer-events: auto; +} + /* Menu Dropdowns */ .menu-dropdown { position: fixed; From e19d0f6a57cd88ed72c9b52b49e198baf420d609 Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 15:18:24 -0500 Subject: [PATCH 06/10] Moved login and subscription info to premium section. --- app/components/Icons.tsx | 97 ++++++++++++++++++++ app/components/LLMChatInline.tsx | 45 ++------- app/components/MenuBar.tsx | 148 ++++++------------------------ app/components/PremiumPaywall.tsx | 80 ++-------------- app/globals.css | 112 +++++++++++++++++++--- app/page.tsx | 86 ++++++++++++----- 6 files changed, 307 insertions(+), 261 deletions(-) diff --git a/app/components/Icons.tsx b/app/components/Icons.tsx index 0cbc9a1..ca67f8b 100644 --- a/app/components/Icons.tsx +++ b/app/components/Icons.tsx @@ -419,6 +419,103 @@ export function CrownIcon({ className = "", size = 20 }: IconProps) { ); } +// Premium Feature Icons - Ornate and intricate +export function RunAllIcon({ className = "", size = 24 }: IconProps) { + return ( + + {/* Ornate outer circle */} + + + + {/* Play triangle with ornate details */} + + + + {/* Decorative accents */} + + + + {/* Corner flourishes */} + + + + + + {/* Decorative dots */} + + + + + + ); +} + +export function LLMChatIcon({ className = "", size = 24 }: IconProps) { + return ( + + {/* Ornate outer frame */} + + + + {/* Chat bubble with ornate details */} + + + + {/* Ornate chat dots */} + + + + + + + + {/* Decorative corner elements */} + + + + + + ); +} + +export function OverviewReportIcon({ className = "", size = 24 }: IconProps) { + return ( + + {/* Ornate document frame */} + + + + {/* Folded corner with ornate detail */} + + + + {/* Document lines with ornate flourishes */} + + + + + + + + + + {/* Decorative corner accents */} + + + + + {/* Border embellishments */} + + + + + ); +} + export function UserIcon({ className = "", size = 20 }: IconProps) { return ( {showConsentModal && ( @@ -608,36 +612,6 @@ Remember: You have plenty of space. Use ALL of it to provide a complete, thoroug /> )}
- {isBlocked && ( -
-
-

🔒 Premium Feature

-

- LLM Chat requires an active premium subscription. -

-

- Subscribe for $4.99/month to unlock LLM-powered analysis of your genetic results. -

-
-
- )}

🤖 LLM Chat: Your Genetic Results

@@ -813,9 +787,10 @@ Remember: You have plenty of space. Use ALL of it to provide a complete, thoroug

diff --git a/app/components/MenuBar.tsx b/app/components/MenuBar.tsx index 5ad9750..71c6076 100644 --- a/app/components/MenuBar.tsx +++ b/app/components/MenuBar.tsx @@ -7,14 +7,13 @@ import { useCustomization } from "./CustomizationContext"; import CustomizationModal from "./CustomizationModal"; import LLMConfigModal from "./LLMConfigModal"; import { MyDataDropdown, ResultsDropdown, CacheDropdown, HelpDropdown } from "./MenuDropdowns"; -import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon, CrownIcon, UserIcon } from "./Icons"; -import { AuthButton, useAuth } from "./AuthProvider"; +import { DNAIcon, FolderIcon, MicroscopeIcon, SparklesIcon, CacheIcon, HelpCircleIcon, SunIcon, MoonIcon } from "./Icons"; +import { getLLMConfig, getProviderDisplayName } from "@/lib/llm-config"; export default function MenuBar() { const { isUploaded, genotypeData, fileHash } = useGenotype(); const { savedResults, saveToFile, loadFromFile, clearResults } = useResults(); const { status: customizationStatus } = useCustomization(); - const { isAuthenticated, hasActiveSubscription, subscriptionData, user } = useAuth(); const [isLoadingFile, setIsLoadingFile] = useState(false); const [showCustomizationModal, setShowCustomizationModal] = useState(false); const [showLLMConfigModal, setShowLLMConfigModal] = useState(false); @@ -24,22 +23,7 @@ export default function MenuBar() { const [showHelpDropdown, setShowHelpDropdown] = useState(false); const [theme, setTheme] = useState<"light" | "dark">("dark"); const [cacheInfo, setCacheInfo] = useState<{ studies: number; sizeMB: number } | null>(null); - const [showSubscriptionMenu, setShowSubscriptionMenu] = useState(false); - - useEffect(() => { - // Close subscription menu when clicking outside - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as HTMLElement; - if (!target.closest('.subscription-indicator')) { - setShowSubscriptionMenu(false); - } - }; - - if (showSubscriptionMenu) { - document.addEventListener('click', handleClickOutside); - return () => document.removeEventListener('click', handleClickOutside); - } - }, [showSubscriptionMenu]); + const [llmProvider, setLlmProvider] = useState(''); useEffect(() => { // Detect system preference on mount @@ -51,6 +35,10 @@ export default function MenuBar() { document.documentElement.setAttribute("data-theme", initialTheme); document.documentElement.style.colorScheme = initialTheme; + // Load LLM config + const config = getLLMConfig(); + setLlmProvider(getProviderDisplayName(config.provider)); + // Load cache info const loadCacheInfo = async () => { const { gwasDB } = await import('@/lib/gwas-db'); @@ -128,7 +116,12 @@ export default function MenuBar() { /> setShowLLMConfigModal(false)} + onClose={() => { + setShowLLMConfigModal(false); + // Refresh LLM provider after closing modal + const config = getLLMConfig(); + setLlmProvider(getProviderDisplayName(config.provider)); + }} onSave={() => {}} /> My Data + {isUploaded && genotypeData && ( + {genotypeData.size.toLocaleString()} + )} - +
- -
- - {/* Subscription/Plan Icon Button */} - - - {showSubscriptionMenu && hasActiveSubscription && subscriptionData && ( -
-
-

Premium Subscription

-

Expires: {subscriptionData.expiresAt ? new Date(subscriptionData.expiresAt).toLocaleDateString() : 'N/A'}

-

Days remaining: {subscriptionData.daysRemaining}

-
- -
- )} - -
- - {/* Auth/Account Icon Button with integrated DynamicWidget */} -
-
- - - - Account -
- {/* DynamicWidget overlaid on top */} -
- -
-
diff --git a/app/components/PremiumPaywall.tsx b/app/components/PremiumPaywall.tsx index 82c8ffd..f8989c9 100644 --- a/app/components/PremiumPaywall.tsx +++ b/app/components/PremiumPaywall.tsx @@ -42,6 +42,15 @@ export function PremiumPaywall({ children }: PremiumPaywallProps) { } }, []); + // Listen for payment modal trigger + useEffect(() => { + const handleOpenPaymentModal = () => { + setShowPaymentModal(true); + }; + window.addEventListener('openPaymentModal', handleOpenPaymentModal); + return () => window.removeEventListener('openPaymentModal', handleOpenPaymentModal); + }, []); + const handleRemovePromoCode = () => { localStorage.removeItem('promo_access'); setHasPromoAccess(false); @@ -64,7 +73,7 @@ export function PremiumPaywall({ children }: PremiumPaywallProps) { setTimeout(() => refreshSubscription(), 5000); }; - // Always show content, with subscription banner if needed + // Always show content return ( <> setShowPaymentModal(false)} onSuccess={handleModalSuccess} /> - - {!hasActiveSubscription && !hasPromoAccess && ( -
- - Premium subscription required - Subscribe for $4.99/month to access LLM Chat, Run All Analysis, and more. - - -
- )} - - {hasPromoAccess && ( -
- ✓ Premium access active (promo code: {promoCode}) - -
- )} - {children} ); diff --git a/app/globals.css b/app/globals.css index 3f8ef49..38cc2d7 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3884,6 +3884,87 @@ details[open] .summary-arrow { } /* Premium Section */ +.premium-compact-header { + padding: 1rem 3rem; + max-width: 1400px; + margin: 0 auto; + width: 100%; +} + +.auth-prompt, +.subscription-prompt, +.subscription-active { + padding: 0.75rem 1rem; + background: var(--surface-bg); + border: 2px solid var(--border-color); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + font-size: 0.9rem; +} + +.auth-prompt { + border-color: rgba(59, 130, 246, 0.4); +} + +.subscription-prompt { + border-color: rgba(245, 158, 11, 0.4); + background: rgba(254, 243, 199, 0.1); +} + +.subscription-active { + border-color: rgba(16, 185, 129, 0.4); + background: rgba(209, 250, 229, 0.1); + color: var(--text-primary); +} + +.subscription-message { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.subscription-message strong { + color: var(--text-primary); +} + +.subscription-message span { + color: var(--text-secondary); + font-size: 0.85rem; +} + +.subscribe-button { + padding: 0.5rem 1rem; + background: #f59e0b; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + font-size: 0.875rem; + white-space: nowrap; + transition: background 0.2s; +} + +.subscribe-button:hover { + background: #d97706; +} + +@media (max-width: 900px) { + .premium-compact-header { + padding: 1rem 1.5rem; + } + + .auth-prompt, + .subscription-prompt, + .subscription-active { + flex-direction: column; + align-items: flex-start; + } +} + .premium-section { flex: 1; display: flex; @@ -3904,20 +3985,22 @@ details[open] .summary-arrow { } .premium-features-header .collapse-button { - background: var(--surface-bg); - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 0.5rem 1rem; - font-size: 0.875rem; + background: transparent; + border: none; + border-radius: 4px; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; cursor: pointer; transition: all 0.2s ease; color: var(--text-secondary); - font-weight: 500; + font-weight: 400; + opacity: 0.6; } .premium-features-header .collapse-button:hover { - background: var(--border-color); + opacity: 1; color: var(--text-primary); + background: rgba(128, 128, 128, 0.1); } /* Premium Features Overview - Compact 3-column cards */ @@ -3970,15 +4053,20 @@ details[open] .summary-arrow { } .feature-icon { - width: 2rem; - height: 2rem; - color: var(--accent-blue); - stroke-width: 2; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-primary); + opacity: 0.85; +} + +.feature-icon svg { + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1)); } .feature-overview-card.disabled .feature-icon { color: var(--text-secondary); - opacity: 0.6; + opacity: 0.4; } .feature-overview-card h3 { diff --git a/app/page.tsx b/app/page.tsx index 8f3dc29..27ed616 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -4,6 +4,8 @@ import { useEffect, useMemo, useState, useCallback, useRef } from "react"; import { GenotypeProvider, useGenotype } from "./components/UserDataUpload"; import { ResultsProvider, useResults } from "./components/ResultsContext"; import { CustomizationProvider } from "./components/CustomizationContext"; +import { AuthButton, useAuth } from "./components/AuthProvider"; +import { RunAllIcon, LLMChatIcon, OverviewReportIcon } from "./components/Icons"; import StudyResultReveal from "./components/StudyResultReveal"; import MenuBar from "./components/MenuBar"; import VariantChips from "./components/VariantChips"; @@ -203,6 +205,7 @@ function MainContent() { const { genotypeData, isUploaded, setOnDataLoadedCallback } = useGenotype(); const { setOnResultsLoadedCallback, addResult, addResultsBatch, hasResult } = useResults(); const resultsContext = useResults(); + const { isAuthenticated, hasActiveSubscription, subscriptionData, checkingSubscription } = useAuth(); // Track client-side mounting to prevent hydration errors const [mounted, setMounted] = useState(false); @@ -1086,7 +1089,37 @@ function MainContent() { ) : ( /* Premium Tab - 3 Features with LLM Chat Primary */ - + <> + {/* Account & Subscription Compact Header */} +
+ {!isAuthenticated ? ( +
+ Sign in to access premium features + +
+ ) : !hasActiveSubscription ? ( +
+
+ Premium subscription required + Subscribe for $4.99/month to access Run All Analysis, LLM Chat, and Overview Report. +
+ +
+ ) : subscriptionData ? ( +
+ ✓ Premium Active - {subscriptionData.daysRemaining} days remaining (expires {new Date(subscriptionData.expiresAt || '').toLocaleDateString()}) +
+ ) : null} +
+
{/* Feature Overview Cards - Compact 3-column with collapse button */}
@@ -1103,9 +1136,9 @@ function MainContent() {
{/* Run All Card - First */}
- - - +
+ +

Run All

{!mounted ? 'Loading...' : @@ -1116,10 +1149,18 @@ function MainContent() {

@@ -1127,25 +1168,18 @@ function MainContent() { {/* LLM Chat Card - Primary */}
- - - - - - +
+ +

LLM Chat

Ask a private LLM questions about your genetic data

{/* Overview Report Card */}
- - - - - - - +
+ +

Overview Report (Experimental)

{!mounted ? 'Loading...' : @@ -1155,10 +1189,18 @@ function MainContent() {

@@ -1170,7 +1212,7 @@ function MainContent() { {/* LLM Chat - Full Interface */}
-
+ )}
From 91316ebeb51b7465afc2c11d2dc226bcc44fc7f7 Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 15:36:35 -0500 Subject: [PATCH 07/10] Moved stuff around --- app/components/Icons.tsx | 315 +++++++++++++++++++++++-------- app/components/LLMChatInline.tsx | 5 +- app/globals.css | 44 +++-- app/page.tsx | 67 +++---- 4 files changed, 302 insertions(+), 129 deletions(-) diff --git a/app/components/Icons.tsx b/app/components/Icons.tsx index ca67f8b..dd90d10 100644 --- a/app/components/Icons.tsx +++ b/app/components/Icons.tsx @@ -420,98 +420,265 @@ export function CrownIcon({ className = "", size = 20 }: IconProps) { } // Premium Feature Icons - Ornate and intricate +// Run All: Machine that processes genes into reports export function RunAllIcon({ className = "", size = 24 }: IconProps) { return ( - {/* Ornate outer circle */} - - - - {/* Play triangle with ornate details */} - - - - {/* Decorative accents */} - - - - {/* Corner flourishes */} - - - - - - {/* Decorative dots */} - - - - + {/* Machine body - ornate box */} + + + + {/* Input funnel (left) for genes */} + + + + {/* DNA helix going into machine */} + + + + + + {/* Output chute (right) for reports */} + + + + {/* Report paper coming out */} + + + + + {/* Machine gears and processing */} + + + + + + + + + {/* Gear teeth */} + + + + + + + + + + + {/* Control panel on top */} + + + + + + + {/* Steam/processing indicator */} + + + ); } +// LLM Chat: Witch using a typewriter export function LLMChatIcon({ className = "", size = 24 }: IconProps) { return ( - {/* Ornate outer frame */} - - - - {/* Chat bubble with ornate details */} - - - - {/* Ornate chat dots */} - - - - - - - - {/* Decorative corner elements */} - - - - + {/* Witch hat - pointed with ornate brim */} + + + + + {/* Witch head/face */} + + + + {/* Witch hair flowing */} + + + + {/* Witch body/shoulders */} + + + + + {/* Arms reaching to typewriter */} + + + + {/* Typewriter body - ornate vintage design */} + + + + {/* Typewriter keys */} + + + + + + + + + + + + + + {/* Paper coming out of typewriter */} + + + + + {/* Magic sparkles around */} + + + ); } +// Overview Report: Lab tech looking into microscope export function OverviewReportIcon({ className = "", size = 24 }: IconProps) { return ( - {/* Ornate document frame */} - - - - {/* Folded corner with ornate detail */} - - - - {/* Document lines with ornate flourishes */} - - - - - - - - + {/* Lab tech head */} + + + + {/* Lab coat collar */} + + + + + {/* Body/shoulders hunched over microscope */} + + + + + {/* Arms/hands on microscope */} + + + + {/* Microscope base/platform */} + + + + {/* Microscope body - ornate details */} + + + + {/* Microscope eyepiece where tech is looking */} + + + + {/* Microscope objective lens (bottom) */} + + + + {/* Focus knobs */} + + + + {/* Slide on stage */} + + + {/* Data/results visualization on side (screen/readout) */} + + + + {/* Graph/data lines on screen */} + + + + + + + + {/* DNA helix on screen */} + + + + + {/* Lab environment details */} + + + + {/* Analysis sparkle */} + + + ); +} - {/* Decorative corner accents */} - - - - - {/* Border embellishments */} - - - +// Robot AI icon for LLM Chat header +export function RobotIcon({ className = "", size = 24 }: IconProps) { + return ( + + {/* Antenna with ornate details */} + + + + + {/* Head - ornate box with multiple layers */} + + + + + {/* Eyes - glowing ornate circles */} + + + + + + + + {/* Mouth/display panel */} + + + + + {/* Body - ornate rectangle */} + + + + {/* Chest panel with circuit pattern */} + + + + + + + + {/* Arms - ornate mechanical limbs */} + + + + + + {/* Hands/claws */} + + + + {/* Legs - mechanical supports */} + + + + + + {/* Feet */} + + + + {/* Decorative screws/bolts */} + + + + + + {/* Energy/AI sparkles */} + + ); } diff --git a/app/components/LLMChatInline.tsx b/app/components/LLMChatInline.tsx index 5237f3e..c5ad971 100644 --- a/app/components/LLMChatInline.tsx +++ b/app/components/LLMChatInline.tsx @@ -9,6 +9,7 @@ import { useAuth } from "./AuthProvider"; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { callLLM, getLLMDescription } from "@/lib/llm-client"; +import { RobotIcon } from "./Icons"; type Message = { role: 'user' | 'assistant'; @@ -613,7 +614,9 @@ Remember: You have plenty of space. Use ALL of it to provide a complete, thoroug )}
-

🤖 LLM Chat: Your Genetic Results

+

+ LLM Chat: Your Genetic Results +

{getLLMDescription()} - Your data is processed securely

diff --git a/app/globals.css b/app/globals.css index 38cc2d7..232ce83 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3885,38 +3885,45 @@ details[open] .summary-arrow { /* Premium Section */ .premium-compact-header { - padding: 1rem 3rem; + padding: 0.75rem 3rem 0.5rem 3rem; max-width: 1400px; margin: 0 auto; width: 100%; } -.auth-prompt, -.subscription-prompt, -.subscription-active { - padding: 0.75rem 1rem; +.premium-header-content { + display: flex; + align-items: center; + gap: 1rem; background: var(--surface-bg); border: 2px solid var(--border-color); border-radius: 8px; + padding: 0.75rem 1rem; +} + +.premium-wallet-section { + flex-shrink: 0; +} + +.auth-prompt-inline, +.subscription-prompt-inline, +.subscription-active-inline { + flex: 1; display: flex; align-items: center; - justify-content: space-between; gap: 1rem; font-size: 0.9rem; } -.auth-prompt { - border-color: rgba(59, 130, 246, 0.4); +.auth-prompt-inline { + color: var(--text-secondary); } -.subscription-prompt { - border-color: rgba(245, 158, 11, 0.4); - background: rgba(254, 243, 199, 0.1); +.subscription-prompt-inline { + justify-content: space-between; } -.subscription-active { - border-color: rgba(16, 185, 129, 0.4); - background: rgba(209, 250, 229, 0.1); +.subscription-active-inline { color: var(--text-primary); } @@ -3957,9 +3964,12 @@ details[open] .summary-arrow { padding: 1rem 1.5rem; } - .auth-prompt, - .subscription-prompt, - .subscription-active { + .premium-header-content { + flex-direction: column; + align-items: flex-start; + } + + .subscription-prompt-inline { flex-direction: column; align-items: flex-start; } diff --git a/app/page.tsx b/app/page.tsx index 27ed616..ae4cd02 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1092,32 +1092,36 @@ function MainContent() { <> {/* Account & Subscription Compact Header */}
- {!isAuthenticated ? ( -
- Sign in to access premium features - -
- ) : !hasActiveSubscription ? ( -
-
- Premium subscription required - Subscribe for $4.99/month to access Run All Analysis, LLM Chat, and Overview Report. +
+ {!isAuthenticated ? ( +
+ Sign in to access premium features →
- -
- ) : subscriptionData ? ( -
- ✓ Premium Active - {subscriptionData.daysRemaining} days remaining (expires {new Date(subscriptionData.expiresAt || '').toLocaleDateString()}) + ) : !hasActiveSubscription ? ( +
+
+ Premium subscription required + Subscribe for $4.99/month to access Run All Analysis, LLM Chat, and Overview Report. +
+ +
+ ) : subscriptionData ? ( +
+ ✓ Premium Active +
+ ) : null} +
+
- ) : null} +
@@ -1140,13 +1144,7 @@ function MainContent() {

Run All

-

- {!mounted ? 'Loading...' : - !isUploaded ? 'Upload DNA data first' : - resultsContext.savedResults.length > 0 - ? `${resultsContext.savedResults.length.toLocaleString()} traits analyzed` - : 'Analyze all GWAS studies'} -

+

Run your data through all million+ traits

Overview Report (Experimental)

-

- {!mounted ? 'Loading...' : - resultsContext.savedResults.length < 1000 - ? 'Analyze 1,000+ traits first' - : 'Generate comprehensive LLM report'} -

+

Have an LLM analyze all your traits

diff --git a/app/page.tsx b/app/page.tsx index ae4cd02..06311f3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1148,7 +1148,11 @@ function MainContent() {
From 7a07cbe6d7dc446d64ebddb0e97818b4b545657d Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 15:59:15 -0500 Subject: [PATCH 09/10] Added cancel functionality. --- app/globals.css | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ app/page.tsx | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) diff --git a/app/globals.css b/app/globals.css index 232ce83..9d81dc4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3959,6 +3959,103 @@ details[open] .summary-arrow { background: #d97706; } +.subscription-menu-container { + position: relative; +} + +.subscription-menu-button { + padding: 0.25rem; + background: transparent; + border: none; + cursor: pointer; + color: var(--text-secondary); + opacity: 0.6; + transition: opacity 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.subscription-menu-button:hover { + opacity: 1; +} + +.subscription-menu-backdrop { + position: fixed; + inset: 0; + z-index: 999; +} + +.subscription-menu-dropdown { + position: absolute; + top: calc(100% + 0.5rem); + right: 0; + background: var(--surface-bg); + border: 2px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + min-width: 220px; + z-index: 1000; + overflow: hidden; +} + +.subscription-menu-header { + padding: 0.75rem 1rem; +} + +.subscription-menu-info { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.subscription-menu-info strong { + font-size: 0.875rem; + color: var(--text-primary); +} + +.subscription-menu-info span { + font-size: 0.8rem; + color: var(--text-secondary); +} + +.subscription-menu-info .expires-date { + font-size: 0.75rem; + opacity: 0.8; +} + +.subscription-menu-divider { + height: 1px; + background: var(--border-color); + margin: 0; +} + +.subscription-menu-item { + width: 100%; + padding: 0.75rem 1rem; + background: transparent; + border: none; + text-align: left; + cursor: pointer; + color: var(--text-primary); + font-size: 0.875rem; + transition: background 0.2s; + display: block; +} + +.subscription-menu-item:hover { + background: rgba(128, 128, 128, 0.1); +} + +.subscription-menu-item.cancel { + color: var(--text-secondary); +} + +.subscription-menu-item.cancel:hover { + background: rgba(239, 68, 68, 0.1); + color: #ef4444; +} + @media (max-width: 900px) { .premium-compact-header { padding: 1rem 1.5rem; diff --git a/app/page.tsx b/app/page.tsx index 06311f3..dac6e0f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -256,6 +256,7 @@ function MainContent() { const [runAllProgress, setRunAllProgress] = useState({ current: 0, total: 0 }); const [showRunAllModal, setShowRunAllModal] = useState(false); const [showOverviewReportModal, setShowOverviewReportModal] = useState(false); + const [showSubscriptionMenu, setShowSubscriptionMenu] = useState(false); const [showRunAllDisclaimer, setShowRunAllDisclaimer] = useState(false); const [runAllStatus, setRunAllStatus] = useState<{ phase: 'fetching' | 'downloading' | 'decompressing' | 'parsing' | 'storing' | 'analyzing' | 'embeddings' | 'complete' | 'error'; @@ -1121,6 +1122,76 @@ function MainContent() {
+ {subscriptionData && ( +
+ + {showSubscriptionMenu && ( + <> +
setShowSubscriptionMenu(false)} + /> +
+
+
+ Subscription Details + {subscriptionData.daysRemaining > 0 && ( + {subscriptionData.daysRemaining} days remaining in current cycle + )} + {subscriptionData.expiresAt && ( + Renews {new Date(subscriptionData.expiresAt).toLocaleDateString()} + )} +
+
+
+ +
+ + )} +
+ )}
From 3af511ad06e601ca5992a943b96075aae02fc39c Mon Sep 17 00:00:00 2001 From: Vishakh Date: Fri, 14 Nov 2025 16:29:18 -0500 Subject: [PATCH 10/10] Fixed build errors. --- app/page.tsx | 4 ++-- lib/subscription-indexer.ts | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index dac6e0f..13f774f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -205,7 +205,7 @@ function MainContent() { const { genotypeData, isUploaded, setOnDataLoadedCallback } = useGenotype(); const { setOnResultsLoadedCallback, addResult, addResultsBatch, hasResult } = useResults(); const resultsContext = useResults(); - const { isAuthenticated, hasActiveSubscription, subscriptionData, checkingSubscription } = useAuth(); + const { isAuthenticated, hasActiveSubscription, subscriptionData, checkingSubscription, user } = useAuth(); // Track client-side mounting to prevent hydration errors const [mounted, setMounted] = useState(false); @@ -1194,7 +1194,7 @@ function MainContent() { )}
- + {null}
{/* Feature Overview Cards - Compact 3-column with collapse button */}
diff --git a/lib/subscription-indexer.ts b/lib/subscription-indexer.ts index e3a2e5a..fe4a17f 100644 --- a/lib/subscription-indexer.ts +++ b/lib/subscription-indexer.ts @@ -5,13 +5,16 @@ */ // Fix Next.js + Alchemy SDK compatibility by polyfilling global fetch -// This intercepts all fetch calls (including nested ones in ethers.js) to remove invalid referrer +// This intercepts all fetch calls (including nested ones in ethers.js) to handle invalid referrer +// The Alchemy SDK (via ethers.js) passes referrer: "client" which is valid in browsers +// but throws "Referrer 'client' is not a valid URL" in Node.js const originalFetch = global.fetch; global.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise => { const options = { ...init }; - // Remove referrer property that causes "Referrer 'client' is not a valid URL" error in Node.js - if (options && 'referrer' in options) { - delete options.referrer; + // Set referrer to empty string to avoid Node.js validation errors + // Empty string is valid in Node.js fetch, while "client" is not + if (options) { + options.referrer = ''; } return originalFetch(input, options); };