feat: dark / light theme almost complete
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 180 KiB |
BIN
frontend/public/images/logos/motovaultpro-logo-title.png
Normal file
BIN
frontend/public/images/logos/motovaultpro-logo-title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
BIN
frontend/public/images/logos/motovaultpro-title-slogan.png
Normal file
BIN
frontend/public/images/logos/motovaultpro-title-slogan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.0 MiB |
@@ -8,9 +8,7 @@ import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { useIsAuthInitialized } from './core/auth/auth-gate';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { md3Theme } from './shared-minimal/theme/md3Theme';
|
||||
import { ThemeProvider } from './shared-minimal/theme/ThemeContext';
|
||||
import { Layout } from './components/Layout';
|
||||
import { UnitsProvider } from './core/units/UnitsContext';
|
||||
|
||||
@@ -451,8 +449,7 @@ function App() {
|
||||
if (isLoading) {
|
||||
if (mobileMode) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<Layout mobileMode={true}>
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-slate-500">Loading...</div>
|
||||
@@ -462,8 +459,7 @@ function App() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-lg">Loading...</div>
|
||||
</div>
|
||||
@@ -474,8 +470,7 @@ function App() {
|
||||
// Callback route requires authentication - handled by CallbackPage component
|
||||
if (isCallbackRoute && isAuthenticated) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<React.Suspense fallback={
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-lg">Processing login...</div>
|
||||
@@ -490,8 +485,7 @@ function App() {
|
||||
|
||||
if (shouldShowHomePage) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<HomePage />
|
||||
<DebugInfo />
|
||||
</ThemeProvider>
|
||||
@@ -501,8 +495,7 @@ function App() {
|
||||
// Signup route is public - no authentication required
|
||||
if (isSignupRoute) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<React.Suspense fallback={
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-lg">Loading...</div>
|
||||
@@ -519,8 +512,7 @@ function App() {
|
||||
// (Auth0 blocks unverified users from logging in)
|
||||
if (isVerifyEmailRoute) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<React.Suspense fallback={
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-lg">Loading...</div>
|
||||
@@ -539,8 +531,7 @@ function App() {
|
||||
|
||||
if (isOnboardingRoute) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<React.Suspense fallback={
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-lg">Loading...</div>
|
||||
@@ -559,8 +550,7 @@ function App() {
|
||||
console.log('[DEBUG App] Auth gate not ready yet, showing loading state');
|
||||
if (mobileMode) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<Layout mobileMode={true}>
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-slate-500">Initializing session...</div>
|
||||
@@ -570,8 +560,7 @@ function App() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-lg">Initializing session...</div>
|
||||
</div>
|
||||
@@ -582,8 +571,7 @@ function App() {
|
||||
// Mobile app rendering
|
||||
if (mobileMode) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<UnitsProvider>
|
||||
<Layout mobileMode={true}>
|
||||
<AnimatePresence mode="popLayout" initial={false}>
|
||||
@@ -881,8 +869,7 @@ function App() {
|
||||
|
||||
// Desktop app rendering (fallback)
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<ThemeProvider>
|
||||
<UnitsProvider>
|
||||
<Layout mobileMode={false}>
|
||||
<RouteSuspense>
|
||||
|
||||
@@ -6,7 +6,6 @@ import React from 'react';
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Container, Paper, Typography, Box, IconButton, Avatar } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import DirectionsCarRoundedIcon from '@mui/icons-material/DirectionsCarRounded';
|
||||
import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRounded';
|
||||
import BuildRoundedIcon from '@mui/icons-material/BuildRounded';
|
||||
@@ -28,7 +27,6 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
const { user, logout } = useAuth0();
|
||||
const { sidebarOpen, toggleSidebar, setSidebarOpen } = useAppStore();
|
||||
const location = useLocation();
|
||||
const theme = useTheme();
|
||||
|
||||
// Ensure desktop has a visible navigation by default (only on mount)
|
||||
React.useEffect(() => {
|
||||
@@ -67,7 +65,11 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
{/* App header */}
|
||||
<div className="px-5 pt-5 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-lg font-semibold tracking-tight">MotoVaultPro</div>
|
||||
<img
|
||||
src="/images/logos/motovaultpro-title-slogan.png"
|
||||
alt="MotoVaultPro"
|
||||
className="h-6 w-auto"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<NotificationBell />
|
||||
<div className="text-xs text-slate-500">v1.0</div>
|
||||
@@ -107,31 +109,31 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={0}
|
||||
square
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
height: 64,
|
||||
px: 3,
|
||||
px: 2,
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
borderRadius: 0
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, color: 'primary.main' }}>
|
||||
MotoVaultPro
|
||||
</Typography>
|
||||
<img
|
||||
src="/images/logos/motovaultpro-title-slogan.png"
|
||||
alt="MotoVaultPro"
|
||||
style={{ height: 24, width: 'auto', maxWidth: 180 }}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={toggleSidebar}
|
||||
size="small"
|
||||
sx={{ color: 'text.secondary' }}
|
||||
sx={{ color: 'text.secondary', flexShrink: 0 }}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mt: 3, px: 2, flex: 1 }}>
|
||||
{navigation.map((item) => {
|
||||
@@ -152,14 +154,14 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
borderRadius: 2,
|
||||
transition: 'all 0.2s',
|
||||
backgroundColor: isActive
|
||||
? theme.palette.primary.main + '12'
|
||||
? 'primary.main'
|
||||
: 'transparent',
|
||||
color: isActive
|
||||
? 'primary.main'
|
||||
? '#FFFFFF'
|
||||
: 'text.primary',
|
||||
'&:hover': {
|
||||
backgroundColor: isActive
|
||||
? theme.palette.primary.main + '18'
|
||||
? 'primary.dark'
|
||||
: 'action.hover',
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -160,7 +160,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Vehicle</label>
|
||||
<select
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
value={vehicleID}
|
||||
onChange={(e) => setVehicleID(e.target.value)}
|
||||
required
|
||||
@@ -175,7 +175,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Document Type</label>
|
||||
<select
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
value={documentType}
|
||||
onChange={(e) => setDocumentType(e.target.value as DocumentType)}
|
||||
>
|
||||
@@ -188,7 +188,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col md:col-span-2">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Title</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="text"
|
||||
value={title}
|
||||
placeholder={
|
||||
@@ -206,7 +206,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Insurance company</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="text"
|
||||
value={insuranceCompany}
|
||||
onChange={(e) => setInsuranceCompany(e.target.value)}
|
||||
@@ -215,7 +215,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Policy number</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="text"
|
||||
value={policyNumber}
|
||||
onChange={(e) => setPolicyNumber(e.target.value)}
|
||||
@@ -262,7 +262,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Bodily Injury (Person)</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="text"
|
||||
placeholder="$25,000"
|
||||
value={bodilyInjuryPerson}
|
||||
@@ -272,7 +272,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Bodily Injury (Incident)</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="text"
|
||||
placeholder="$50,000"
|
||||
value={bodilyInjuryIncident}
|
||||
@@ -283,7 +283,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Property Damage</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="text"
|
||||
placeholder="$25,000"
|
||||
value={propertyDamage}
|
||||
@@ -293,7 +293,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Premium</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
@@ -309,7 +309,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">License Plate</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="text"
|
||||
value={licensePlate}
|
||||
onChange={(e) => setLicensePlate(e.target.value)}
|
||||
@@ -336,7 +336,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col md:col-span-2">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Cost</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 bg-white text-gray-900 border-slate-300 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
@@ -354,7 +354,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
type="checkbox"
|
||||
checked={scanForMaintenance}
|
||||
onChange={(e) => setScanForMaintenance(e.target.checked)}
|
||||
className="w-5 h-5 rounded border-slate-300 text-primary-600 focus:ring-primary-500"
|
||||
className="w-5 h-5 rounded border-slate-300 text-primary-600 focus:ring-primary-500 dark:border-silverstone dark:focus:ring-abudhabi"
|
||||
/>
|
||||
<span className="ml-2 text-sm font-medium text-slate-700">
|
||||
Scan for Maintenance Schedule
|
||||
@@ -367,7 +367,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col md:col-span-2">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Notes</label>
|
||||
<textarea
|
||||
className="min-h-[88px] rounded-lg border border-slate-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="min-h-[88px] rounded-lg border px-3 py-2 bg-white text-gray-900 border-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
value={notes}
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
/>
|
||||
@@ -376,7 +376,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
|
||||
<div className="flex flex-col md:col-span-2">
|
||||
<label className="text-sm font-medium text-slate-700 mb-1">Upload image/PDF</label>
|
||||
<input
|
||||
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="h-11 min-h-[44px] rounded-lg border px-3 py-2 bg-white text-gray-900 border-slate-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-primary-500/10 file:text-primary-600 dark:file:bg-abudhabi/20 dark:file:text-abudhabi"
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,application/pdf"
|
||||
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
||||
|
||||
@@ -416,7 +416,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</p>
|
||||
<input
|
||||
{...register('vin')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 text-base"
|
||||
className="w-full px-3 py-2 border rounded-md text-base bg-white text-gray-900 border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
placeholder="Enter 17-character VIN (optional if License Plate provided)"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
@@ -438,7 +438,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
const year = parseInt(e.target.value);
|
||||
setValue('year', year);
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Year</option>
|
||||
@@ -461,7 +461,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
const make = e.target.value;
|
||||
setValue('make', make);
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
disabled={loadingDropdowns || !watchedYear || makes.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
@@ -493,7 +493,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
const model = e.target.value;
|
||||
setValue('model', model);
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
disabled={loadingDropdowns || !watchedMake || models.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
@@ -528,7 +528,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
const trim = e.target.value;
|
||||
setValue('trimLevel', trim);
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
disabled={loadingDropdowns || !watchedModel || trims.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
@@ -556,7 +556,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<select
|
||||
{...register('engine')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
disabled={loadingDropdowns || !selectedTrim || engines.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
@@ -584,7 +584,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<select
|
||||
{...register('transmission')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
disabled={loadingDropdowns || !selectedTrim || transmissions.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
@@ -612,7 +612,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<input
|
||||
{...register('nickname')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
placeholder="e.g., Family Car"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
@@ -625,7 +625,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<input
|
||||
{...register('color')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
placeholder="e.g., Blue"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
@@ -637,7 +637,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<input
|
||||
{...register('licensePlate')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
placeholder="e.g., ABC-123 (required if VIN omitted)"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
@@ -655,7 +655,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
{...register('odometerReading', { valueAsNumber: true })}
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:placeholder-canna dark:focus:ring-abudhabi dark:focus:border-abudhabi"
|
||||
placeholder="e.g., 50000"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
|
||||
@@ -24,39 +24,45 @@ export const HomePage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<div className="min-h-screen bg-nero text-avus">
|
||||
{/* Navigation Bar */}
|
||||
<nav className="bg-white shadow-md sticky top-0 z-50">
|
||||
<nav className="sticky top-0 z-50 bg-nero/90 backdrop-blur border-b border-white/10">
|
||||
<div className="max-w-7xl mx-auto px-4 md:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
{/* Logo */}
|
||||
<div className="flex-shrink-0">
|
||||
<h1 className="text-2xl font-bold text-primary-500">MotoVaultPro</h1>
|
||||
<a href="#home" className="flex items-center">
|
||||
<img
|
||||
src="/images/logos/motovaultpro-title-slogan.png"
|
||||
alt="MotoVaultPro - Precision Vehicle Management"
|
||||
className="h-8 md:h-10 w-auto"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<div className="hidden md:flex items-center space-x-8">
|
||||
<a href="#home" className="text-gray-700 hover:text-primary-500 transition-colors">
|
||||
<a href="#home" className="text-white/75 hover:text-white transition-colors">
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="#features"
|
||||
className="text-gray-700 hover:text-primary-500 transition-colors"
|
||||
className="text-white/75 hover:text-white transition-colors"
|
||||
>
|
||||
Features
|
||||
</a>
|
||||
<a href="#about" className="text-gray-700 hover:text-primary-500 transition-colors">
|
||||
<a href="#about" className="text-white/75 hover:text-white transition-colors">
|
||||
About
|
||||
</a>
|
||||
<button
|
||||
onClick={handleSignup}
|
||||
className="border-2 border-primary-500 text-primary-500 hover:bg-primary-50 font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
|
||||
className="border border-primary-500/90 text-primary-500 hover:bg-primary-500/10 hover:border-primary-500 font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAuthAction}
|
||||
className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
|
||||
className="bg-primary-500 hover:bg-primary-600 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300 shadow-lg shadow-black/30 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
@@ -66,7 +72,7 @@ export const HomePage = () => {
|
||||
<div className="md:hidden">
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="text-gray-700 hover:text-primary-500 focus:outline-none"
|
||||
className="text-white/80 hover:text-white focus:outline-none"
|
||||
>
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
@@ -93,35 +99,35 @@ export const HomePage = () => {
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="md:hidden py-4 space-y-3"
|
||||
className="md:hidden py-4 space-y-3 bg-nero border-t border-white/10"
|
||||
>
|
||||
<a
|
||||
href="#home"
|
||||
className="block text-gray-700 hover:text-primary-500 transition-colors py-2"
|
||||
className="block text-white/75 hover:text-white transition-colors py-2"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="#features"
|
||||
className="block text-gray-700 hover:text-primary-500 transition-colors py-2"
|
||||
className="block text-white/75 hover:text-white transition-colors py-2"
|
||||
>
|
||||
Features
|
||||
</a>
|
||||
<a
|
||||
href="#about"
|
||||
className="block text-gray-700 hover:text-primary-500 transition-colors py-2"
|
||||
className="block text-white/75 hover:text-white transition-colors py-2"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<button
|
||||
onClick={handleSignup}
|
||||
className="w-full border-2 border-primary-500 text-primary-500 hover:bg-primary-50 font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
|
||||
className="w-full border border-primary-500/90 text-primary-500 hover:bg-primary-500/10 font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAuthAction}
|
||||
className="w-full bg-primary-500 hover:bg-primary-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
|
||||
className="w-full bg-primary-500 hover:bg-primary-600 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
@@ -136,28 +142,31 @@ export const HomePage = () => {
|
||||
</section>
|
||||
|
||||
{/* Welcome Section */}
|
||||
<section className="py-16 px-4 md:px-8 bg-white">
|
||||
<section className="py-16 px-4 md:px-8 bg-[#1D1A18] border-t border-white/5">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<p className="text-primary-500 text-sm font-semibold uppercase tracking-wide mb-4">
|
||||
Welcome
|
||||
</p>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
|
||||
Thank you for your interest in MotoVaultPro!
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 leading-relaxed mb-8">
|
||||
{/* Full Logo */}
|
||||
<div className="mb-8">
|
||||
<img
|
||||
src="/images/logos/motovaultpro-logo-title.png"
|
||||
alt="MotoVaultPro - Precision Vehicle Management"
|
||||
className="w-[280px] sm:w-[380px] md:w-[520px] lg:w-[620px] max-w-[90vw] h-auto mx-auto"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-lg text-titanio leading-relaxed mb-8">
|
||||
We are pleased to provide comprehensive vehicle management solutions including Vehicle
|
||||
Tracking, Fuel Log Management, Maintenance Records, Document Storage, Service Station
|
||||
Locations, and detailed Analytics for all your vehicles. A combination of these features
|
||||
can create a perfect management system for your fleet. Based on your specific needs, our
|
||||
platform will help you determine the best approach to managing your vehicles.
|
||||
</p>
|
||||
<p className="text-lg text-gray-600 leading-relaxed mb-8">
|
||||
<p className="text-lg text-titanio leading-relaxed mb-8">
|
||||
Do not hesitate to reach out for assistance in creating a custom workflow that best fits
|
||||
your needs.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleAuthAction}
|
||||
className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
|
||||
className="bg-primary-500 hover:bg-primary-600 text-white font-semibold py-3 px-8 rounded-lg transition-colors duration-300 shadow-lg shadow-black/30 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
@@ -165,30 +174,30 @@ export const HomePage = () => {
|
||||
</section>
|
||||
|
||||
{/* About Section */}
|
||||
<section id="about" className="py-16 px-4 md:px-8 bg-gray-100">
|
||||
<section id="about" className="py-16 px-4 md:px-8 bg-nero border-t border-white/5">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-primary-500 uppercase tracking-wide mb-4">
|
||||
About Us
|
||||
</h3>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-avus mb-6">
|
||||
Overall, our goal is to meet each individual's needs with quality, passion, and
|
||||
professionalism.
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 leading-relaxed mb-6">
|
||||
<p className="text-lg text-titanio leading-relaxed mb-6">
|
||||
Most importantly, we treat each and every vehicle as if it were our own and strive to
|
||||
achieve perfection in vehicle management. If you are unsure of what you need for your
|
||||
vehicles, we are happy to help talk you through the best options for comprehensive
|
||||
tracking.
|
||||
</p>
|
||||
<p className="text-lg text-gray-600 leading-relaxed">
|
||||
<p className="text-lg text-titanio leading-relaxed">
|
||||
We are proud to use the finest technology and best practices to provide quality and
|
||||
satisfaction for our users.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-64 h-64 bg-primary-500 rounded-lg flex items-center justify-center">
|
||||
<div className="w-64 h-64 bg-primary-500 rounded-lg border border-white/10 flex items-center justify-center shadow-lg shadow-black/30">
|
||||
<div className="text-center text-white p-8">
|
||||
<svg
|
||||
className="w-32 h-32 mx-auto mb-4"
|
||||
@@ -217,14 +226,14 @@ export const HomePage = () => {
|
||||
</section>
|
||||
|
||||
{/* Bottom CTA */}
|
||||
<section className="py-16 px-4 md:px-8 bg-primary-500 text-white">
|
||||
<section className="py-16 px-4 md:px-8 bg-gradient-to-r from-primary-700 via-primary-500 to-primary-700 text-white border-t border-white/10">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h2 className="text-2xl md:text-3xl font-bold mb-6">
|
||||
We are a cloud-based platform accessible anywhere, anytime.
|
||||
</h2>
|
||||
<button
|
||||
onClick={handleAuthAction}
|
||||
className="bg-white text-primary-500 hover:bg-gray-100 font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
|
||||
className="bg-white/95 text-primary-500 hover:bg-white font-semibold py-3 px-8 rounded-lg transition-colors duration-300 shadow-lg shadow-black/30 focus:outline-none focus:ring-2 focus:ring-white/50"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
@@ -232,9 +241,9 @@ export const HomePage = () => {
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-gray-900 text-white py-8 px-4 md:px-8">
|
||||
<footer className="bg-black text-white py-8 px-4 md:px-8 border-t border-white/10">
|
||||
<div className="max-w-7xl mx-auto text-center">
|
||||
<p className="text-gray-400">
|
||||
<p className="text-white/50">
|
||||
© {new Date().getFullYear()} MotoVaultPro. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const FeatureCard = ({ title, description, imageSrc, imageAlt }: FeatureC
|
||||
transition={{ duration: 0.5 }}
|
||||
whileHover={{ y: -5 }}
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg shadow-lg hover:shadow-xl transition-shadow duration-300">
|
||||
<div className="overflow-hidden rounded-lg bg-white/5 border border-white/10 shadow-lg shadow-black/30 hover:border-white/20 hover:shadow-xl hover:shadow-black/40 transition-all duration-300">
|
||||
<div className="relative h-56 overflow-hidden">
|
||||
<img
|
||||
src={imageSrc}
|
||||
@@ -25,9 +25,9 @@ export const FeatureCard = ({ title, description, imageSrc, imageAlt }: FeatureC
|
||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-white p-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">{title}</h3>
|
||||
<p className="text-gray-600 leading-relaxed">{description}</p>
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-bold text-avus mb-2">{title}</h3>
|
||||
<p className="text-titanio leading-relaxed">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -53,13 +53,13 @@ const features = [
|
||||
|
||||
export const FeaturesGrid = () => {
|
||||
return (
|
||||
<section className="py-16 px-4 md:px-8 bg-gray-50">
|
||||
<section className="py-16 px-4 md:px-8 bg-[#1D1A18] border-t border-white/5">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<p className="text-primary-500 text-sm font-semibold uppercase tracking-wide mb-2">
|
||||
Our Features
|
||||
</p>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900">What We Offer</h2>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-avus">What We Offer</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
@@ -69,7 +69,7 @@ export const FeaturesGrid = () => {
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<p className="text-lg text-gray-600 mb-6">
|
||||
<p className="text-lg text-titanio mb-6">
|
||||
We are a cloud-based platform accessible anywhere, anytime.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -71,17 +71,6 @@ export const HeroCarousel = () => {
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/60 via-black/40 to-black/60" />
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-center px-4">
|
||||
<p className="text-white text-sm md:text-base font-semibold uppercase tracking-widest mb-4">
|
||||
Welcome to
|
||||
</p>
|
||||
<h1 className="text-white text-4xl md:text-6xl lg:text-7xl font-bold mb-6 leading-tight">
|
||||
MOTOVAULTPRO
|
||||
</h1>
|
||||
<button className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-3 px-8 rounded-lg transition-colors duration-300">
|
||||
Learn More
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -121,12 +110,12 @@ export const HeroCarousel = () => {
|
||||
.hero-carousel .slick-prev:before,
|
||||
.hero-carousel .slick-next:before {
|
||||
font-size: 50px;
|
||||
opacity: 0.75;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-prev:hover:before,
|
||||
.hero-carousel .slick-next:hover:before {
|
||||
opacity: 1;
|
||||
opacity: 0.9;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useUnits } from '../core/units/UnitsContext';
|
||||
import { useAdminAccess } from '../core/auth/useAdminAccess';
|
||||
import { useProfile, useUpdateProfile } from '../features/settings/hooks/useProfile';
|
||||
import { useTheme } from '../shared-minimal/theme/ThemeContext';
|
||||
import { DeleteAccountDialog } from '../features/settings/components/DeleteAccountDialog';
|
||||
import { PendingDeletionBanner } from '../features/settings/components/PendingDeletionBanner';
|
||||
import {
|
||||
@@ -44,9 +45,9 @@ export const SettingsPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { unitSystem, setUnitSystem } = useUnits();
|
||||
const { isAdmin, loading: adminLoading } = useAdminAccess();
|
||||
const { isDarkMode, setDarkMode } = useTheme();
|
||||
const [notifications, setNotifications] = useState(true);
|
||||
const [emailUpdates, setEmailUpdates] = useState(false);
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
|
||||
// Profile state
|
||||
const { data: profile, isLoading: profileLoading } = useProfile();
|
||||
@@ -314,7 +315,7 @@ export const SettingsPage: React.FC = () => {
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
checked={darkMode}
|
||||
checked={isDarkMode}
|
||||
onChange={(e) => setDarkMode(e.target.checked)}
|
||||
color="primary"
|
||||
/>
|
||||
|
||||
140
frontend/src/shared-minimal/theme/ThemeContext.tsx
Normal file
140
frontend/src/shared-minimal/theme/ThemeContext.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @ai-summary Theme context for managing light/dark mode across the app
|
||||
* Uses same localStorage key as useSettings for consistency
|
||||
* Applies Tailwind dark class to document root
|
||||
*/
|
||||
|
||||
import { createContext, useContext, useEffect, useMemo, useState, useCallback, ReactNode } from 'react';
|
||||
import { ThemeProvider as MuiThemeProvider, Theme } from '@mui/material/styles';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import { md3LightTheme, md3DarkTheme } from './md3Theme';
|
||||
|
||||
// Same storage key as useSettingsPersistence for consistency
|
||||
const SETTINGS_STORAGE_KEY = 'motovaultpro-mobile-settings';
|
||||
|
||||
interface ThemeContextValue {
|
||||
isDarkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
setDarkMode: (value: boolean) => void;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// Read dark mode preference from localStorage (synced with useSettings)
|
||||
const getStoredDarkMode = (): boolean => {
|
||||
try {
|
||||
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
||||
if (stored) {
|
||||
const settings = JSON.parse(stored);
|
||||
return settings.darkMode ?? false;
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Update dark mode in localStorage while preserving other settings
|
||||
const setStoredDarkMode = (darkMode: boolean): void => {
|
||||
try {
|
||||
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
||||
const settings = stored ? JSON.parse(stored) : {};
|
||||
settings.darkMode = darkMode;
|
||||
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
};
|
||||
|
||||
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||
const [isDarkMode, setIsDarkMode] = useState<boolean>(getStoredDarkMode);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
// Initialize on mount
|
||||
useEffect(() => {
|
||||
setIsDarkMode(getStoredDarkMode());
|
||||
setIsInitialized(true);
|
||||
}, []);
|
||||
|
||||
// Apply dark class to document root for Tailwind
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (isDarkMode) {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
}, [isDarkMode]);
|
||||
|
||||
// Listen for storage changes from other tabs/components
|
||||
useEffect(() => {
|
||||
const handleStorageChange = (e: StorageEvent) => {
|
||||
if (e.key === SETTINGS_STORAGE_KEY && e.newValue) {
|
||||
try {
|
||||
const settings = JSON.parse(e.newValue);
|
||||
if (typeof settings.darkMode === 'boolean') {
|
||||
setIsDarkMode(settings.darkMode);
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
return () => window.removeEventListener('storage', handleStorageChange);
|
||||
}, []);
|
||||
|
||||
const setDarkMode = useCallback((value: boolean) => {
|
||||
setIsDarkMode(value);
|
||||
setStoredDarkMode(value);
|
||||
}, []);
|
||||
|
||||
const toggleDarkMode = useCallback(() => {
|
||||
setDarkMode(!isDarkMode);
|
||||
}, [isDarkMode, setDarkMode]);
|
||||
|
||||
const theme = useMemo(() => {
|
||||
return isDarkMode ? md3DarkTheme : md3LightTheme;
|
||||
}, [isDarkMode]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
isDarkMode,
|
||||
toggleDarkMode,
|
||||
setDarkMode,
|
||||
theme,
|
||||
}),
|
||||
[isDarkMode, toggleDarkMode, setDarkMode, theme]
|
||||
);
|
||||
|
||||
// Prevent flash of wrong theme during SSR/initial load
|
||||
if (!isInitialized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={value}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</MuiThemeProvider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTheme = (): ThemeContextValue => {
|
||||
const context = useContext(ThemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
// Re-export for convenience
|
||||
export { md3LightTheme, md3DarkTheme } from './md3Theme';
|
||||
@@ -1,26 +1,20 @@
|
||||
/**
|
||||
* @ai-summary Material Design 3 theme configuration for MotoVaultPro
|
||||
* Supports both light and dark modes
|
||||
*/
|
||||
|
||||
import { createTheme, alpha } from '@mui/material/styles';
|
||||
import { createTheme, alpha, ThemeOptions } from '@mui/material/styles';
|
||||
|
||||
// Brand color from mockup
|
||||
// Brand colors
|
||||
const primaryHex = '#7A212A';
|
||||
|
||||
export const md3Theme = createTheme({
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
main: primaryHex
|
||||
},
|
||||
secondary: {
|
||||
main: alpha(primaryHex, 0.8)
|
||||
},
|
||||
background: {
|
||||
default: '#F8F5F3',
|
||||
paper: '#FFFFFF'
|
||||
},
|
||||
},
|
||||
// Dark theme palette (Ferrari-inspired)
|
||||
const nero = '#231F1C'; // Nero Daytona - page base
|
||||
const avus = '#F2F3F6'; // Bianco Avus - primary text
|
||||
const titanio = '#A8B8C0'; // Grigio Titanio - secondary text
|
||||
|
||||
// Shared theme options
|
||||
const baseThemeOptions: ThemeOptions = {
|
||||
shape: {
|
||||
borderRadius: 16
|
||||
},
|
||||
@@ -37,8 +31,6 @@ export const md3Theme = createTheme({
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 20,
|
||||
boxShadow:
|
||||
'0 1px 2px rgba(16,24,40,.04), 0 4px 16px rgba(16,24,40,.06)',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -71,15 +63,6 @@ export const md3Theme = createTheme({
|
||||
},
|
||||
],
|
||||
},
|
||||
MuiBottomNavigation: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderTop: '1px solid rgba(16,24,40,.08)',
|
||||
background: alpha('#FFFFFF', 0.8),
|
||||
backdropFilter: 'blur(8px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiBottomNavigationAction: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
@@ -93,4 +76,85 @@ export const md3Theme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Light theme
|
||||
export const md3LightTheme = createTheme({
|
||||
...baseThemeOptions,
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: { main: primaryHex },
|
||||
secondary: { main: alpha(primaryHex, 0.8) },
|
||||
background: {
|
||||
default: '#F8F5F3',
|
||||
paper: '#FFFFFF'
|
||||
},
|
||||
text: {
|
||||
primary: '#1a1a1a',
|
||||
secondary: '#666666',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...baseThemeOptions.components,
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 20,
|
||||
boxShadow: '0 1px 2px rgba(16,24,40,.04), 0 4px 16px rgba(16,24,40,.06)',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiBottomNavigation: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderTop: '1px solid rgba(16,24,40,.08)',
|
||||
background: alpha('#FFFFFF', 0.8),
|
||||
backdropFilter: 'blur(8px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Dark theme
|
||||
export const md3DarkTheme = createTheme({
|
||||
...baseThemeOptions,
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: { main: primaryHex },
|
||||
secondary: { main: alpha(primaryHex, 0.8) },
|
||||
background: {
|
||||
default: nero,
|
||||
paper: '#1D1A18'
|
||||
},
|
||||
text: {
|
||||
primary: avus,
|
||||
secondary: titanio,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...baseThemeOptions.components,
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 20,
|
||||
boxShadow: '0 1px 2px rgba(0,0,0,.2), 0 4px 16px rgba(0,0,0,.3)',
|
||||
backgroundColor: 'rgba(255,255,255,0.05)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiBottomNavigation: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderTop: '1px solid rgba(255,255,255,.1)',
|
||||
background: alpha(nero, 0.9),
|
||||
backdropFilter: 'blur(8px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Default export for backward compatibility
|
||||
export const md3Theme = md3LightTheme;
|
||||
|
||||
@@ -4,23 +4,41 @@ export default {
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Primary color scale (Rosso Mugello based)
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
50: '#fdf2f3',
|
||||
100: '#fce4e6',
|
||||
200: '#f9cdd1',
|
||||
300: '#f4a5ad',
|
||||
400: '#ec717e',
|
||||
500: '#7A212A',
|
||||
600: '#7A212A',
|
||||
700: '#9c2a36',
|
||||
600: '#6a1c24',
|
||||
700: '#5a171e',
|
||||
800: '#4a1318',
|
||||
900: '#3a0f12',
|
||||
},
|
||||
gray: {
|
||||
850: '#18202f',
|
||||
},
|
||||
// Dark theme tokens (Ferrari-inspired palette)
|
||||
nero: '#231F1C', // Nero Daytona - page base
|
||||
avus: '#F2F3F6', // Bianco Avus - primary text on dark
|
||||
titanio: '#A8B8C0', // Grigio Titanio - secondary text on dark
|
||||
canna: '#7E8792', // Canna Di Fucile - placeholder text
|
||||
scuro: '#4C4E4D', // Grigio Scuro - input backgrounds
|
||||
silverstone: '#585C64', // Grigio Silverstone - input borders
|
||||
abudhabi: '#2885B5', // Blu Abu Dhabi - focus rings
|
||||
// Legacy aliases
|
||||
'moto-red': '#7A212A',
|
||||
'moto-red-light': '#9c2a36',
|
||||
},
|
||||
backgroundImage: {
|
||||
'gradient-moto': 'linear-gradient(90deg, #7A212A, #9c2a36)',
|
||||
'gradient-cta': 'linear-gradient(90deg, #5a171e 0%, #7A212A 50%, #5a171e 100%)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
BIN
motovaultpro-vehicle-darkpatch.zip
Normal file
BIN
motovaultpro-vehicle-darkpatch.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user