feat: delete users - not tested

This commit is contained in:
Eric Gullickson
2025-12-22 18:20:25 -06:00
parent 91b4534e76
commit 4897f0a52c
73 changed files with 4923 additions and 62 deletions

View 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>
);
};

View 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>
);
};

View 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>
);
};