import { useState, useEffect } from "react" import { supabase } from "./lib/supabase" import api from "./lib/api" import AuthView from "./components/AuthView" import OnboardingView from "./components/OnboardingView" import ResultsView from "./components/ResultsView" import HistoryView from "./components/HistoryView" import ProfileView from "./components/ProfileView" import HomeView from "./components/HomeView" import HowItWorksView from "./components/HowItWorksView" import MeetStatusBanner from "./components/MeetStatusBanner" import FeedbackModal from "./components/FeedbackModal" const isExtension = typeof chrome !== "undefined" && !!chrome.runtime?.id const isFullPage = new URLSearchParams(window.location.search).get("fullpage") === "1" function openFullPage() { chrome.tabs.create({ url: chrome.runtime.getURL("index.html") + "?fullpage=1" }) } export default function App() { const [session, setSession] = useState(null) const [authLoading, setAuthLoading] = useState(true) const [onboardingDone, setOnboardingDone] = useState( () => !!localStorage.getItem("bm_onboarding_v1") ) const [view, setView] = useState("home") const [results, setResults] = useState(null) const [showAccountMenu, setShowAccountMenu] = useState(false) const [confirmDelete, setConfirmDelete] = useState(false) const [deleting, setDeleting] = useState(false) const [showFeedback, setShowFeedback] = useState(false) // Each key tracks how many times we've navigated TO that view — re-mounting // the wrapper div causes the .view-enter animation to fire every tab switch. const [viewKeys, setViewKeys] = useState({ home: 0, profile: 0, history: 0 }) useEffect(() => { if (view === "home" || view === "profile" || view === "history") { setViewKeys(k => ({ ...k, [view]: k[view] + 1 })) } }, [view]) const handleDeleteAccount = async () => { setDeleting(true) try { await api.delete("/api/account") await supabase.auth.signOut() setDeleting(false) setConfirmDelete(false) } catch (e) { console.error("Delete account failed:", e) setDeleting(false) setConfirmDelete(false) } } useEffect(() => { supabase.auth.getSession().then(({ data: { session } }) => { setSession(session) setAuthLoading(false) // Sync token on initial load so background worker can authenticate Meet uploads if (session && typeof chrome !== 'undefined' && chrome.runtime?.id) { chrome.runtime.sendMessage({ action: 'sync_token', token: session.access_token, userId: session.user.id, }).catch(() => {}) } }) const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { setSession(session) if (!session) { setConfirmDelete(false) setShowAccountMenu(false) setDeleting(false) } if (session) { // Keep background service worker's JWT in sync for Meet recording if (typeof chrome !== 'undefined' && chrome.runtime?.id) { chrome.runtime.sendMessage({ action: 'sync_token', token: session.access_token, userId: session.user.id, }).catch(() => {}) } } }) return () => subscription.unsubscribe() }, []) // Lets background.js force a fresh token on demand (e.g. after a 401 mid-recording) — // getSession() proactively refreshes if the current session is expired, regardless of // whether Supabase's own background auto-refresh timer has fired yet. useEffect(() => { if (typeof chrome === 'undefined' || !chrome.runtime?.id) return function onMessage(msg, _sender, sendResponse) { if (msg.action !== 'request_token_refresh') return supabase.auth.getSession().then(({ data: { session } }) => { if (session) { chrome.runtime.sendMessage({ action: 'sync_token', token: session.access_token, userId: session.user.id, }).catch(() => {}) } sendResponse({ token: session?.access_token || null, userId: session?.user?.id || null }) }) return true } chrome.runtime.onMessage.addListener(onMessage) return () => chrome.runtime.onMessage.removeListener(onMessage) }, []) if (authLoading) return (
Loading…
) if (!session) return if (!onboardingDone) return ( setOnboardingDone(true)} /> ) return ( <> {/* Ambient background blobs */}
{/* Header */}

mirror.

{/* Open as full page — only in side panel */} {isExtension && !isFullPage && ( )}
{confirmDelete ? (
Delete all data?
) : ( <> {showAccountMenu && (
{[ { label: "How it works", onClick: () => { setView("howItWorks"); setShowAccountMenu(false) }, color: "#f0eeff" }, { label: "Send feedback", onClick: () => { setShowFeedback(true); setShowAccountMenu(false) }, color: "#f0eeff" }, { label: "Sign out", onClick: () => { supabase.auth.signOut(); setShowAccountMenu(false) }, color: "#f0eeff" }, { label: "Delete account", onClick: () => { setConfirmDelete(true); setShowAccountMenu(false) }, color: "#f87171" }, ].map(({ label, onClick, color }) => ( ))} e.currentTarget.style.color = "#8b89aa"} onMouseLeave={e => e.currentTarget.style.color = "#4a4d6a"}> Privacy Policy
)} )}
{/* end flex header-right */}
{!isFullPage && setView("history")} />} {/* Nav */} {showFeedback && ( setShowFeedback(false)} /> )} {view === "howItWorks" && ( setView("home")} /> )} {/* Persistent views — kept mounted, but wrapper remounts each tab visit so .view-enter animation fires every time you switch to that tab */}
{view === "results" && results && (
setView("history")} />
)}
{ setResults(r); setView("results") }} />
) }