feat: delete users - not tested
This commit is contained in:
114
frontend/src/features/onboarding/components/AddVehicleStep.tsx
Normal file
114
frontend/src/features/onboarding/components/AddVehicleStep.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @ai-summary Step 2 of onboarding - Optionally add first vehicle
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from '../../../shared-minimal/components/Button';
|
||||
import { VehicleForm } from '../../vehicles/components/VehicleForm';
|
||||
import { CreateVehicleRequest } from '../../vehicles/types/vehicles.types';
|
||||
|
||||
interface AddVehicleStepProps {
|
||||
onNext: () => void;
|
||||
onAddVehicle: (data: CreateVehicleRequest) => void;
|
||||
onBack: () => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const AddVehicleStep: React.FC<AddVehicleStepProps> = ({
|
||||
onNext,
|
||||
onAddVehicle,
|
||||
onBack,
|
||||
loading,
|
||||
}) => {
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
|
||||
const handleSkip = () => {
|
||||
onNext();
|
||||
};
|
||||
|
||||
const handleAddVehicle = (data: CreateVehicleRequest) => {
|
||||
onAddVehicle(data);
|
||||
};
|
||||
|
||||
if (!showForm) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto w-20 h-20 bg-primary-100 rounded-full flex items-center justify-center mb-4">
|
||||
<svg
|
||||
className="w-10 h-10 text-primary-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold text-slate-800 mb-2">Add Your First Vehicle</h2>
|
||||
<p className="text-slate-600 mb-6">
|
||||
Add a vehicle now or skip this step and add it later from your garage.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={() => setShowForm(true)}
|
||||
className="w-full min-h-[44px]"
|
||||
>
|
||||
Add Vehicle
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleSkip}
|
||||
className="w-full min-h-[44px]"
|
||||
>
|
||||
Skip for Now
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onBack}
|
||||
className="min-h-[44px]"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-slate-800 mb-2">Add Your First Vehicle</h2>
|
||||
<p className="text-sm text-slate-600 mb-4">
|
||||
Fill in the details below. You can always edit this later.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<VehicleForm
|
||||
onSubmit={handleAddVehicle}
|
||||
onCancel={() => setShowForm(false)}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onBack}
|
||||
className="min-h-[44px]"
|
||||
disabled={loading}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
70
frontend/src/features/onboarding/components/CompleteStep.tsx
Normal file
70
frontend/src/features/onboarding/components/CompleteStep.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @ai-summary Step 3 of onboarding - Success screen
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '../../../shared-minimal/components/Button';
|
||||
|
||||
interface CompleteStepProps {
|
||||
onComplete: () => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const CompleteStep: React.FC<CompleteStepProps> = ({ onComplete, loading }) => {
|
||||
return (
|
||||
<div className="space-y-6 text-center py-8">
|
||||
<div className="mx-auto w-24 h-24 bg-green-100 rounded-full flex items-center justify-center animate-bounce">
|
||||
<svg
|
||||
className="w-12 h-12 text-green-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-slate-800 mb-2">You're All Set!</h2>
|
||||
<p className="text-slate-600 max-w-md mx-auto">
|
||||
Welcome to MotoVault Pro. Your account is ready and you can now start tracking your vehicles.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-primary-50 rounded-lg p-6 max-w-md mx-auto">
|
||||
<h3 className="font-semibold text-primary-900 mb-2">What's Next?</h3>
|
||||
<ul className="text-left space-y-2 text-sm text-primary-800">
|
||||
<li className="flex items-start">
|
||||
<svg className="w-5 h-5 text-primary-600 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>Add or manage your vehicles in the garage</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<svg className="w-5 h-5 text-primary-600 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>Track fuel logs and maintenance records</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<svg className="w-5 h-5 text-primary-600 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>Upload important vehicle documents</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="pt-6">
|
||||
<Button onClick={onComplete} loading={loading} className="min-h-[44px] px-8">
|
||||
Go to My Garage
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
141
frontend/src/features/onboarding/components/PreferencesStep.tsx
Normal file
141
frontend/src/features/onboarding/components/PreferencesStep.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @ai-summary Step 1 of onboarding - Set user preferences
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { Button } from '../../../shared-minimal/components/Button';
|
||||
import { OnboardingPreferences } from '../types/onboarding.types';
|
||||
|
||||
const preferencesSchema = z.object({
|
||||
unitSystem: z.enum(['imperial', 'metric']),
|
||||
currencyCode: z.string().length(3),
|
||||
timeZone: z.string().min(1).max(100),
|
||||
});
|
||||
|
||||
interface PreferencesStepProps {
|
||||
onNext: (data: OnboardingPreferences) => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const PreferencesStep: React.FC<PreferencesStepProps> = ({ onNext, loading }) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
watch,
|
||||
setValue,
|
||||
} = useForm<OnboardingPreferences>({
|
||||
resolver: zodResolver(preferencesSchema),
|
||||
defaultValues: {
|
||||
unitSystem: 'imperial',
|
||||
currencyCode: 'USD',
|
||||
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
},
|
||||
});
|
||||
|
||||
const unitSystem = watch('unitSystem');
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onNext)} className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-slate-800 mb-4">Set Your Preferences</h2>
|
||||
<p className="text-slate-600 mb-6">
|
||||
Choose your preferred units and settings to personalize your experience.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Unit System Toggle */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">
|
||||
Unit System
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setValue('unitSystem', 'imperial')}
|
||||
className={`min-h-[44px] py-3 px-4 rounded-lg border-2 font-medium transition-all ${
|
||||
unitSystem === 'imperial'
|
||||
? 'border-primary-600 bg-primary-50 text-primary-700'
|
||||
: 'border-gray-300 bg-white text-gray-700 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm font-semibold">Imperial</div>
|
||||
<div className="text-xs mt-1">Miles & Gallons</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setValue('unitSystem', 'metric')}
|
||||
className={`min-h-[44px] py-3 px-4 rounded-lg border-2 font-medium transition-all ${
|
||||
unitSystem === 'metric'
|
||||
? 'border-primary-600 bg-primary-50 text-primary-700'
|
||||
: 'border-gray-300 bg-white text-gray-700 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm font-semibold">Metric</div>
|
||||
<div className="text-xs mt-1">Kilometers & Liters</div>
|
||||
</button>
|
||||
</div>
|
||||
{errors.unitSystem && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.unitSystem.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Currency Dropdown */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
Currency
|
||||
</label>
|
||||
<select
|
||||
{...register('currencyCode')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="USD">USD - US Dollar</option>
|
||||
<option value="EUR">EUR - Euro</option>
|
||||
<option value="GBP">GBP - British Pound</option>
|
||||
<option value="CAD">CAD - Canadian Dollar</option>
|
||||
<option value="AUD">AUD - Australian Dollar</option>
|
||||
</select>
|
||||
{errors.currencyCode && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.currencyCode.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Timezone Dropdown */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
Time Zone
|
||||
</label>
|
||||
<select
|
||||
{...register('timeZone')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="America/New_York">Eastern Time (ET)</option>
|
||||
<option value="America/Chicago">Central Time (CT)</option>
|
||||
<option value="America/Denver">Mountain Time (MT)</option>
|
||||
<option value="America/Los_Angeles">Pacific Time (PT)</option>
|
||||
<option value="America/Phoenix">Arizona Time (MST)</option>
|
||||
<option value="America/Anchorage">Alaska Time (AKT)</option>
|
||||
<option value="Pacific/Honolulu">Hawaii Time (HST)</option>
|
||||
<option value="Europe/London">London (GMT/BST)</option>
|
||||
<option value="Europe/Paris">Paris (CET/CEST)</option>
|
||||
<option value="Asia/Tokyo">Tokyo (JST)</option>
|
||||
<option value="Australia/Sydney">Sydney (AEST/AEDT)</option>
|
||||
</select>
|
||||
{errors.timeZone && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.timeZone.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button type="submit" loading={loading} className="min-h-[44px]">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user