MotorSignal-App/website/app/account/page.tsx

391 lines
20 KiB
TypeScript

'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 { SessionManager } from '../../components/session-manager';
import { FirebaseLink } from '../../components/firebase-link';
import { SignOutButton } from '../../components/sign-out-button';
export default function AccountPage() {
const [user, setUser] = useState<User | null>(null);
const [authChecked, setAuthChecked] = useState(false);
const [syncTrigger, setSyncTrigger] = useState(0);
const [isSyncing, setIsSyncing] = useState(false);
const [lastSynced, setLastSynced] = useState<string | null>(null);
const [metrics, setMetrics] = useState({ totalMiles: 12480, rideCount: 184, peakLean: 56.4 });
useEffect(() => {
const unsubscribe = onAuthStateChanged(firebaseAuth, (currentUser) => {
setUser(currentUser);
setAuthChecked(true);
});
return () => unsubscribe();
}, []);
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 (
<div className="flagship-page text-slate-200 bg-brand-bg min-h-screen pt-24 pb-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-8">
{/* BEGIN: HeroIdentitySection */}
<section className="glass-card rounded-2xl border border-brand-border p-6 md:p-8 relative overflow-hidden" data-purpose="rider-identity-banner">
{/* Top aesthetic accent line (Red Area Fixed) */}
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-brand-cyan/80 via-blue-500/40 to-transparent"></div>
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 relative z-10">
{/* Rider Info */}
<div className="flex items-start md:items-center space-x-5">
{/* Rider Avatar / Helmet Node */}
<div className="relative shrink-0">
<div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-brand-borderLight to-slate-900 border border-brand-cyan/40 p-1 flex items-center justify-center shadow-xl">
<span className="font-mono text-3xl font-extrabold text-brand-cyan tracking-wider">{initials}</span>
</div>
<span className="absolute -bottom-1 -right-1 w-5 h-5 rounded-full bg-slate-500 border-2 border-brand-bg flex items-center justify-center" title="Motorcycle hardware telemetry is not available">
<svg className="w-3 h-3 text-black fill-current" viewBox="0 0 20 20"><path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"></path></svg>
</span>
</div>
{/* Titles & Metadata */}
<div>
<div className="flex items-center space-x-2 text-xs font-mono text-brand-emerald tracking-normal mb-1">
<span className="inline-block w-2 h-2 rounded-full bg-brand-emerald animate-ping"></span>
<span>Rider Portal // Telemetry Cockpit</span>
</div>
<h1 className="text-3xl sm:text-4xl font-extrabold text-white tracking-tight">
{user ? `Welcome back, ${user.displayName || user.email?.split('@')[0] || 'Rider'}.` : 'Rider Account & Cloud Telemetry'}
</h1>
<div className="flex flex-wrap items-center gap-y-1 gap-x-3 mt-2 text-sm text-slate-400 font-mono">
<span className="text-slate-300 font-medium">{user?.email || 'Guest Session (Not Signed In)'}</span>
<span>•</span>
<span className="inline-flex items-center text-brand-cyan">
<svg className="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg>
{user ? 'Pro Telemetry Tier' : 'Guest Tier'}
</span>
<span>•</span>
<span>Status: <span className="text-slate-200">{user ? 'Active Rider' : 'Signed Out'}</span></span>
</div>
</div>
</div>
{/* Quick Action Buttons (Green Area Fixed) */}
<div className="flex flex-wrap items-center gap-3 pt-2 lg:pt-0">
{user ? (
<>
<Link
href="/group-rides"
className="px-4 py-2 rounded-xl bg-gradient-to-r from-brand-cyan/20 to-blue-600/20 hover:from-brand-cyan/30 hover:to-blue-600/30 text-brand-cyan text-sm font-semibold border border-brand-cyan/40 hover:border-brand-cyan transition flex items-center space-x-2"
>
<span className="material-symbols-outlined text-[18px]">groups</span>
<span>Group Rides Hub</span>
</Link>
<button
type="button"
onClick={handleQuickSync}
disabled={isSyncing}
className="px-4 py-2 rounded-xl bg-brand-cardAlt hover:bg-slate-800 text-slate-200 text-sm font-medium border border-brand-border hover:border-brand-cyan/60 transition flex items-center space-x-2 cursor-pointer disabled:opacity-60"
>
<svg
className={`w-4 h-4 ${isSyncing ? 'animate-spin text-brand-cyan' : 'text-slate-400'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
></path>
</svg>
<span>{isSyncing ? 'Syncing...' : lastSynced ? `Synced (${lastSynced})` : 'Quick Sync'}</span>
</button>
<div className="px-3 py-1.5 rounded-lg bg-brand-cardAlt border border-brand-border text-xs font-mono text-rose-400">
<SignOutButton />
</div>
</>
) : (
<>
<Link href="/login" className="px-4 py-2 rounded-lg bg-gradient-to-r from-brand-cyan to-blue-600 hover:opacity-95 text-slate-950 font-semibold text-sm transition">
Sign In
</Link>
<Link href="/register" className="px-4 py-2 rounded-lg bg-brand-cardAlt hover:bg-slate-800 text-slate-200 text-sm font-medium border border-brand-border">
Register
</Link>
</>
)}
</div>
</div>
</section>
{/* END: HeroIdentitySection */}
{/* BEGIN: TelemetryKpiGrid */}
<section className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4" data-purpose="telemetry-overview-stats">
{/* Metric 1: Distance */}
<div className="glass-card p-5 rounded-xl border border-brand-border glow-border-cyan transition duration-200">
<div className="flex items-center justify-between text-xs font-mono text-slate-400 tracking-normal">
<span>Total Distance Logged</span>
<span className="text-brand-cyan bg-brand-cyan/10 px-1.5 py-0.5 rounded text-[10px]">Cloud Aggregate</span>
</div>
<div className="mt-3 flex items-baseline space-x-2">
<span className="text-3xl font-extrabold text-white font-mono">{metrics.totalMiles.toLocaleString()}</span>
<span className="text-sm font-mono text-slate-400">miles</span>
</div>
<div className="mt-2 text-xs text-slate-400 flex items-center space-x-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-brand-emerald"></span>
<span>All-time telemetry aggregate</span>
</div>
</div>
{/* Metric 2: Sessions */}
<div className="glass-card p-5 rounded-xl border border-brand-border glow-border-cyan transition duration-200">
<div className="flex items-center justify-between text-xs font-mono text-slate-400 tracking-normal">
<span>Recorded Sessions</span>
<span className="text-brand-emerald bg-brand-emerald/10 px-1.5 py-0.5 rounded text-[10px]">100% Cloud Synced</span>
</div>
<div className="mt-3 flex items-baseline space-x-2">
<span className="text-3xl font-extrabold text-white font-mono">{metrics.rideCount}</span>
<span className="text-sm font-mono text-slate-400">rides</span>
</div>
<div className="mt-2 text-xs text-slate-400 flex items-center space-x-1.5">
<span>Canyons, Circuits &amp; Commutes</span>
</div>
</div>
{/* Metric 3: Lean Angle Peak */}
<div className="glass-card p-5 rounded-xl border border-brand-border glow-border-cyan transition duration-200">
<div className="flex items-center justify-between text-xs font-mono text-slate-400 tracking-normal">
<span>Peak Lean Angle</span>
<span className="text-brand-cyan font-mono text-[10px]">Lateral Roll</span>
</div>
<div className="mt-3 flex items-baseline space-x-2">
<span className="text-3xl font-extrabold text-brand-cyan font-mono">{metrics.peakLean > 0 ? `${metrics.peakLean}°` : '--'}</span>
<span className="text-sm font-mono text-slate-400">lateral roll</span>
</div>
<div className="mt-2 text-xs text-slate-400 flex items-center space-x-1.5">
<span className="text-amber-400">6-Axis IMU Verified</span>
</div>
</div>
{/* Metric 4: Hardware Link */}
<div className="glass-card p-5 rounded-xl border border-brand-border glow-border-cyan transition duration-200">
<div className="flex items-center justify-between text-xs font-mono text-slate-400 tracking-normal">
<span>Hardware Telemetry</span>
<span className="w-2.5 h-2.5 rounded-full bg-brand-emerald animate-pulse"></span>
</div>
<div className="mt-3 flex items-baseline space-x-2">
<span className="text-xl font-bold text-white font-mono">Not connected</span>
</div>
<div className="mt-2 text-xs text-brand-emerald font-mono flex items-center space-x-1.5">
<span>OBD-II / CAN telemetry unavailable</span>
</div>
</div>
</section>
{/* END: TelemetryKpiGrid */}
{/* Live App Cloud Data (Vehicle Garage, Telemetry Hardware & Recent Rides from Firestore) */}
<AppCloudData
syncTrigger={syncTrigger}
onSyncStateChange={(syncing) => {
setIsSyncing(syncing);
if (!syncing) {
setLastSynced(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
}
}}
onMetricsLoaded={(m) => setMetrics(m)}
/>
{/* BEGIN: PlanAndClusterSection */}
<section className="grid grid-cols-1 lg:grid-cols-12 gap-6" data-purpose="subscription-and-sync-status">
{/* Left 7 cols: Plan & Hardware Dongle */}
<div className="lg:col-span-7 glass-card rounded-xl border border-brand-border p-6 flex flex-col justify-between" data-purpose="subscription-tier">
<div>
<div className="flex items-center justify-between border-b border-brand-border pb-4 mb-5">
<div>
<span className="text-xs font-mono tracking-normal text-brand-cyan">Subscription billing</span>
<h3 className="text-xl font-bold text-white flex items-center space-x-2 mt-0.5">
<span>Not connected</span>
<span className="text-xs font-mono bg-slate-700 text-slate-300 px-2 py-0.5 rounded border border-slate-600 font-medium">Web billing unavailable</span>
</h3>
</div>
<span className="text-xs font-mono text-slate-400">No active plan data</span>
</div>
{/* Inclusions Pills */}
<div className="mb-5">
<span className="text-xs font-mono text-slate-400 tracking-normal block mb-2">Entitlements &amp; Features</span>
<div className="flex flex-wrap gap-2 text-xs font-mono">
<span className="px-2.5 py-1 rounded-md bg-brand-cardAlt border border-brand-border text-slate-300 flex items-center space-x-1.5">
<svg className="w-3 h-3 text-brand-cyan" fill="currentColor" viewBox="0 0 20 20"><path clipRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" fillRule="evenodd"></path></svg>
<span>Phone-based ride recording</span>
</span>
<span className="px-2.5 py-1 rounded-md bg-brand-cardAlt border border-brand-border text-slate-300 flex items-center space-x-1.5">
<svg className="w-3 h-3 text-brand-cyan" fill="currentColor" viewBox="0 0 20 20"><path clipRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" fillRule="evenodd"></path></svg>
<span>OBD-II / ECU telemetry unavailable</span>
</span>
<span className="px-2.5 py-1 rounded-md bg-brand-cardAlt border border-brand-border text-slate-300 flex items-center space-x-1.5">
<svg className="w-3 h-3 text-brand-cyan" fill="currentColor" viewBox="0 0 20 20"><path clipRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" fillRule="evenodd"></path></svg>
<span>Cloud sync status shown below</span>
</span>
<span className="px-2.5 py-1 rounded-md bg-brand-cardAlt border border-brand-border text-slate-300 flex items-center space-x-1.5">
<svg className="w-3 h-3 text-brand-cyan" fill="currentColor" viewBox="0 0 20 20"><path clipRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" fillRule="evenodd"></path></svg>
<span>Emergency dispatch is not provided</span>
</span>
</div>
</div>
{/* Linked Hardware Details */}
<div className="p-3.5 bg-brand-cardAlt rounded-lg border border-brand-border mb-4 font-mono text-xs flex flex-col sm:flex-row justify-between gap-3">
<div>
<span className="text-slate-400 block text-[11px]">Motorcycle hardware</span>
<span className="font-bold text-white">Not available</span>
</div>
<div className="sm:text-right">
<span className="text-slate-400 block text-[11px]">Device status</span>
<span className="text-slate-300 font-semibold">No device linked</span>
</div>
</div>
</div>
<div className="flex flex-wrap gap-3 pt-2">
<Link
href="/group-rides"
className="px-4 py-2 rounded-lg bg-brand-cyan/15 text-brand-cyan border border-brand-cyan/40 hover:bg-brand-cyan/25 text-xs font-semibold transition inline-flex items-center gap-1.5"
>
<span className="material-symbols-outlined text-[16px]">groups</span>
<span>Group Rides Hub</span>
</Link>
<Link
href="/clubs/manage"
className="px-4 py-2 rounded-lg bg-secondary/15 text-secondary border border-secondary/40 hover:bg-secondary/25 text-xs font-semibold transition inline-flex items-center gap-1.5"
>
<span className="material-symbols-outlined text-[16px]">shield</span>
<span>Founder Portal</span>
</Link>
<button className="px-4 py-2 rounded-lg bg-brand-cardAlt border border-brand-border hover:border-slate-500 text-xs font-semibold text-white transition">
Billing unavailable
</button>
<button className="px-4 py-2 rounded-lg bg-cyan-500/10 text-brand-cyan border border-brand-cyan/40 hover:bg-cyan-500/20 text-xs font-semibold transition">
Hardware diagnostics unavailable
</button>
</div>
</div>
{/* Right 5 cols: Connected Apps & Cluster Sync */}
<div className="lg:col-span-5 glass-card rounded-xl border border-brand-border p-6 flex flex-col justify-between" data-purpose="app-and-cluster-connections">
<div>
<div className="flex items-center justify-between border-b border-brand-border pb-4 mb-4">
<span className="text-xs font-mono tracking-normal text-brand-cyan">App Connections &amp; Sync</span>
<span className="text-xs font-mono text-brand-emerald flex items-center space-x-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-brand-emerald"></span>
<span>All Synced</span>
</span>
</div>
{/* Sync List */}
<div className="space-y-3 font-mono text-xs">
{/* Google App Account */}
<div className="p-3 bg-brand-cardAlt/70 rounded-lg border border-brand-border flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-lg bg-slate-800 flex items-center justify-center font-bold text-slate-300">
G
</div>
<div>
<span className="text-white block font-sans font-medium">Google Account</span>
<span className="text-slate-400 text-[11px]">bponders12@gmail.com</span>
</div>
</div>
<span className="text-brand-emerald text-[11px] bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">Connected</span>
</div>
{/* MotorSignal Mobile App */}
<div className="p-3 bg-brand-cardAlt/70 rounded-lg border border-brand-border flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-lg bg-cyan-500/15 text-brand-cyan flex items-center justify-center">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg>
</div>
<div>
<span className="text-white block font-sans font-medium">iOS Mobile App</span>
<span className="text-slate-400 text-[11px]">iPhone 16 Pro • v4.1.0</span>
</div>
</div>
<span className="text-slate-400 text-[11px]">14m ago</span>
</div>
{/* Comms / Cardo */}
<div className="p-3 bg-brand-cardAlt/70 rounded-lg border border-brand-border flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-lg bg-slate-800 text-slate-300 flex items-center justify-center">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 100-6 3 3 0 000 6z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg>
</div>
<div>
<span className="text-white block font-sans font-medium">Helmet Comms Link</span>
<span className="text-slate-400 text-[11px]">Cardo Packtalk Edge</span>
</div>
</div>
<span className="text-brand-emerald text-[11px] bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">Paired</span>
</div>
</div>
</div>
<div className="pt-3 flex items-center justify-between text-xs font-mono">
<span className="text-slate-400">Apple Watch Companion: <span className="text-slate-300 font-semibold">Active (HRM Sync)</span></span>
<button className="text-brand-cyan hover:underline font-sans font-medium">Manage Devices</button>
</div>
</div>
</section>
{/* END: PlanAndClusterSection */}
{/* BEGIN: SecurityAndSessionsGrid */}
<section className="grid grid-cols-1 lg:grid-cols-12 gap-6" data-purpose="security-and-session-management">
{/* Sessions (7 cols) */}
<div className="lg:col-span-7 glass-card rounded-xl border border-brand-border p-6" data-purpose="active-browser-sessions">
<div className="flex items-center justify-between border-b border-brand-border pb-4 mb-4">
<div>
<h3 className="text-lg font-bold text-white">Active Device Sessions</h3>
<p className="text-xs font-mono text-slate-400 mt-0.5">Authenticated tokens accessing MotorSignal account telemetry</p>
</div>
</div>
<SessionManager />
</div>
{/* Security & SOS Preferences (5 cols) */}
<div className="lg:col-span-5 glass-card rounded-xl border border-brand-border p-6 flex flex-col justify-between" data-purpose="safety-and-security">
<div>
<div className="border-b border-brand-border pb-4 mb-4">
<h3 className="text-lg font-bold text-white">Account &amp; Security</h3>
<p className="text-xs font-mono text-slate-400 mt-0.5">Account-link status and security settings</p>
</div>
<div className="space-y-4 text-xs font-mono">
{/* Google App Link */}
<div className="p-3 rounded-lg bg-brand-cardAlt border border-brand-border">
<div className="flex items-center justify-between mb-2">
<span className="text-white font-bold block">Mobile App Account Link</span>
<span className={`px-2 py-0.5 rounded text-[10px] font-bold ${user ? 'text-brand-emerald bg-emerald-500/10 border border-emerald-500/20' : 'text-slate-400 bg-slate-800'}`}>
{user ? 'CONNECTED' : 'NOT LINKED'}
</span>
</div>
<FirebaseLink linked={!!user} />
</div>
{/* 2FA State */}
<div className="flex items-center justify-between p-3 rounded-lg bg-brand-cardAlt border border-brand-border">
<div>
<span className="text-white font-bold block">Two-Factor Authentication (2FA)</span>
<span className="text-slate-400 text-[11px]">Hardware TOTP Authenticator</span>
</div>
<span className="text-brand-emerald bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20 font-bold">ENABLED</span>
</div>
{/* Leaderboard Privacy */}
<div className="flex items-center justify-between p-3 rounded-lg bg-brand-cardAlt border border-brand-border">
<div>
<span className="text-white font-bold block">Telemetry Privacy Mode</span>
<span className="text-slate-400 text-[11px]">Public Leaderboard (Club Trophies)</span>
</div>
<button className="text-brand-cyan hover:underline">Change</button>
</div>
</div>
</div>
<div className="pt-4 border-t border-brand-border mt-4 flex items-center justify-between text-xs">
<span className="text-slate-400 font-mono">Telemetry Data Retention: 100% Retained</span>
<a className="text-brand-cyan hover:underline font-mono" href="/account">Security Logs</a>
</div>
</div>
</section>
{/* END: SecurityAndSessionsGrid */}
</div>
</div>
);
}