Files
vip-coordinator/frontend/src/components/UserOnboarding.tsx
kyle dc4655cef4 Backup: 2025-06-07 19:48 - Script test
[Restore from backup: vip-coordinator-backup-2025-06-07-19-48-script-test]
2026-01-24 09:33:58 +01:00

257 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { apiCall } from '../utils/api';
import { useToast } from '../contexts/ToastContext';
import { LoadingSpinner } from './LoadingSpinner';
interface OnboardingData {
requestedRole: 'coordinator' | 'driver' | 'viewer';
phone: string;
organization: string;
reason: string;
// Driver-specific fields
vehicleType?: string;
vehicleCapacity?: number;
licensePlate?: string;
}
const UserOnboarding: React.FC = () => {
const navigate = useNavigate();
const { showToast } = useToast();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState<OnboardingData>({
requestedRole: 'viewer',
phone: '',
organization: '',
reason: '',
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const token = localStorage.getItem('authToken');
const response = await apiCall('/api/users/complete-onboarding', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
onboardingData: formData,
phone: formData.phone,
organization: formData.organization,
}),
});
if (response.ok) {
showToast('Onboarding completed! Your account is pending approval.', 'success');
navigate('/pending-approval');
} else {
showToast('Failed to complete onboarding. Please try again.', 'error');
}
} catch (error) {
showToast('An error occurred. Please try again.', 'error');
} finally {
setLoading(false);
}
};
const handleRoleChange = (role: 'coordinator' | 'driver' | 'viewer') => {
setFormData(prev => ({
...prev,
requestedRole: role,
// Clear driver fields if not driver
vehicleType: role === 'driver' ? prev.vehicleType : undefined,
vehicleCapacity: role === 'driver' ? prev.vehicleCapacity : undefined,
licensePlate: role === 'driver' ? prev.licensePlate : undefined,
}));
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center p-4">
<div className="bg-white rounded-2xl shadow-xl max-w-2xl w-full p-8">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-slate-800 mb-2">Welcome to VIP Coordinator</h1>
<p className="text-slate-600">Please complete your profile to request access</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Role Selection */}
<div className="form-section">
<label className="block text-sm font-medium text-slate-700 mb-3">
What type of access do you need?
</label>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<button
type="button"
onClick={() => handleRoleChange('coordinator')}
className={`p-4 rounded-lg border-2 transition-all ${
formData.requestedRole === 'coordinator'
? 'border-amber-500 bg-amber-50'
: 'border-slate-200 hover:border-slate-300'
}`}
>
<div className="text-2xl mb-2">📋</div>
<div className="font-semibold text-slate-800">Coordinator</div>
<div className="text-xs text-slate-600 mt-1">Manage VIPs & schedules</div>
</button>
<button
type="button"
onClick={() => handleRoleChange('driver')}
className={`p-4 rounded-lg border-2 transition-all ${
formData.requestedRole === 'driver'
? 'border-amber-500 bg-amber-50'
: 'border-slate-200 hover:border-slate-300'
}`}
>
<div className="text-2xl mb-2">🚗</div>
<div className="font-semibold text-slate-800">Driver</div>
<div className="text-xs text-slate-600 mt-1">Transport VIPs</div>
</button>
<button
type="button"
onClick={() => handleRoleChange('viewer')}
className={`p-4 rounded-lg border-2 transition-all ${
formData.requestedRole === 'viewer'
? 'border-amber-500 bg-amber-50'
: 'border-slate-200 hover:border-slate-300'
}`}
>
<div className="text-2xl mb-2">👁</div>
<div className="font-semibold text-slate-800">Viewer</div>
<div className="text-xs text-slate-600 mt-1">View-only access</div>
</button>
</div>
</div>
{/* Common Fields */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Phone Number *
</label>
<input
type="tel"
required
value={formData.phone}
onChange={(e) => setFormData(prev => ({ ...prev, phone: e.target.value }))}
className="form-input w-full"
placeholder="+1 (555) 123-4567"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Organization *
</label>
<input
type="text"
required
value={formData.organization}
onChange={(e) => setFormData(prev => ({ ...prev, organization: e.target.value }))}
className="form-input w-full"
placeholder="Your company or department"
/>
</div>
</div>
{/* Driver-specific Fields */}
{formData.requestedRole === 'driver' && (
<div className="space-y-4 p-4 bg-blue-50 rounded-lg border border-blue-200">
<h3 className="font-semibold text-slate-800 mb-3">Driver Information</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Vehicle Type *
</label>
<select
required
value={formData.vehicleType || ''}
onChange={(e) => setFormData(prev => ({ ...prev, vehicleType: e.target.value }))}
className="form-select w-full"
>
<option value="">Select vehicle type</option>
<option value="sedan">Sedan</option>
<option value="suv">SUV</option>
<option value="van">Van</option>
<option value="minibus">Minibus</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Passenger Capacity *
</label>
<input
type="number"
required
min="1"
max="20"
value={formData.vehicleCapacity || ''}
onChange={(e) => setFormData(prev => ({ ...prev, vehicleCapacity: parseInt(e.target.value) }))}
className="form-input w-full"
placeholder="4"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
License Plate *
</label>
<input
type="text"
required
value={formData.licensePlate || ''}
onChange={(e) => setFormData(prev => ({ ...prev, licensePlate: e.target.value }))}
className="form-input w-full"
placeholder="ABC-1234"
/>
</div>
</div>
</div>
)}
{/* Reason for Access */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Why do you need access? *
</label>
<textarea
required
rows={3}
value={formData.reason}
onChange={(e) => setFormData(prev => ({ ...prev, reason: e.target.value }))}
className="form-textarea w-full"
placeholder="Please explain your role and why you need access to the VIP Coordinator system..."
/>
</div>
{/* Submit Button */}
<div className="flex justify-end space-x-3 pt-4">
<button
type="button"
onClick={() => navigate('/')}
className="btn btn-secondary"
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className="btn btn-primary"
>
{loading ? <LoadingSpinner size="sm" /> : 'Submit Request'}
</button>
</div>
</form>
</div>
</div>
);
};
export default UserOnboarding;