mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
257 lines
10 KiB
TypeScript
257 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { Train, ShieldCheck, CheckCircle, Info, ArrowLeft, ArrowRight } from 'lucide-react';
|
|
import { iamAuthApi } from '@/lib/api/auth';
|
|
|
|
interface FaydaSetupWizardProps {
|
|
// Prefilled OTP when landing from the SMS link (/set-password?verificationCode=...)
|
|
initialOtp?: string;
|
|
}
|
|
|
|
type Outcome = 'success' | 'hasPassword' | null;
|
|
|
|
export default function FaydaSetupWizard({ initialOtp }: FaydaSetupWizardProps) {
|
|
const router = useRouter();
|
|
const [step, setStep] = useState<1 | 2>(initialOtp ? 2 : 1);
|
|
const [phone, setPhone] = useState('');
|
|
const [otp, setOtp] = useState(initialOtp || '');
|
|
const [newPassword, setNewPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
const [outcome, setOutcome] = useState<Outcome>(null);
|
|
|
|
const handleRequestCode = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
setError('');
|
|
try {
|
|
await iamAuthApi.faydaRequestPasswordSetup(phone);
|
|
setStep(2);
|
|
} catch (err: any) {
|
|
setError(err.response?.data?.message || 'Failed to send the code. Please try again.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSetPassword = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError('');
|
|
if (newPassword.length < 6) {
|
|
setError('Password must be at least 6 characters.');
|
|
return;
|
|
}
|
|
if (newPassword !== confirmPassword) {
|
|
setError('Passwords do not match.');
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
const res = await iamAuthApi.faydaVerifyAndLogin({ phoneNumber: phone, otp });
|
|
const data = (res.data as any)?.data ?? res.data;
|
|
if (!data.requiresPassword) {
|
|
setOutcome('hasPassword');
|
|
return;
|
|
}
|
|
await iamAuthApi.setFaydaPassword(
|
|
{ userId: data.iamUserId, newPassword, confirmPassword },
|
|
data.token,
|
|
);
|
|
setOutcome('success');
|
|
setTimeout(() => router.push('/login'), 2500);
|
|
} catch (err: any) {
|
|
const msg = err.response?.data?.message || err.message || '';
|
|
setError(msg || 'Verification failed. The code may be wrong or expired.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-[rgb(20_113_76)] from-10% via-transparent to-[rgb(20_113_76)] to-90% dark:from-gray-900 dark:to-gray-800 flex items-center justify-center py-12 px-4">
|
|
<div className="max-w-md w-full">
|
|
<div className="text-center mb-8">
|
|
<div className="flex justify-center mb-4">
|
|
<div className="w-12 h-12 bg-[rgb(20_113_76)] rounded-lg flex items-center justify-center">
|
|
<Train className="w-6 h-6 text-white" />
|
|
</div>
|
|
</div>
|
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100 flex items-center justify-center gap-2">
|
|
<ShieldCheck className="w-7 h-7 text-[rgb(20_113_76)] dark:text-emerald-400" />
|
|
Fayda account setup
|
|
</h1>
|
|
<p className="text-gray-600 dark:text-gray-400 mt-2">
|
|
Already verified with Fayda? Set a password to access your account online.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="card">
|
|
{outcome === 'success' ? (
|
|
<div className="space-y-4">
|
|
<div className="flex items-start gap-3 bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800 text-emerald-700 dark:text-emerald-300 px-4 py-3 rounded">
|
|
<CheckCircle className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
|
<p className="text-sm">
|
|
Your password has been set and your account is now active. Redirecting to sign in…
|
|
</p>
|
|
</div>
|
|
<Link href="/login" className="btn-primary w-full flex items-center justify-center gap-2">
|
|
Go to sign in
|
|
<ArrowRight className="w-4 h-4" />
|
|
</Link>
|
|
</div>
|
|
) : outcome === 'hasPassword' ? (
|
|
<div className="space-y-4">
|
|
<div className="flex items-start gap-3 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 text-blue-700 dark:text-blue-300 px-4 py-3 rounded">
|
|
<Info className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
|
<p className="text-sm">
|
|
This account already has a password. Sign in with your email or phone number,
|
|
or use forgot password if you can't remember it.
|
|
</p>
|
|
</div>
|
|
<Link href="/login" className="btn-primary w-full flex items-center justify-center">
|
|
Sign in
|
|
</Link>
|
|
<Link
|
|
href="/forgot-password"
|
|
className="flex items-center justify-center text-sm text-gray-600 dark:text-gray-400 hover:text-[rgb(20_113_76)] dark:hover:text-emerald-400 transition-colors"
|
|
>
|
|
Forgot password?
|
|
</Link>
|
|
</div>
|
|
) : step === 1 ? (
|
|
<form onSubmit={handleRequestCode} className="space-y-4">
|
|
{error && (
|
|
<div className="bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-300 px-4 py-3 rounded">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Phone number</label>
|
|
<input
|
|
type="tel"
|
|
value={phone}
|
|
onChange={(e) => { setPhone(e.target.value); setError(''); }}
|
|
className="input-field"
|
|
placeholder="+251912345678"
|
|
autoComplete="tel"
|
|
required
|
|
/>
|
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
The phone number you used during Fayda verification.
|
|
</p>
|
|
</div>
|
|
|
|
<button type="submit" className="btn-primary w-full" disabled={loading || !phone}>
|
|
{loading ? 'Sending...' : 'Send verification code'}
|
|
</button>
|
|
</form>
|
|
) : (
|
|
<form onSubmit={handleSetPassword} className="space-y-4">
|
|
<div className="flex items-start gap-3 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 text-blue-700 dark:text-blue-300 px-4 py-3 rounded">
|
|
<Info className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
|
<p className="text-sm">
|
|
If this phone number is Fayda-verified, an SMS with a verification code has been sent.
|
|
Enter it below with your new password.
|
|
</p>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-300 px-4 py-3 rounded">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Phone number</label>
|
|
<input
|
|
type="tel"
|
|
value={phone}
|
|
onChange={(e) => { setPhone(e.target.value); setError(''); }}
|
|
className="input-field"
|
|
placeholder="+251912345678"
|
|
autoComplete="tel"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Verification code</label>
|
|
<input
|
|
type="text"
|
|
value={otp}
|
|
onChange={(e) => { setOtp(e.target.value); setError(''); }}
|
|
className="input-field"
|
|
placeholder="6-character code from SMS"
|
|
maxLength={6}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">New password</label>
|
|
<input
|
|
type="password"
|
|
value={newPassword}
|
|
onChange={(e) => { setNewPassword(e.target.value); setError(''); }}
|
|
className="input-field"
|
|
placeholder="••••••••"
|
|
autoComplete="new-password"
|
|
minLength={6}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Confirm new password</label>
|
|
<input
|
|
type="password"
|
|
value={confirmPassword}
|
|
onChange={(e) => { setConfirmPassword(e.target.value); setError(''); }}
|
|
className="input-field"
|
|
placeholder="••••••••"
|
|
autoComplete="new-password"
|
|
minLength={6}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
className="btn-primary w-full"
|
|
disabled={loading || !phone || !otp || !newPassword || !confirmPassword}
|
|
>
|
|
{loading ? 'Verifying...' : 'Verify & set password'}
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => { setStep(1); setError(''); setOtp(''); }}
|
|
className="w-full text-sm text-gray-600 dark:text-gray-400 hover:text-[rgb(20_113_76)] dark:hover:text-emerald-400 transition-colors"
|
|
>
|
|
Didn't get a code? Send again
|
|
</button>
|
|
</form>
|
|
)}
|
|
|
|
{outcome === null && (
|
|
<div className="mt-6 text-center">
|
|
<Link
|
|
href="/login"
|
|
className="inline-flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-[rgb(20_113_76)] dark:hover:text-emerald-400 transition-colors"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
Back to sign in
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|