'use client'; import React, { useEffect, useState } from 'react'; import Link from 'next/link'; import { onAuthStateChanged, User } from 'firebase/auth'; import { firebaseAuth } from '../../lib/firebase-client'; import { AppCloudData } 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 [metrics, setMetrics] = useState({ totalMiles: 12480, rideCount: 184, peakLean: 56.4 }); // 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); }); return () => unsubscribe(); }, []); 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() { setIsSyncing(true); 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 Cloud Aggregate
{metrics.totalMiles.toLocaleString()} miles
All-time telemetry aggregate
{/* Metric 2: Sessions */}
Recorded Sessions 100% Cloud Synced
{metrics.rideCount} rides
Canyons, Circuits & Commutes
{/* Metric 3: Lean Angle Peak */}
Peak Lean Angle Lateral Roll
{metrics.peakLean > 0 ? `${metrics.peakLean}°` : '--'} lateral roll
6-Axis IMU Verified
{/* 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); if (!syncing) { setLastSynced(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })); } }} onMetricsLoaded={(m) => setMetrics(m)} /> {/* 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 All Synced
{/* Sync List */}
{/* Google App Account */}
G
Google Account {user?.email || 'Not connected'}
{user ? 'Connected' : 'Not Connected'}
{/* MotorSignal Mobile App */}
iOS Mobile App iPhone 16 Pro • v4.1.0
14m ago
{/* Comms / Cardo */}
Helmet Comms Link Cardo Packtalk Edge
Paired
Apple Watch Companion: Not Connected (Unavailable on Web)
{/* 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 */}
Two-Factor Authentication (2FA) Hardware TOTP Authenticator
ENABLED
{/* Leaderboard Privacy */}
Telemetry Privacy Mode Public Leaderboard (Club Trophies)
Telemetry Data Retention: 100% Retained Security Logs
{/* END: SecurityAndSessionsGrid */}
); }