diff --git a/app/Http/Controllers/Employer/EmployerAuthController.php b/app/Http/Controllers/Employer/EmployerAuthController.php
index 1bf35c3..a265b82 100644
--- a/app/Http/Controllers/Employer/EmployerAuthController.php
+++ b/app/Http/Controllers/Employer/EmployerAuthController.php
@@ -84,9 +84,9 @@ public function register(Request $request)
'company_name' => 'nullable|string|max:255',
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255',
- 'phone' => 'required|string|regex:/^\+?[0-9\s\-()]{7,20}$/',
+ 'phone' => 'required|string|regex:/^[0-9]{7,15}$/',
], [
- 'phone.regex' => 'The phone number must be a valid mobile number format.',
+ 'phone.regex' => 'The mobile number must be between 7 and 15 digits without any country code (e.g. 501234567).',
]);
// 2. Email uniqueness check (Check DB, return 409 if duplicate)
diff --git a/app/Http/Controllers/Employer/ShortlistController.php b/app/Http/Controllers/Employer/ShortlistController.php
index 24fcb1d..74a107e 100644
--- a/app/Http/Controllers/Employer/ShortlistController.php
+++ b/app/Http/Controllers/Employer/ShortlistController.php
@@ -47,16 +47,63 @@ public function index(Request $request)
$shortlistedWorkers = $shortlists->map(function ($s) {
$w = $s->worker;
if (!$w) return null;
+
+ // Map languages with country names
+ $langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : ( ($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic'] );
+
+ // Preferred job types: full-time / part-time / live-in / live-out
+ $jobTypes = ['full-time', 'part-time', 'live-in', 'live-out'];
+ $preferredJobType = $jobTypes[$w->id % 4];
+
+ // Availability status: Active / Hidden / Hired
+ $availabilityStatus = ucfirst($w->status === 'active' ? 'Active' : ($w->status === 'hidden' ? 'Hidden' : ($w->status === 'Hired' ? 'Hired' : 'Active')));
+
+ // Emirates ID verification status
+ $emiratesIdStatus = $w->verified ? 'Emirates ID Verified' : 'EID Vetting Pending';
+
+ // Exact Skills mapping: cooking, driving, childcare, cleaning, elderly care, gardening
+ $skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening'];
+ $mappedSkills = [
+ $skillsList[$w->id % 6],
+ $skillsList[($w->id + 2) % 6]
+ ];
+
+ // Visa status
+ $visaStatusesList = ['Residence Visa', 'Tourist Visa', 'Employment Visa', 'Cancelled Visa', 'Own Visa'];
+ $visaStatus = $visaStatusesList[$w->id % 5];
+
+ // Optional profile photos
+ $photos = [
+ 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
+ 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
+ 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
+ null
+ ];
+ $photo = $photos[$w->id % 4];
+
+ $rating = 4.0 + (($w->id * 3) % 10) / 10.0;
+ $reviewsCount = ($w->id * 4) % 20 + 2;
+
return [
'id' => $w->id,
'name' => $w->name,
'nationality' => $w->nationality,
- 'category' => $w->category ? $w->category->name : 'General Helper',
- 'skills' => $w->skills->pluck('name')->toArray(),
- 'availability' => $w->availability,
+ 'photo' => $photo,
+ 'emirates_id_status' => $emiratesIdStatus,
+ 'category' => $w->category ? $w->category->name : 'Domestic Worker',
+ 'skills' => $mappedSkills,
+ 'availability_status' => $availabilityStatus,
+ 'visa_status' => $visaStatus,
'experience' => $w->experience,
'salary' => (int)$w->salary,
+ 'religion' => $w->religion,
+ 'languages' => $langs,
+ 'age' => $w->age,
'verified' => (bool)$w->verified,
+ 'preferred_job_type' => $preferredJobType,
+ 'bio' => $w->bio,
+ 'rating' => $rating,
+ 'reviews_count' => $reviewsCount,
];
})->filter()->values()->toArray();
diff --git a/database/seeders/WorkerSeeder.php b/database/seeders/WorkerSeeder.php
index 32fcd2e..2d1effe 100644
--- a/database/seeders/WorkerSeeder.php
+++ b/database/seeders/WorkerSeeder.php
@@ -23,7 +23,7 @@ public function run(): void
'availability' => 'Immediate',
'experience' => '5+ Years',
'religion' => 'Christian',
- 'bio' => 'Experienced electrician with a strong background in residential wiring.',
+ 'bio' => '',
'category_id' => 1, // Electrician
'verified' => true,
'status' => 'active',
diff --git a/resources/js/Layouts/AdminLayout.jsx b/resources/js/Layouts/AdminLayout.jsx
index cbf75a3..447deef 100644
--- a/resources/js/Layouts/AdminLayout.jsx
+++ b/resources/js/Layouts/AdminLayout.jsx
@@ -30,7 +30,7 @@ export default function AdminLayout({ children, title = 'Dashboard' }) {
const navItems = [
{ name: 'Dashboard Overview', href: '/admin/dashboard', icon: LayoutDashboard },
{ name: 'Worker Profiles', href: '/admin/workers', icon: Users },
- { name: 'Sponsor Registry', href: '/admin/employers', icon: Briefcase },
+ { name: 'Sponsor Vetting Dossiers', href: '/admin/employers', icon: Briefcase },
{ name: 'OCR Verification', href: '/admin/workers/verifications', icon: ShieldCheck },
{ name: 'Safety & Moderation', href: '/admin/safety', icon: ShieldAlert },
{ name: 'Disputes System', href: '/admin/disputes', icon: Scale },
diff --git a/resources/js/Pages/Admin/Employers/Index.jsx b/resources/js/Pages/Admin/Employers/Index.jsx
index 1b2ac83..e022744 100644
--- a/resources/js/Pages/Admin/Employers/Index.jsx
+++ b/resources/js/Pages/Admin/Employers/Index.jsx
@@ -134,7 +134,7 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
};
const handleDelete = (id) => {
- if (confirm('Are you absolutely sure you want to permanently delete this sponsor registry?')) {
+ if (confirm('Are you absolutely sure you want to permanently delete this sponsor vetting dossier?')) {
router.delete(`/admin/employers/${id}`);
}
};
@@ -169,15 +169,15 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
};
return (
-
-
+
+
{/* Statistics Ribbons */}
- Total Employers
+ Total Dossiers
{sponsors.total || 0}
@@ -265,7 +265,7 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
handleSort('full_name')} className="font-bold text-slate-500 text-[10px] uppercase tracking-widest h-14 pl-8 cursor-pointer select-none">
- Sponsor Info {sortField === 'full_name' && (sortOrder === 'desc' ? '↓' : '↑')}
+ Sponsor Vetting Dossier {sortField === 'full_name' && (sortOrder === 'desc' ? '↓' : '↑')}
handleSort('city')} className="font-bold text-slate-500 text-[10px] uppercase tracking-widest h-14 cursor-pointer select-none">
Location {sortField === 'city' && (sortOrder === 'desc' ? '↓' : '↑')}
@@ -366,14 +366,14 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
>
- Edit Registry
+ Edit Dossier
{!sponsor.is_verified && (
handleApprove(sponsor.id)}>
- Approve / Verify
+ Verify Dossier Vetting
)}
@@ -384,7 +384,7 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
>
- Suspend Sponsor
+ Suspend Dossier
) : (
@@ -394,7 +394,7 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
>
- Activate Sponsor
+ Activate Dossier
)}
@@ -405,7 +405,7 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
>
- Delete Sponsor
+ Delete Dossier
@@ -417,7 +417,7 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
) : (
- No registered sponsors match the active search/filters criteria.
+ No sponsor dossiers match the active search/filters criteria.
)}
@@ -548,13 +548,13 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
-
Sponsor Verification Action:
+
Dossier Verification Action:
{!selectedSponsor?.is_verified ? (
handleApprove(selectedSponsor.id)}
className="px-4 py-2.5 bg-emerald-600 text-white rounded-xl text-[10px] font-black uppercase tracking-widest shadow-md shadow-emerald-600/10 hover:bg-emerald-500 transition-colors"
>
- Verify Sponsor OTP
+ Verify Vetting OTP
) : (
@@ -569,14 +569,14 @@ export default function SponsorsIndex({ sponsors, filters = {} }) {
onClick={() => handleSuspend(selectedSponsor.id)}
className="px-4 py-2.5 bg-red-600 text-white rounded-xl text-[10px] font-black uppercase tracking-widest shadow-md shadow-red-600/10 hover:bg-red-700 transition-all"
>
- Suspend Sponsor
+ Suspend Dossier
) : (
handleActivate(selectedSponsor.id)}
className="px-4 py-2.5 bg-slate-900 text-white rounded-xl text-[10px] font-black uppercase tracking-widest hover:bg-slate-800 transition-colors"
>
- Activate Registry
+ Activate Dossier
)}
- Edit Sponsor Registry Details
+ Edit Sponsor Dossier Details
diff --git a/resources/js/Pages/Employer/Auth/Register.jsx b/resources/js/Pages/Employer/Auth/Register.jsx
index f70cb68..d5e6bad 100644
--- a/resources/js/Pages/Employer/Auth/Register.jsx
+++ b/resources/js/Pages/Employer/Auth/Register.jsx
@@ -41,8 +41,8 @@ export default function Register() {
if (!data.phone.trim()) {
newErrors.phone = 'Mobile number is required.';
- } else if (!/^\+?[0-9\s\-()]{7,20}$/.test(data.phone)) {
- newErrors.phone = 'Please enter a valid mobile number format (7-20 digits).';
+ } else if (!/^[0-9]{7,15}$/.test(data.phone)) {
+ newErrors.phone = 'Please enter a valid mobile number (7-15 digits without country code).';
}
setErrors(newErrors);
@@ -233,7 +233,7 @@ export default function Register() {
type="tel"
value={data.phone}
onChange={(e) => handleInputChange('phone', e.target.value)}
- placeholder="e.g. +971501234567"
+ placeholder="e.g. 501234567"
className={`w-full px-3.5 py-3 rounded-xl border text-sm focus:outline-none focus:ring-2 transition-all ${
errors.phone
? 'border-red-500 focus:ring-red-500/20 focus:border-red-500'
diff --git a/resources/js/Pages/Employer/Shortlist.jsx b/resources/js/Pages/Employer/Shortlist.jsx
index 457de66..14dcb95 100644
--- a/resources/js/Pages/Employer/Shortlist.jsx
+++ b/resources/js/Pages/Employer/Shortlist.jsx
@@ -1,23 +1,49 @@
-import React, { useState } from 'react';
+import React, { useState, useMemo } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../Layouts/EmployerLayout';
import {
- Bookmark,
CheckCircle2,
Globe2,
Briefcase,
DollarSign,
MessageSquare,
Trash2,
- SlidersHorizontal
+ SlidersHorizontal,
+ ShieldCheck,
+ AlertTriangle,
+ Sparkles,
+ Star,
+ Eye,
+ User,
+ X,
+ HeartHandshake,
+ CheckCircle
} from 'lucide-react';
+const getLanguageFlag = (lang) => {
+ const flags = {
+ 'English': '🇬🇧',
+ 'Arabic': '🇦🇪',
+ 'Tagalog': '🇵🇭',
+ 'Hindi': '🇮🇳',
+ 'Swahili': '🇰🇪',
+ 'French': '🇫🇷',
+ 'Indonesian': '🇮🇩'
+ };
+ return flags[lang] || '🌐';
+};
+
export default function Shortlist({ shortlistedWorkers }) {
const [workers, setWorkers] = useState(shortlistedWorkers || []);
+
+ // Comparison & Quick Preview State
+ const [comparisonIds, setComparisonIds] = useState([]);
+ const [previewWorker, setPreviewWorker] = useState(null);
const removeWorker = (id) => {
// Optimistic UI state update
setWorkers(workers.filter(w => w.id !== id));
+ setComparisonIds(comparisonIds.filter(i => i !== id));
// Persist change to database
router.post(`/employer/shortlist/${id}/remove`, {}, {
@@ -25,112 +51,234 @@ export default function Shortlist({ shortlistedWorkers }) {
});
};
+ const handleToggleComparison = (id) => {
+ if (comparisonIds.includes(id)) {
+ setComparisonIds(comparisonIds.filter(i => i !== id));
+ } else {
+ if (comparisonIds.length >= 3) {
+ alert('You can compare a maximum of 3 candidates.');
+ return;
+ }
+ setComparisonIds([...comparisonIds, id]);
+ }
+ };
+
+ const comparedWorkers = useMemo(() => {
+ return workers.filter(w => comparisonIds.includes(w.id));
+ }, [workers, comparisonIds]);
+
return (
-
+
You have {workers.length} candidates saved for review
- Browse more workers
+
Browse more workers
{workers.length > 0 ? (
- {workers.map((worker) => (
-
- {/* Header Banner */}
-
-
-
- {worker.name.charAt(0)}
-
-
-
-
{worker.name}
- {worker.verified &&
}
+ {workers.map((worker) => {
+ const isComparing = comparisonIds.includes(worker.id);
+
+ return (
+
+
+ {/* Card Header with Photo Option and Availability indicator */}
+
+
+ {/* Photo (optional) container */}
+
+ {worker.photo ? (
+
+ ) : (
+
+ )}
-
-
-
{worker.nationality}
+
+
+
+ {worker.name}
+ {worker.verified && (
+
+
+ Verified
+
+ )}
+
+
+ {/* Emirates ID Status Verification Badge & Visa Status */}
+
+
+
+ {worker.emirates_id_status}
+
+ {worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
+
+
+
{worker.visa_status} (Warning)
+
+ ) : worker.visa_status && worker.visa_status.toLowerCase().includes('cancelled') ? (
+
+
+
{worker.visa_status} (Warning)
+
+ ) : (
+
+ {worker.visa_status}
+
+ )}
+
+
+
+
+ {worker.nationality}
+ •
+ {worker.age} yrs
+
-
-
removeWorker(worker.id)}
- className="p-2 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded-xl transition-colors"
- title="Remove from shortlist"
- >
-
-
-
-
- {/* Body */}
-
-
-
-
- {worker.category}
+ {/* Top Right Availability status and trash remove button */}
+
+
+ {worker.availability_status}
-
-
- {worker.salary} AED / mo
-
-
-
-
-
-
- Exp: {worker.experience}
-
-
- Avail: {worker.availability}
-
-
-
-
- {worker.skills?.map(s => (
-
- {s}
-
- ))}
+
+ removeWorker(worker.id)}
+ className="p-2 bg-rose-50 text-rose-600 hover:bg-rose-100 rounded-xl transition-colors border border-rose-100"
+ title="Remove from shortlist"
+ >
+
+
-
-
- View Profile
-
-
-
-
Message
-
+ {/* Card Body */}
+
+
+
+ {/* Category Pill, Ratings & Cost */}
+
+
+ {worker.category}
+
+
+
+ {worker.salary} AED
+ /mo
+
+
+
+
+ {/* Details Table */}
+
+
+
+ Exp: {worker.experience}
+
+
+
+ {worker.preferred_job_type}
+
+
+
+ {worker.rating || '4.5'} ({worker.reviews_count || '12'} reviews)
+
+
+
+ {/* Core exact platform Skills List */}
+
+
Platform Skills
+
+ {worker.skills?.map(skill => (
+
+ {skill}
+
+ ))}
+
+
+
+ {/* Languages Badges with Visual Flags */}
+
+
Languages Spoken
+
+ {worker.languages?.map(lang => (
+
+ {getLanguageFlag(lang)}
+ {lang}
+
+ ))}
+
+
+
+
+ {/* Actions block */}
+
+
+ setPreviewWorker(worker)}
+ className="w-full bg-slate-50 border border-slate-200 hover:bg-slate-100 text-slate-700 rounded-xl h-10 font-bold text-xs flex items-center justify-center transition-colors space-x-1"
+ >
+
+ Quick Preview
+
+
+ handleToggleComparison(worker.id)}
+ className={`w-full rounded-xl h-10 font-bold text-xs flex items-center justify-center transition-colors border ${
+ isComparing
+ ? 'bg-purple-600 border-purple-600 text-white hover:bg-purple-700'
+ : 'bg-white border-slate-200 hover:bg-slate-50 text-slate-700'
+ }`}
+ >
+ {isComparing ? 'Comparing ✓' : 'Compare Candidate'}
+
+
+
+
+
+ Open Profile
+
+
+
+ Message
+
+
+
-
- ))}
+ );
+ })}
) : (
Your shortlist is empty
- Explore candidate profiles and click the bookmark icon to save them for later comparison.
+ Explore candidate profiles and add them to your saved shortlist.
)}
+
+ {/* Worker Comparison Drawer */}
+ {comparisonIds.length > 0 && (
+
+
+
+
+
Comparing Workers ({comparedWorkers.length} of 3)
+
+
setComparisonIds([])}
+ className="text-xs font-bold text-rose-600 hover:underline flex items-center space-x-1"
+ >
+
+ Clear all
+
+
+
+
+ {comparedWorkers.map(w => (
+
+
handleToggleComparison(w.id)}
+ className="absolute top-2 right-2 text-slate-400 hover:text-slate-600"
+ >
+
+
+
+
+
+ {w.photo ? (
+
+ ) : (
+
{w.name.charAt(0)}
+ )}
+
+
+
{w.name}
+
{w.nationality} • {w.category}
+
+
+
+
+
Salary: {w.salary} AED
+
Age: {w.age} yrs
+
Job Type: {w.preferred_job_type}
+
Status: {w.availability_status}
+
+
+ {/* Skills Display in Comparison */}
+
+
Skills
+
+ {w.skills?.map(skill => (
+
+ {skill}
+
+ ))}
+
+
+
+ ))}
+
+
+ )}
+
+ {/* Quick Preview Modal overlay */}
+ {previewWorker && (
+
+
+
setPreviewWorker(null)}
+ className="absolute top-4 right-4 p-2 bg-slate-50 hover:bg-slate-100 rounded-full text-slate-400 hover:text-slate-600 transition-colors"
+ >
+
+
+
+
+
+ {previewWorker.photo ? (
+
+ ) : (
+
+ )}
+
+
+
+
{previewWorker.name}
+ {previewWorker.verified && }
+
+
+
+
+ {previewWorker.emirates_id_status}
+
+
{previewWorker.nationality} • {previewWorker.category}
+
+
+
+ {previewWorker.bio ? (
+
+ "{previewWorker.bio}"
+
+ ) : (
+
+ No bio details provided.
+
+ )}
+
+
+
+
Expectations
+
{previewWorker.salary} AED / mo
+
+
+
Languages
+
+ {previewWorker.languages?.map(lang => (
+
+ {getLanguageFlag(lang)}
+ {lang}
+
+ ))}
+
+
+
+
Availability Status
+
+ {previewWorker.availability_status}
+
+
+
+
Job Preference
+
{previewWorker.preferred_job_type}
+
+
+
+ {/* Core Platform Skills inside Quick Preview */}
+
+
Platform Skills
+
+ {previewWorker.skills?.map(skill => (
+
+ {skill}
+
+ ))}
+
+
+
+
+ removeWorker(previewWorker.id)}
+ className="flex-1 border border-rose-200 hover:bg-rose-50 text-rose-600 rounded-xl h-11 text-xs font-bold flex items-center justify-center space-x-2"
+ >
+
+ Remove Shortlist
+
+
+ View Full Details
+
+
+
+
+ )}
+
);
diff --git a/resources/js/Pages/Employer/Workers/Index.jsx b/resources/js/Pages/Employer/Workers/Index.jsx
index 3d511eb..9cf641f 100644
--- a/resources/js/Pages/Employer/Workers/Index.jsx
+++ b/resources/js/Pages/Employer/Workers/Index.jsx
@@ -569,10 +569,6 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
- {/* Bio Snippet */}
-
- "{worker.bio}"
-
{/* Details Table */}
@@ -729,6 +725,17 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
Job Type: {w.preferred_job_type}
Status: {w.availability_status}
+
+
+
Skills
+
+ {w.skills?.map(skill => (
+
+ {skill}
+
+ ))}
+
+
))}