'use client'; import React, { useEffect, useState } from 'react'; import Link from 'next/link'; import { onAuthStateChanged, multiFactor, User } from 'firebase/auth'; import { firebaseAuth } from '../../lib/firebase-client'; import { AppCloudData, type CloudDataStatus } from '../../components/app-cloud-data'; import { FirebaseLink } from '../../components/firebase-link'; import { SignOutButton } from '../../components/sign-out-button'; type DeviceSession = { id: string; device_name: string | null; created_at: string; expires_at?: string; current: boolean; }; export default function AccountPage() { const [user, setUser] = useState(null); const [authChecked, setAuthChecked] = useState(false); const [syncTrigger, setSyncTrigger] = useState(0); const [isSyncing, setIsSyncing] = useState(false); const [lastSynced, setLastSynced] = useState(null); const [syncError, setSyncError] = useState(null); // Truthful metrics & sync states: // Distinguish 'idle', 'loading', 'unauthenticated', 'ready', 'permission-denied', 'unavailable', and 'error'. // Never initialize unavailable information in a way that displays as verified zero metrics or 100% synced. const [metricsStatus, setMetricsStatus] = useState('loading'); const [metrics, setMetrics] = useState<{ totalMiles: number; rideCount: number; peakLean: number } | null>(null); // Active Device Sessions State const [sessions, setSessions] = useState([]); const [sessionMessage, setSessionMessage] = useState(''); const [visibleSessionCount, setVisibleSessionCount] = useState(10); const [isLoadingSessions, setIsLoadingSessions] = useState(false); // Billing Management State const [isBillingLoading, setIsBillingLoading] = useState(false); const [billingNotice, setBillingNotice] = useState<{ text: string; type: 'error' | 'info' | 'success' } | null>(null); useEffect(() => { const unsubscribe = onAuthStateChanged(firebaseAuth, (currentUser) => { setUser(currentUser); setAuthChecked(true); if (!currentUser) { setMetricsStatus('unauthenticated'); setMetrics(null); setSyncError(null); } else { setMetricsStatus('loading'); setMetrics(null); } }); return () => unsubscribe(); }, []); // Truthful 2FA state: reflect the user's actual Firebase multi-factor enrollment. // The web app has no MFA enrollment flow, so this is read-only; enrollment is done in // the mobile app. Never claim 2FA is enabled unless a factor is actually enrolled. const twoFactorEnrolled = (() => { if (!user) return false; try { return multiFactor(user).enrolledFactors.length > 0; } catch { return false; } })(); async function loadSessions() { setIsLoadingSessions(true); try { const response = await fetch('/api/account/sessions', { cache: 'no-store' }); if (response.ok) { const data: DeviceSession[] = await response.json(); setSessions(Array.isArray(data) ? data : []); } else { setSessions([]); } } catch { setSessions([]); } finally { setIsLoadingSessions(false); } } useEffect(() => { void loadSessions(); }, []); async function handleRevokeSession(id: string) { try { const response = await fetch(`/api/account/sessions/${id}`, { method: 'DELETE' }); if (response.ok) { setSessionMessage('Session revoked.'); await loadSessions(); } else { setSessionMessage('Failed to revoke session.'); } } catch { setSessionMessage('Network error revoking session.'); } } async function handleRevokeAllSessions() { try { const response = await fetch('/api/account/sessions', { method: 'POST' }); if (response.ok) { setSessionMessage('All sessions revoked.'); await loadSessions(); } else { setSessionMessage('Failed to revoke all sessions.'); } } catch { setSessionMessage('Network error revoking all sessions.'); } } async function handleManagePlan() { if (!user) { setBillingNotice({ type: 'info', text: 'Please sign in to manage your plan and billing invoices.', }); return; } setIsBillingLoading(true); setBillingNotice(null); try { const response = await fetch('/api/billing/portal', { method: 'POST', headers: { 'Content-Type': 'application/json' }, cache: 'no-store', }); if (response.ok) { const data = await response.json(); if (data?.url) { window.location.href = data.url; return; } } if (response.status === 404) { setBillingNotice({ type: 'error', text: 'Web billing portal is currently unavailable (endpoint /api/billing/portal is unconfigured). Subscriptions and invoices are managed through the mobile app.', }); } else if (response.status === 401) { setBillingNotice({ type: 'error', text: 'Authentication required. Please sign in again to access the billing portal.', }); } else { const errorData = await response.json().catch(() => null); setBillingNotice({ type: 'error', text: errorData?.detail || errorData?.message || 'Unable to load billing portal at this time. Please manage subscriptions in the mobile app.', }); } } catch { setBillingNotice({ type: 'error', text: 'Web billing portal is unconfigured or unreachable. Subscriptions and invoices can be managed directly in the mobile app.', }); } finally { setIsBillingLoading(false); } } function handleQuickSync() { if (!user) return; setIsSyncing(true); setSyncError(null); setMetricsStatus('loading'); setSyncTrigger((prev) => prev + 1); } const initials = user?.displayName ? user.displayName.split(' ').map((n) => n[0]).join('').slice(0, 2).toUpperCase() : (user?.email ? user.email.slice(0, 2).toUpperCase() : 'MS'); return (
{/* BEGIN: HeroIdentitySection */}
{/* Top aesthetic accent line (Red Area Fixed) */}
{/* Rider Info */}
{/* Rider Avatar / Helmet Node */}
{initials}
{/* Titles & Metadata */}
Rider Portal // Telemetry Cockpit

{user ? `Welcome back, ${user.displayName || user.email?.split('@')[0] || 'Rider'}.` : 'Rider Account & Cloud Telemetry'}

{user?.email || 'Guest Session (Not Signed In)'} • {user ? 'Pro Telemetry Tier' : 'Guest Tier'} • Status: {user ? 'Active Rider' : 'Signed Out'}
{/* Quick Action Buttons (Green Area Fixed) */}
{user ? ( <> groups Group Rides Hub
) : ( <> Sign In Register )}
{/* END: HeroIdentitySection */} {/* BEGIN: TelemetryKpiGrid */}
{/* Metric 1: Distance */}
Total Distance Logged {metricsStatus === 'loading' ? ( Loading… ) : metricsStatus === 'unauthenticated' ? ( Sign in to view ) : metricsStatus === 'permission-denied' ? ( Permission denied ) : metricsStatus === 'unavailable' || metricsStatus === 'error' ? ( Sync unavailable ) : ( Cloud Aggregate )}
{metricsStatus === 'ready' && metrics !== null ? ( <> {metrics.totalMiles.toLocaleString()} miles ) : metricsStatus === 'loading' ? ( Loading… ) : metricsStatus === 'unauthenticated' ? ( — ) : ( Unavailable )}
{metricsStatus === 'ready' ? ( <> All-time telemetry aggregate ) : metricsStatus === 'loading' ? ( Retrieving telemetry aggregate… ) : metricsStatus === 'unauthenticated' ? ( Sign in to view telemetry aggregate ) : metricsStatus === 'permission-denied' ? ( Access to cloud telemetry restricted ) : ( Telemetry aggregate unavailable )}
{/* Metric 2: Sessions */}
Recorded Sessions {metricsStatus === 'loading' ? ( Checking sync… ) : metricsStatus === 'unauthenticated' ? ( Sign-in required ) : metricsStatus === 'permission-denied' ? ( Permission denied ) : metricsStatus === 'unavailable' || metricsStatus === 'error' ? ( Sync status unavailable ) : metrics !== null && metrics.rideCount > 0 ? ( 100% Cloud Synced ) : ( Cloud Synced )}
{metricsStatus === 'ready' && metrics !== null ? ( <> {metrics.rideCount} rides ) : metricsStatus === 'loading' ? ( Loading… ) : metricsStatus === 'unauthenticated' ? ( — ) : ( Unavailable )}
{metricsStatus === 'ready' && metrics !== null ? ( metrics.rideCount > 0 ? ( Canyons, Circuits & Commutes ) : ( No rides recorded yet ) ) : metricsStatus === 'loading' ? ( Checking cloud telemetry sessions… ) : metricsStatus === 'unauthenticated' ? ( Sign in to load synced rides ) : metricsStatus === 'permission-denied' ? ( Check Firestore permissions ) : ( Could not retrieve sessions from cloud )}
{/* Metric 3: Lean Angle Peak */}
Peak Lean Angle {metricsStatus === 'loading' ? ( Loading… ) : metricsStatus === 'unauthenticated' ? ( Sign in to view ) : metricsStatus === 'permission-denied' ? ( Permission denied ) : metricsStatus === 'unavailable' || metricsStatus === 'error' ? ( Sync unavailable ) : ( Lateral Roll )}
{metricsStatus === 'ready' && metrics !== null ? ( <> {metrics.peakLean > 0 ? `${metrics.peakLean}°` : '--'} lateral roll ) : metricsStatus === 'loading' ? ( Loading… ) : metricsStatus === 'unauthenticated' ? ( — ) : ( Unavailable )}
{metricsStatus === 'ready' && metrics !== null && metrics.peakLean > 0 ? ( 6-Axis IMU Verified ) : metricsStatus === 'loading' ? ( Retrieving lean telemetry… ) : metricsStatus === 'unauthenticated' ? ( Sign in to view lean telemetry ) : metricsStatus === 'permission-denied' ? ( Lean telemetry restricted ) : metricsStatus === 'unavailable' || metricsStatus === 'error' ? ( Lean telemetry unavailable ) : ( No IMU roll data )}
{/* Metric 4: Hardware Link */}
Hardware Telemetry
Not connected
OBD-II / CAN telemetry unavailable
{/* END: TelemetryKpiGrid */} {/* Live App Cloud Data (Vehicle Garage, Telemetry Hardware & Recent Rides from Firestore) */} { setIsSyncing(syncing); }} onStatusChange={(status, _err) => { setMetricsStatus(status); if (status === 'ready') { setSyncError(null); setLastSynced(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })); } else if (status === 'permission-denied' || status === 'unavailable' || status === 'error') { setMetrics(null); setSyncError('Sync failed'); } else if (status === 'unauthenticated') { setMetrics(null); setSyncError(null); } else if (status === 'loading') { setMetrics(null); } }} onMetricsLoaded={(m) => { setMetrics(m); setMetricsStatus('ready'); }} /> {/* BEGIN: PlanAndClusterSection */}
{/* Left 7 cols: Plan & Hardware Dongle */}
Subscription billing

{user ? 'Pro Telemetry Tier' : 'Guest Tier'} {user ? 'Pro Account' : 'Web billing unavailable'}

{user ? 'Active telemetry tier' : 'No active plan data'}
{/* Inclusions Pills */}
Entitlements & Features
Phone-based ride recording OBD-II / ECU telemetry unavailable Cloud sync status shown below Emergency dispatch is not provided
{/* Linked Hardware Details */}
Motorcycle hardware Not available
Device status No device linked
groups Group Rides Hub shield Founder Portal
{billingNotice && (
{billingNotice.text}
)}
{/* Right 5 cols: Connected Apps & Cluster Sync */}
App Connections & Sync {user ? 'Account linked' : 'Not signed in'}
{/* Sync List */}
{/* Google App Account (real: reflects signed-in Firebase user) */}
G
Google Account {user?.email || 'Not connected'}
{user ? 'Connected' : 'Not Connected'}
{/* Authenticated devices (real: from /api/account/sessions) */}
Authenticated Devices {!user ? 'Sign in to view linked devices' : isLoadingSessions ? 'Checking sessions…' : 'Manage in Active Device Sessions below'}
0 ? 'text-brand-emerald bg-emerald-500/10 border-emerald-500/20' : 'text-slate-400 bg-slate-800 border-slate-700'}`}> {!user ? '—' : isLoadingSessions ? '…' : `${sessions.length} active`}
{/* Helmet comms / accessories — Bluetooth pairing is not observable from a browser */}
Helmet Comms & Accessories Bluetooth pairing is managed in the mobile app
Unavailable on web
Apple Watch Companion: Not Connected (Unavailable on Web) Manage Devices
{/* END: PlanAndClusterSection */} {/* BEGIN: SecurityAndSessionsGrid */}
{/* Sessions (7 cols) */}

Active Device Sessions

Authenticated tokens accessing MotorSignal account telemetry

{sessions.length} {sessions.length === 1 ? 'Session' : 'Sessions'}

Sessions

{sessions.length > 0 && ( (Showing {Math.min(visibleSessionCount, sessions.length)} of {sessions.length}) )}
{sessions.length > 0 && ( )}
{sessions.length > 0 ? (
{sessions.slice(0, visibleSessionCount).map((session) => (
{session.device_name ?? 'Unnamed device'} {session.current && '(this device)'}

Created {new Date(session.created_at).toLocaleString()}

{!session.current && ( )}
))} {visibleSessionCount < sessions.length && (
)}
) : (

{isLoadingSessions ? 'Loading sessions...' : 'No active sessions.'}

)} {sessionMessage && (

{sessionMessage}

)}
{/* Security & SOS Preferences (5 cols) */}

Account & Security

Account-link status and security settings

{/* Google App Link */}
Mobile App Account Link {user ? 'CONNECTED' : 'NOT LINKED'}
{/* 2FA State — reflects real Firebase multi-factor enrollment; enrollment is managed in the mobile app */}
Two-Factor Authentication (2FA) {twoFactorEnrolled ? 'Authenticator enrolled' : 'Enroll in the mobile app'}
{twoFactorEnrolled ? ( ENABLED ) : ( NOT ENABLED )}
{/* Leaderboard Privacy — managed in the mobile app; the web app has no privacy toggle backend */}
Telemetry Privacy Mode Leaderboard visibility is managed in the mobile app
In mobile app
Telemetry Data Retention: 100% Retained Review active sessions
{/* END: SecurityAndSessionsGrid */}
); }