573 lines
31 KiB
JavaScript
573 lines
31 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { Head, Link, router } from '@inertiajs/react';
|
|
import axios from 'axios';
|
|
import { toast } from 'sonner';
|
|
import { compressImage } from '../../../lib/utils';
|
|
import {
|
|
Loader2,
|
|
ShieldAlert,
|
|
ShieldCheck,
|
|
UploadCloud,
|
|
FileText,
|
|
Users,
|
|
TrendingUp,
|
|
MessageSquare,
|
|
ArrowLeft
|
|
} from 'lucide-react';
|
|
|
|
export default function UploadEmiratesId({ email }) {
|
|
const [frontFile, setFrontFile] = useState(null);
|
|
const [backFile, setBackFile] = useState(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState(null);
|
|
|
|
// Modal state for verification & editing
|
|
const [showModal, setShowModal] = useState(false);
|
|
const [modalData, setModalData] = useState({
|
|
name: '',
|
|
emirates_id_number: '',
|
|
expiry_date: '',
|
|
date_of_birth: '',
|
|
nationality: '',
|
|
gender: '',
|
|
card_number: '',
|
|
occupation: '',
|
|
employer: '',
|
|
issue_place: '',
|
|
issue_date: '',
|
|
});
|
|
const [modalSaving, setModalSaving] = useState(false);
|
|
const [modalError, setModalError] = useState(null);
|
|
|
|
const handleModalChange = (field, value) => {
|
|
setModalData(prev => ({ ...prev, [field]: value }));
|
|
if (modalError) setModalError(null);
|
|
};
|
|
|
|
const handleConfirmSubmit = async () => {
|
|
if (!modalData.name.trim()) return setModalError('Full name is required.');
|
|
if (!modalData.emirates_id_number.trim()) return setModalError('Emirates ID number is required.');
|
|
if (!modalData.expiry_date.trim()) return setModalError('Expiry date is required.');
|
|
if (!modalData.date_of_birth.trim()) return setModalError('Date of birth is required.');
|
|
|
|
setModalSaving(true);
|
|
setModalError(null);
|
|
|
|
try {
|
|
await axios.post('/employer/confirm-emirates-id', modalData);
|
|
toast.success('Emirates ID details confirmed!');
|
|
setShowModal(false);
|
|
router.visit('/employer/register-payment');
|
|
} catch (err) {
|
|
if (err.response && err.response.data && err.response.data.errors) {
|
|
const errors = err.response.data.errors;
|
|
const firstError = Object.values(errors).flat()[0];
|
|
setModalError(firstError || 'Validation failed.');
|
|
} else {
|
|
setModalError(err.response?.data?.error || 'Failed to save confirmed data.');
|
|
}
|
|
} finally {
|
|
setModalSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleFrontFileChange = async (e) => {
|
|
const selectedFile = e.target.files[0];
|
|
if (selectedFile) {
|
|
if (selectedFile.size > 10240 * 1024) {
|
|
setError('The document must not exceed 10MB.');
|
|
toast.error('The document must not exceed 10MB.');
|
|
return;
|
|
}
|
|
try {
|
|
const compressedFile = await compressImage(selectedFile);
|
|
setFrontFile(compressedFile);
|
|
} catch (err) {
|
|
setFrontFile(selectedFile);
|
|
}
|
|
setError(null);
|
|
}
|
|
};
|
|
|
|
const handleBackFileChange = async (e) => {
|
|
const selectedFile = e.target.files[0];
|
|
if (selectedFile) {
|
|
if (selectedFile.size > 10240 * 1024) {
|
|
setError('The document must not exceed 10MB.');
|
|
toast.error('The document must not exceed 10MB.');
|
|
return;
|
|
}
|
|
try {
|
|
const compressedFile = await compressImage(selectedFile);
|
|
setBackFile(compressedFile);
|
|
} catch (err) {
|
|
setBackFile(selectedFile);
|
|
}
|
|
setError(null);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
if (!frontFile || !backFile) {
|
|
setError('Please upload both the front and back side of your Emirates ID.');
|
|
toast.error('Please upload both sides of your Emirates ID.');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const formData = new FormData();
|
|
formData.append('emirates_id_front', frontFile);
|
|
formData.append('emirates_id_back', backFile);
|
|
|
|
try {
|
|
const res = await axios.post('/employer/upload-emirates-id', formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data'
|
|
}
|
|
});
|
|
toast.success('Emirates ID scanned successfully!');
|
|
|
|
const extracted = res.data?.data || {};
|
|
setModalData({
|
|
name: extracted.name || '',
|
|
emirates_id_number: extracted.emirates_id_number || '',
|
|
expiry_date: extracted.expiry_date || '',
|
|
date_of_birth: extracted.date_of_birth || '',
|
|
nationality: extracted.nationality || '',
|
|
gender: extracted.gender || '',
|
|
card_number: extracted.card_number || '',
|
|
occupation: extracted.occupation || '',
|
|
employer: extracted.employer || '',
|
|
issue_place: extracted.issue_place || '',
|
|
issue_date: extracted.issue_date || '',
|
|
});
|
|
setShowModal(true);
|
|
} catch (err) {
|
|
if (err.response && err.response.data && err.response.data.errors) {
|
|
const errors = err.response.data.errors;
|
|
const firstError = Object.values(errors).flat()[0];
|
|
const errorMsg = firstError || 'Validation failed.';
|
|
setError(errorMsg);
|
|
toast.error(errorMsg);
|
|
} else {
|
|
const errorMsg = err.response?.data?.error || 'Scan extraction failed. Please try again.';
|
|
setError(errorMsg);
|
|
toast.error(errorMsg);
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const renderStepIndicator = () => (
|
|
<div className="w-full flex items-center justify-center space-x-1 sm:space-x-2 select-none pb-4 border-b border-slate-100">
|
|
{[
|
|
{ step: 1, label: 'Register' },
|
|
{ step: 2, label: 'Verify' },
|
|
{ step: 3, label: 'Emirates ID' },
|
|
{ step: 4, label: 'Payment' },
|
|
{ step: 5, label: 'Password' }
|
|
].map((s) => (
|
|
<React.Fragment key={s.step}>
|
|
<div className="flex items-center space-x-1 sm:space-x-1.5">
|
|
<div className={`w-5 h-5 rounded-full flex items-center justify-center font-bold text-[10px] border transition-all duration-300 ${
|
|
s.step === 3
|
|
? 'bg-[#185FA5] text-white border-[#185FA5] shadow-xs'
|
|
: s.step < 3
|
|
? 'bg-blue-50 text-[#185FA5] border-blue-200'
|
|
: 'bg-white text-slate-400 border-slate-200'
|
|
}`}>
|
|
{s.step}
|
|
</div>
|
|
{s.step === 3 && (
|
|
<span className="text-[10px] font-bold tracking-tight text-[#185FA5]">
|
|
{s.label}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{s.step < 5 && (
|
|
<div className="w-4 sm:w-6 h-0.5 bg-slate-100" />
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-12 bg-slate-50 font-sans">
|
|
<Head title="Upload Emirates ID - Migrant Portal" />
|
|
|
|
{/* Left Brand Panel (Desktop Only) */}
|
|
<div className="hidden lg:flex lg:col-span-5 bg-[#185FA5] p-12 flex-col justify-between text-white relative overflow-hidden select-none">
|
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-600/20 to-black/30 pointer-events-none" />
|
|
|
|
<div className="relative z-10 space-y-6">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-10 h-10 bg-white text-[#185FA5] rounded-xl flex items-center justify-center font-bold text-xl shadow-md">
|
|
M
|
|
</div>
|
|
<span className="font-bold text-2xl tracking-tight">Migrant Portal</span>
|
|
</div>
|
|
|
|
<div className="space-y-3 pt-6">
|
|
<span className="inline-block px-3 py-1 bg-white/10 border border-white/20 rounded-full text-xs font-semibold uppercase tracking-wider">
|
|
Sponsor Registration
|
|
</span>
|
|
<h1 className="text-4xl font-extrabold tracking-tight leading-tight">
|
|
Find verified domestic workers directly.
|
|
</h1>
|
|
<p className="text-blue-100 text-sm font-medium">
|
|
Unlock direct access to top domestic candidates with a Migrant premium subscription. Find, interview, and hire directly without middlemen.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stat Row */}
|
|
<div className="grid grid-cols-3 gap-4 pt-8 border-t border-white/10">
|
|
<div className="bg-white/10 backdrop-blur-sm p-4 rounded-xl border border-white/10 text-center space-y-1">
|
|
<Users className="w-6 h-6 text-emerald-300 mx-auto" />
|
|
<div className="font-bold text-lg">3000+</div>
|
|
<div className="text-[10px] text-blue-100 uppercase tracking-wider font-semibold">Worker Profiles</div>
|
|
</div>
|
|
|
|
<div className="bg-white/10 backdrop-blur-sm p-4 rounded-xl border border-white/10 text-center space-y-1">
|
|
<TrendingUp className="w-6 h-6 text-emerald-300 mx-auto" />
|
|
<div className="font-bold text-lg">550+</div>
|
|
<div className="text-[9px] text-blue-100 uppercase tracking-wider font-semibold leading-tight">Avg Monthly Hires</div>
|
|
</div>
|
|
|
|
<div className="bg-white/10 backdrop-blur-sm p-4 rounded-xl border border-white/10 text-center space-y-1">
|
|
<MessageSquare className="w-6 h-6 text-emerald-300 mx-auto" />
|
|
<div className="font-bold text-lg">Direct</div>
|
|
<div className="text-[10px] text-blue-100 uppercase tracking-wider font-semibold">Messaging</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative z-10 text-xs text-blue-200 font-medium">
|
|
© 2026 Migrant. All rights reserved.
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Form Container */}
|
|
<div className="col-span-1 lg:col-span-7 flex flex-col justify-center items-center p-6 sm:p-12 overflow-y-auto">
|
|
<div className="w-full max-w-lg bg-white rounded-2xl shadow-sm border border-slate-200 p-8 space-y-6">
|
|
{renderStepIndicator()}
|
|
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900 tracking-tight">Emirates ID Verification</h2>
|
|
<p className="text-xs text-gray-500 mt-1">Upload your identity document for quick OCR scanning</p>
|
|
</div>
|
|
|
|
{/* Security & Disclaimer Banner */}
|
|
<div className="bg-blue-50/70 border border-blue-100 rounded-xl p-4 flex items-start space-x-3">
|
|
<ShieldCheck className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
|
<div className="space-y-1">
|
|
<h4 className="text-xs font-bold text-blue-900 uppercase tracking-wider">Data Privacy Disclaimer</h4>
|
|
<p className="text-[11px] text-blue-700 leading-relaxed font-medium">
|
|
We do not store your physical Emirates ID document. Your card is temporarily scanned only to securely extract the ID number and expiry date via automated OCR, and the file is permanently deleted immediately after extraction.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{/* Front Side Upload */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-gray-700 mb-2 uppercase tracking-wider">
|
|
Emirates ID Front Side
|
|
</label>
|
|
|
|
<div className={`border-2 border-dashed rounded-xl p-5 text-center transition-all relative ${
|
|
frontFile
|
|
? 'border-emerald-500 bg-emerald-50/20'
|
|
: 'border-slate-300 hover:border-[#185FA5] bg-slate-50/50'
|
|
}`}>
|
|
<input
|
|
type="file"
|
|
accept="image/jpeg,image/png,image/jpg,application/pdf"
|
|
onChange={handleFrontFileChange}
|
|
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
/>
|
|
|
|
{!frontFile ? (
|
|
<div className="space-y-1">
|
|
<UploadCloud className="w-7 h-7 text-slate-400 mx-auto" />
|
|
<p className="text-xs font-bold text-gray-700">
|
|
Click or Drag front image here
|
|
</p>
|
|
<p className="text-[10px] text-gray-400">
|
|
Supported: JPG, JPEG, PNG, PDF (Max 10MB)
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center justify-center space-x-3">
|
|
<FileText className="w-7 h-7 text-emerald-600" />
|
|
<div className="text-left">
|
|
<p className="text-xs font-bold text-gray-800 truncate max-w-[200px]">
|
|
{frontFile.name}
|
|
</p>
|
|
<p className="text-[10px] text-gray-400">
|
|
{(frontFile.size / (1024 * 1024)).toFixed(2)} MB • Front Ready
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Back Side Upload */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-gray-700 mb-2 uppercase tracking-wider">
|
|
Emirates ID Back Side
|
|
</label>
|
|
|
|
<div className={`border-2 border-dashed rounded-xl p-5 text-center transition-all relative ${
|
|
backFile
|
|
? 'border-emerald-500 bg-emerald-50/20'
|
|
: 'border-slate-300 hover:border-[#185FA5] bg-slate-50/50'
|
|
}`}>
|
|
<input
|
|
type="file"
|
|
accept="image/jpeg,image/png,image/jpg,application/pdf"
|
|
onChange={handleBackFileChange}
|
|
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
/>
|
|
|
|
{!backFile ? (
|
|
<div className="space-y-1">
|
|
<UploadCloud className="w-7 h-7 text-slate-400 mx-auto" />
|
|
<p className="text-xs font-bold text-gray-700">
|
|
Click or Drag back image here
|
|
</p>
|
|
<p className="text-[10px] text-gray-400">
|
|
Supported: JPG, JPEG, PNG, PDF (Max 10MB)
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center justify-center space-x-3">
|
|
<FileText className="w-7 h-7 text-emerald-600" />
|
|
<div className="text-left">
|
|
<p className="text-xs font-bold text-gray-800 truncate max-w-[200px]">
|
|
{backFile.name}
|
|
</p>
|
|
<p className="text-[10px] text-gray-400">
|
|
{(backFile.size / (1024 * 1024)).toFixed(2)} MB • Back Ready
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{error && (
|
|
<p className="mt-3 text-xs text-red-500 font-semibold flex items-center">
|
|
<ShieldAlert className="w-3.5 h-3.5 mr-1" />
|
|
{error}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={loading || !frontFile || !backFile}
|
|
className="w-full bg-[#185FA5] hover:bg-[#0C447C] active:bg-[#08305c] text-white rounded-xl h-11 font-semibold text-sm transition-colors shadow-sm flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{loading ? (
|
|
<Loader2 className="w-5 h-5 animate-spin mr-2" />
|
|
) : (
|
|
'Scan & Extract Data'
|
|
)}
|
|
</button>
|
|
</form>
|
|
|
|
<div className="border-t border-slate-100 pt-6 text-center">
|
|
<Link href="/employer/register" className="inline-flex items-center space-x-1 text-xs text-gray-600 font-medium hover:text-[#185FA5] hover:underline">
|
|
<ArrowLeft className="w-3.5 h-3.5 mr-1" />
|
|
<span>Change Email / Back</span>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Confirmation and Edit Modal */}
|
|
{showModal && (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-sm">
|
|
<div className="bg-white rounded-2xl shadow-2xl border border-slate-200 w-full max-w-2xl max-h-[90vh] overflow-y-auto transform scale-100 transition-all duration-300 flex flex-col">
|
|
{/* Header */}
|
|
<div className="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50 rounded-t-2xl">
|
|
<div>
|
|
<h3 className="text-lg font-bold text-slate-800">Confirm Extracted EID Data</h3>
|
|
<p className="text-xs text-slate-500 mt-0.5">Please verify and correct the scanned Emirates ID details before proceeding.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Form Fields */}
|
|
<div className="p-6 space-y-4 overflow-y-auto flex-1">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* Full Name */}
|
|
<div className="md:col-span-2">
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Full Name</label>
|
|
<input
|
|
type="text"
|
|
value={modalData.name}
|
|
onChange={(e) => handleModalChange('name', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Emirates ID Number */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Emirates ID Number</label>
|
|
<input
|
|
type="text"
|
|
value={modalData.emirates_id_number}
|
|
onChange={(e) => handleModalChange('emirates_id_number', e.target.value)}
|
|
placeholder="784-XXXX-XXXXXXX-X"
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Expiry Date */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Expiry Date</label>
|
|
<input
|
|
type="date"
|
|
value={modalData.expiry_date}
|
|
onChange={(e) => handleModalChange('expiry_date', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Date of Birth */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Date of Birth</label>
|
|
<input
|
|
type="date"
|
|
value={modalData.date_of_birth}
|
|
onChange={(e) => handleModalChange('date_of_birth', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Nationality */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Nationality</label>
|
|
<input
|
|
type="text"
|
|
value={modalData.nationality}
|
|
onChange={(e) => handleModalChange('nationality', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Gender */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Gender</label>
|
|
<select
|
|
value={modalData.gender}
|
|
onChange={(e) => handleModalChange('gender', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] bg-white"
|
|
>
|
|
<option value="">Select Gender</option>
|
|
<option value="M">Male (M)</option>
|
|
<option value="F">Female (F)</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Card Number */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Card Number</label>
|
|
<input
|
|
type="text"
|
|
value={modalData.card_number}
|
|
onChange={(e) => handleModalChange('card_number', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Occupation */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Occupation</label>
|
|
<input
|
|
type="text"
|
|
value={modalData.occupation}
|
|
onChange={(e) => handleModalChange('occupation', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Sponsor */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Sponsor</label>
|
|
<input
|
|
type="text"
|
|
value={modalData.employer}
|
|
onChange={(e) => handleModalChange('employer', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Issue Place */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Issue Place</label>
|
|
<input
|
|
type="text"
|
|
value={modalData.issue_place}
|
|
onChange={(e) => handleModalChange('issue_place', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Issue Date */}
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Issue Date</label>
|
|
<input
|
|
type="date"
|
|
value={modalData.issue_date}
|
|
onChange={(e) => handleModalChange('issue_date', e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{modalError && (
|
|
<p className="text-xs text-red-500 font-semibold flex items-center mt-2">
|
|
<ShieldAlert className="w-3.5 h-3.5 mr-1" />
|
|
{modalError}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="px-6 py-4 bg-slate-50 border-t border-slate-100 flex items-center justify-end gap-3 rounded-b-2xl">
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowModal(false)}
|
|
className="px-4 py-2 text-xs font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors h-10"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleConfirmSubmit}
|
|
disabled={modalSaving}
|
|
className="px-5 py-2 text-xs font-semibold text-white bg-[#185FA5] hover:bg-[#0C447C] active:bg-[#08305c] rounded-xl shadow-sm transition-colors flex items-center justify-center h-10 disabled:opacity-50"
|
|
>
|
|
{modalSaving ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 animate-spin mr-1.5" />
|
|
Saving...
|
|
</>
|
|
) : (
|
|
'Confirm & Continue'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|