Homepage Redesign
This commit is contained in:
@@ -29,9 +29,9 @@ const MaintenancePage = lazy(() => import('./features/maintenance/pages/Maintena
|
||||
const VehiclesMobileScreen = lazy(() => import('./features/vehicles/mobile/VehiclesMobileScreen').then(m => ({ default: m.VehiclesMobileScreen })));
|
||||
const VehicleDetailMobile = lazy(() => import('./features/vehicles/mobile/VehicleDetailMobile').then(m => ({ default: m.VehicleDetailMobile })));
|
||||
const DocumentsMobileScreen = lazy(() => import('./features/documents/mobile/DocumentsMobileScreen'));
|
||||
import { HomePage } from './pages/HomePage';
|
||||
import { BottomNavigation, NavigationItem } from './shared-minimal/components/mobile/BottomNavigation';
|
||||
import { GlassCard } from './shared-minimal/components/mobile/GlassCard';
|
||||
import { Button } from './shared-minimal/components/Button';
|
||||
import { RouteSuspense } from './components/SuspenseWrappers';
|
||||
import { Vehicle } from './features/vehicles/types/vehicles.types';
|
||||
import { FuelLogForm } from './features/fuel-logs/components/FuelLogForm';
|
||||
@@ -234,7 +234,7 @@ const AddVehicleScreen: React.FC<AddVehicleScreenProps> = ({ onBack, onAdded })
|
||||
};
|
||||
|
||||
function App() {
|
||||
const { isLoading, isAuthenticated, loginWithRedirect, user } = useAuth0();
|
||||
const { isLoading, isAuthenticated, user } = useAuth0();
|
||||
const [_isPending, startTransition] = useTransition();
|
||||
|
||||
// Initialize data synchronization
|
||||
@@ -368,41 +368,11 @@ function App() {
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
if (mobileMode) {
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<Layout mobileMode={true}>
|
||||
<div className="space-y-6 flex flex-col items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-slate-800 mb-3">Welcome to MotoVaultPro</h1>
|
||||
<p className="text-slate-600 mb-6 text-sm">Your personal vehicle management platform</p>
|
||||
<button
|
||||
onClick={() => loginWithRedirect()}
|
||||
className="h-12 px-8 rounded-2xl text-white font-medium shadow-lg active:scale-[0.99] transition bg-gradient-moto"
|
||||
>
|
||||
Login to Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DebugInfo />
|
||||
</Layout>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ThemeProvider theme={md3Theme}>
|
||||
<CssBaseline />
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-50">
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">MotoVaultPro</h1>
|
||||
<p className="text-gray-600 mb-8">Your personal vehicle management platform</p>
|
||||
<Button onClick={() => loginWithRedirect()}>
|
||||
Login to Continue
|
||||
</Button>
|
||||
</div>
|
||||
<DebugInfo />
|
||||
</div>
|
||||
<HomePage />
|
||||
<DebugInfo />
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,40 +32,40 @@ export const vehiclesApi = {
|
||||
await apiClient.delete(`/vehicles/${id}`);
|
||||
},
|
||||
|
||||
// Dropdown API methods (authenticated)
|
||||
// Dropdown API methods (authenticated) - using unified platform endpoints
|
||||
getYears: async (): Promise<number[]> => {
|
||||
const response = await apiClient.get('/vehicles/dropdown/years');
|
||||
const response = await apiClient.get('/platform/years');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getMakes: async (year: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`);
|
||||
const response = await apiClient.get(`/platform/makes?year=${year}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getModels: async (year: number, makeId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/vehicles/dropdown/models?year=${year}&make_id=${makeId}`);
|
||||
const response = await apiClient.get(`/platform/models?year=${year}&make_id=${makeId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTransmissions: async (year: number, makeId: number, modelId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/vehicles/dropdown/transmissions?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
const response = await apiClient.get(`/platform/transmissions?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getEngines: async (year: number, makeId: number, modelId: number, trimId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/vehicles/dropdown/engines?year=${year}&make_id=${makeId}&model_id=${modelId}&trim_id=${trimId}`);
|
||||
const response = await apiClient.get(`/platform/engines?year=${year}&make_id=${makeId}&model_id=${modelId}&trim_id=${trimId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTrims: async (year: number, makeId: number, modelId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/vehicles/dropdown/trims?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
const response = await apiClient.get(`/platform/trims?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// VIN decode method
|
||||
// VIN decode method - using unified platform endpoint
|
||||
decodeVIN: async (vin: string): Promise<VINDecodeResponse> => {
|
||||
const response = await apiClient.post('/vehicles/decode-vin', { vin });
|
||||
const response = await apiClient.get(`/platform/vehicle?vin=${vin}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -268,11 +268,15 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
VIN or License Plate <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<p className="text-xs text-gray-600 mb-2">
|
||||
Enter VIN to auto-fill vehicle details OR manually select from dropdowns below
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
{...register('vin')}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 text-base"
|
||||
placeholder="Enter 17-character VIN (optional if License Plate provided)"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -280,8 +284,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
loading={decodingVIN}
|
||||
disabled={!watchedVIN || watchedVIN.length !== 17}
|
||||
variant="secondary"
|
||||
className="w-full sm:w-auto min-h-[44px]"
|
||||
>
|
||||
Decode
|
||||
Decode VIN
|
||||
</Button>
|
||||
</div>
|
||||
{decodeSuccess && (
|
||||
@@ -293,14 +298,15 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Vehicle Specification Dropdowns */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Year
|
||||
</label>
|
||||
<select
|
||||
{...register('year', { valueAsNumber: true })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
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]"
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Year</option>
|
||||
{years.map((year) => (
|
||||
@@ -317,8 +323,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<select
|
||||
{...register('make')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
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]"
|
||||
disabled={loadingDropdowns || !watchedYear}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Make</option>
|
||||
{makes.map((make) => (
|
||||
@@ -335,8 +342,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<select
|
||||
{...register('model')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
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]"
|
||||
disabled={loadingDropdowns || !watchedMake || models.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Model</option>
|
||||
{models.map((model) => (
|
||||
@@ -348,7 +356,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{/* Trim (left) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
@@ -356,8 +364,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</label>
|
||||
<select
|
||||
{...register('trimLevel')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
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]"
|
||||
disabled={loadingDropdowns || !watchedModel || trims.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Trim</option>
|
||||
{trims.map((trim) => (
|
||||
@@ -375,8 +384,9 @@ 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"
|
||||
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]"
|
||||
disabled={loadingDropdowns || !watchedModel || !selectedTrim || engines.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Engine</option>
|
||||
{engines.map((engine) => (
|
||||
@@ -394,7 +404,8 @@ 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"
|
||||
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]"
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Transmission</option>
|
||||
<option value="Automatic">Automatic</option>
|
||||
@@ -409,20 +420,22 @@ 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"
|
||||
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]"
|
||||
placeholder="e.g., Family Car"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Color
|
||||
</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"
|
||||
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]"
|
||||
placeholder="e.g., Blue"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -432,8 +445,9 @@ 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"
|
||||
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]"
|
||||
placeholder="e.g., ABC-123 (required if VIN omitted)"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
{errors.licensePlate && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.licensePlate.message}</p>
|
||||
@@ -448,8 +462,10 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
<input
|
||||
{...register('odometerReading', { valueAsNumber: true })}
|
||||
type="number"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
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]"
|
||||
placeholder="e.g., 50000"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
221
frontend/src/pages/HomePage.tsx
Normal file
221
frontend/src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,221 @@
|
||||
import { useState } from 'react';
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { HeroCarousel } from './HomePage/HeroCarousel';
|
||||
import { FeaturesGrid } from './HomePage/FeaturesGrid';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export const HomePage = () => {
|
||||
const { loginWithRedirect } = useAuth0();
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
const handleGetStarted = () => {
|
||||
loginWithRedirect();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Navigation Bar */}
|
||||
<nav className="bg-white shadow-md sticky top-0 z-50">
|
||||
<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>
|
||||
</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">
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="#features"
|
||||
className="text-gray-700 hover:text-primary-500 transition-colors"
|
||||
>
|
||||
Features
|
||||
</a>
|
||||
<a href="#about" className="text-gray-700 hover:text-primary-500 transition-colors">
|
||||
About
|
||||
</a>
|
||||
<button
|
||||
onClick={handleGetStarted}
|
||||
className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<div className="md:hidden">
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="text-gray-700 hover:text-primary-500 focus:outline-none"
|
||||
>
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
{mobileMenuOpen ? (
|
||||
<path d="M6 18L18 6M6 6l12 12" />
|
||||
) : (
|
||||
<path d="M4 6h16M4 12h16M4 18h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{mobileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="md:hidden py-4 space-y-3"
|
||||
>
|
||||
<a
|
||||
href="#home"
|
||||
className="block text-gray-700 hover:text-primary-500 transition-colors py-2"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="#features"
|
||||
className="block text-gray-700 hover:text-primary-500 transition-colors py-2"
|
||||
>
|
||||
Features
|
||||
</a>
|
||||
<a
|
||||
href="#about"
|
||||
className="block text-gray-700 hover:text-primary-500 transition-colors py-2"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<button
|
||||
onClick={handleGetStarted}
|
||||
className="w-full bg-primary-500 hover:bg-primary-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-300"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Hero Carousel */}
|
||||
<section id="home">
|
||||
<HeroCarousel />
|
||||
</section>
|
||||
|
||||
{/* Welcome Section */}
|
||||
<section className="py-16 px-4 md:px-8 bg-white">
|
||||
<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">
|
||||
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">
|
||||
Do not hesitate to reach out for assistance in creating a custom workflow that best fits
|
||||
your needs.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleGetStarted}
|
||||
className="bg-primary-500 hover:bg-primary-700 text-white font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* About Section */}
|
||||
<section id="about" className="py-16 px-4 md:px-8 bg-gray-100">
|
||||
<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">
|
||||
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">
|
||||
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">
|
||||
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="text-center text-white p-8">
|
||||
<svg
|
||||
className="w-32 h-32 mx-auto mb-4"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-xl font-bold">Trusted Platform</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Grid */}
|
||||
<section id="features">
|
||||
<FeaturesGrid />
|
||||
</section>
|
||||
|
||||
{/* Bottom CTA */}
|
||||
<section className="py-16 px-4 md:px-8 bg-primary-500 text-white">
|
||||
<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={handleGetStarted}
|
||||
className="bg-white text-primary-500 hover:bg-gray-100 font-semibold py-3 px-8 rounded-lg transition-colors duration-300"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-gray-900 text-white py-8 px-4 md:px-8">
|
||||
<div className="max-w-7xl mx-auto text-center">
|
||||
<p className="text-gray-400">
|
||||
© {new Date().getFullYear()} MotoVaultPro. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
35
frontend/src/pages/HomePage/FeatureCard.tsx
Normal file
35
frontend/src/pages/HomePage/FeatureCard.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface FeatureCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc: string;
|
||||
imageAlt: string;
|
||||
}
|
||||
|
||||
export const FeatureCard = ({ title, description, imageSrc, imageAlt }: FeatureCardProps) => {
|
||||
return (
|
||||
<motion.div
|
||||
className="group cursor-pointer"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
transition={{ duration: 0.5 }}
|
||||
whileHover={{ y: -5 }}
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg shadow-lg hover:shadow-xl transition-shadow duration-300">
|
||||
<div className="relative h-56 overflow-hidden">
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={imageAlt}
|
||||
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>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
79
frontend/src/pages/HomePage/FeaturesGrid.tsx
Normal file
79
frontend/src/pages/HomePage/FeaturesGrid.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { FeatureCard } from './FeatureCard';
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: 'Vehicle Management',
|
||||
description: 'Track all your vehicles in one centralized location with detailed information and history.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Vehicle Management',
|
||||
},
|
||||
{
|
||||
title: 'Fuel Log Tracking',
|
||||
description: 'Monitor fuel consumption, costs, and efficiency across all your vehicles.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1529369623266-f5264b696110?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Fuel Log Tracking',
|
||||
},
|
||||
{
|
||||
title: 'Maintenance Records',
|
||||
description: 'Keep detailed maintenance logs and never miss scheduled service appointments.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1486262715619-67b85e0b08d3?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Maintenance Records',
|
||||
},
|
||||
{
|
||||
title: 'Document Storage',
|
||||
description: 'Store and organize all vehicle documents, receipts, and important paperwork.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1568605117036-5fe5e7bab0b7?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Document Storage',
|
||||
},
|
||||
{
|
||||
title: 'Service Stations',
|
||||
description: 'Find and track your favorite service stations and fuel locations.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1594940887841-4996b7f80874?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Service Stations',
|
||||
},
|
||||
{
|
||||
title: 'Reports & Analytics',
|
||||
description: 'Generate detailed reports on costs, mileage, and vehicle performance.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Reports & Analytics',
|
||||
},
|
||||
{
|
||||
title: 'Reminders',
|
||||
description: 'Set up automated reminders for maintenance, registration, and insurance renewals.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1434494878577-86c23bcb06b9?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Reminders',
|
||||
},
|
||||
{
|
||||
title: 'Data Export',
|
||||
description: 'Export your data in various formats for reporting and record keeping.',
|
||||
imageSrc: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=600&h=400&fit=crop',
|
||||
imageAlt: 'Data Export',
|
||||
},
|
||||
];
|
||||
|
||||
export const FeaturesGrid = () => {
|
||||
return (
|
||||
<section className="py-16 px-4 md:px-8 bg-gray-50">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{features.map((feature) => (
|
||||
<FeatureCard key={feature.title} {...feature} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<p className="text-lg text-gray-600 mb-6">
|
||||
We are a cloud-based platform accessible anywhere, anytime.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
134
frontend/src/pages/HomePage/HeroCarousel.tsx
Normal file
134
frontend/src/pages/HomePage/HeroCarousel.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { useRef } from 'react';
|
||||
import Slider from 'react-slick';
|
||||
import 'slick-carousel/slick/slick.css';
|
||||
import 'slick-carousel/slick/slick-theme.css';
|
||||
|
||||
interface HeroSlide {
|
||||
id: number;
|
||||
imageSrc: string;
|
||||
imageAlt: string;
|
||||
}
|
||||
|
||||
const heroSlides: HeroSlide[] = [
|
||||
{
|
||||
id: 1,
|
||||
imageSrc: 'https://images.unsplash.com/photo-1492144534655-ae79c964c9d7?w=1920&h=1080&fit=crop',
|
||||
imageAlt: 'Luxury Sports Car',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
imageSrc: 'https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=1920&h=1080&fit=crop',
|
||||
imageAlt: 'Red Sports Car',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
imageSrc: 'https://images.unsplash.com/photo-1552519507-da3b142c6e3d?w=1920&h=1080&fit=crop',
|
||||
imageAlt: 'Green Performance Car',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
imageSrc: 'https://images.unsplash.com/photo-1544636331-e26879cd4d9b?w=1920&h=1080&fit=crop',
|
||||
imageAlt: 'Black Luxury Vehicle',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
imageSrc: 'https://images.unsplash.com/photo-1549317661-bd32c8ce0db2?w=1920&h=1080&fit=crop',
|
||||
imageAlt: 'SUV on Road',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
imageSrc: 'https://images.unsplash.com/photo-1520031441872-265e4ff70366?w=1920&h=1080&fit=crop',
|
||||
imageAlt: 'Luxury Sedan',
|
||||
},
|
||||
];
|
||||
|
||||
export const HeroCarousel = () => {
|
||||
const sliderRef = useRef<Slider>(null);
|
||||
|
||||
const settings = {
|
||||
dots: true,
|
||||
infinite: true,
|
||||
speed: 1000,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
autoplay: true,
|
||||
autoplaySpeed: 5000,
|
||||
fade: true,
|
||||
cssEase: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
pauseOnHover: true,
|
||||
arrows: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative w-full hero-carousel">
|
||||
<Slider ref={sliderRef} {...settings}>
|
||||
{heroSlides.map((slide) => (
|
||||
<div key={slide.id} className="relative">
|
||||
<div className="relative h-[500px] md:h-[600px] lg:h-[700px]">
|
||||
<img
|
||||
src={slide.imageSrc}
|
||||
alt={slide.imageAlt}
|
||||
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>
|
||||
))}
|
||||
</Slider>
|
||||
|
||||
<style>{`
|
||||
.hero-carousel .slick-dots {
|
||||
bottom: 25px;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-dots li button:before {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-dots li.slick-active button:before {
|
||||
color: #7A212A;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-prev,
|
||||
.hero-carousel .slick-next {
|
||||
z-index: 10;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-prev {
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-next {
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-prev:before,
|
||||
.hero-carousel .slick-next:before {
|
||||
font-size: 50px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.hero-carousel .slick-prev:hover:before,
|
||||
.hero-carousel .slick-next:hover:before {
|
||||
opacity: 1;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user