This commit is contained in:
mohanmd 2026-05-20 13:26:43 +05:30
parent 2917f11d12
commit 2e3697db29
87 changed files with 3655 additions and 5123 deletions

View File

@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class ClearDatabaseData extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'db:clear-data';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear active database data tables for manual testing, preserving authentication and categories';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Clearing database tables...');
Schema::disableForeignKeyConstraints();
// Truncate recruitment, dynamic, and worker tables
DB::table('shortlists')->truncate();
DB::table('job_applications')->truncate();
DB::table('job_posts')->truncate();
DB::table('messages')->truncate();
DB::table('conversations')->truncate();
DB::table('announcements')->truncate();
DB::table('worker_documents')->truncate();
DB::table('worker_skills')->truncate();
DB::table('workers')->truncate();
// Also delete non-seeded users if any (keep Admin and Employer)
DB::table('users')->whereNotIn('email', ['admin@example.com', 'employer1@example.com'])->delete();
Schema::enableForeignKeyConstraints();
$this->info('Database cleared successfully! You can now register, post jobs, and apply manually.');
return Command::SUCCESS;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Employer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Announcement;
class AnnouncementController extends Controller
{
public function index(Request $request)
{
$dbAnnouncements = Announcement::latest()->get();
$announcements = $dbAnnouncements->map(function ($ann) {
return [
'id' => $ann->id,
'title' => $ann->title,
'content' => $ann->body,
'audience' => in_array($ann->type, ['Shortlisted', 'Selected Candidates']) ? $ann->type : 'Selected Candidates',
'created_at' => $ann->created_at->diffForHumans(),
];
})->toArray();
return Inertia::render('Employer/Announcements', [
'initialAnnouncements' => $announcements,
]);
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'audience' => 'required|string|in:Shortlisted,Selected Candidates',
]);
Announcement::create([
'title' => $request->title,
'body' => $request->content,
'type' => $request->audience,
]);
return back()->with('success', 'Announcement posted successfully.');
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace App\Http\Controllers\Employer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\JobApplication;
use App\Models\JobPost;
class CandidateController extends Controller
{
private function resolveCurrentUser()
{
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
if (!$sessId) {
$user = User::where('role', 'employer')->first();
if ($user) {
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
return $user;
}
} else {
return User::find($sessId);
}
return null;
}
public function index(Request $request)
{
$user = $this->resolveCurrentUser();
$employerId = $user ? $user->id : 2;
// Get job posts created by this employer
$jobIds = JobPost::where('employer_id', $employerId)->pluck('id');
// Fetch applications for those jobs
$applications = JobApplication::whereIn('job_id', $jobIds)
->with(['worker.category', 'jobPost'])
->get();
$selectedWorkers = $applications->map(function ($app) {
$w = $app->worker;
if (!$w) return null;
// Map DB status to UI friendly status
$status = 'Reviewing';
if ($app->status === 'hired') $status = 'Hired';
elseif ($app->status === 'rejected') $status = 'Rejected';
elseif ($app->status === 'shortlisted' || $app->status === 'offer_sent') $status = 'Offer Sent';
elseif ($app->status === 'applied') $status = 'Reviewing';
else $status = ucfirst($app->status);
return [
'id' => $app->id, // application id
'worker_id' => $w->id,
'name' => $w->name,
'nationality' => $w->nationality,
'category' => $w->category ? $w->category->name : 'General Helper',
'salary' => (int)$w->salary,
'status' => $status,
'applied_at' => $app->created_at->format('M d, Y'),
];
})->filter()->values()->toArray();
return Inertia::render('Employer/SelectedCandidates', [
'selectedWorkers' => $selectedWorkers,
]);
}
public function updateStatus(Request $request, $id)
{
$request->validate([
'status' => 'required|string|in:Reviewing,Offer Sent,Hired,Rejected',
]);
$app = JobApplication::findOrFail($id);
// Map UI status back to DB status
$dbStatus = 'applied';
if ($request->status === 'Hired') $dbStatus = 'hired';
elseif ($request->status === 'Rejected') $dbStatus = 'rejected';
elseif ($request->status === 'Offer Sent') $dbStatus = 'shortlisted';
elseif ($request->status === 'Reviewing') $dbStatus = 'applied';
$app->update([
'status' => $dbStatus,
]);
return back()->with('success', 'Candidate status updated successfully to ' . $request->status);
}
}

View File

@ -5,106 +5,126 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\Shortlist;
use App\Models\Conversation;
use App\Models\Message;
use App\Models\Announcement;
use Carbon\Carbon;
class DashboardController extends Controller
{
public function index(Request $request)
{
// Resolve current employer user
$sess = session('user');
$user = is_array($sess) ? (object)$sess : ($sess ?? (object)[
'name' => 'John Doe',
'subscription_status' => 'active',
'subscription_expires_at' => now()->addDays(24)->format('Y-m-d'),
]);
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
if (!$sessId) {
$user = User::where('role', 'employer')->first();
if ($user) {
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
}
} else {
$user = User::find($sessId);
}
// Fallback user if DB has no users (precaution)
if (!$user) {
$user = new User([
'id' => 2,
'name' => 'John Doe',
'subscription_status' => 'active',
'subscription_expires_at' => now()->addDays(24),
]);
}
// Get subscription expires at date
$sub = $user->subscription()->where('status', 'active')->latest()->first();
$expiresAt = $sub ? $sub->expires_at : now()->addDays(24);
$daysRemaining = $expiresAt ? max(0, now()->diffInDays($expiresAt, false)) : 30;
// 1. Stats
$shortlistedCount = Shortlist::where('employer_id', $user->id)->count();
$messagesSent = Message::where('sender_id', $user->id)->where('sender_type', 'employer')->count();
$stats = [
'shortlisted_count' => $shortlistedCount,
'messages_sent' => $messagesSent,
'profile_views_given' => 45, // Static/mock analytic metric for page visits
'days_remaining' => (int)$daysRemaining,
];
// 2. Shortlisted workers
$shortlists = Shortlist::where('employer_id', $user->id)
->with(['worker.category', 'worker.skills'])
->get();
$shortlistedWorkers = $shortlists->map(function ($s) {
$w = $s->worker;
if (!$w) return null;
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_url' => null,
'verified' => (bool)$w->verified,
];
})->filter()->values()->toArray();
// 3. Recent messages
$dbConversations = Conversation::where('employer_id', $user->id)
->with(['worker', 'messages' => function ($q) {
$q->latest();
}])
->get();
$recentMessages = $dbConversations->map(function ($conv) {
$lastMsg = $conv->messages->first();
$worker = $conv->worker;
if (!$worker || !$lastMsg) return null;
return [
'id' => $conv->id,
'worker_name' => $worker->name,
'last_message' => $lastMsg->text,
'unread' => $lastMsg->sender_type === 'worker' && is_null($lastMsg->read_at),
'sent_at' => $lastMsg->created_at->diffForHumans(),
'timestamp' => $lastMsg->created_at,
];
})->filter()->sortByDesc('timestamp')->values()->toArray();
// 4. Announcements
$dbAnnouncements = Announcement::latest()->limit(5)->get();
$announcements = $dbAnnouncements->map(function ($ann) {
return [
'id' => $ann->id,
'title' => $ann->title,
'body' => $ann->body,
'created_at' => $ann->created_at->format('M d, Y'),
];
})->toArray();
return Inertia::render('Employer/Dashboard', [
'employer' => [
'name' => $user->name ?? 'John Doe',
'subscription_status' => $user->subscription_status ?? 'active',
'subscription_expires_at' => $user->subscription_expires_at ?? '2026-12-31',
'plan_name' => 'Premium Employer Pass',
],
'stats' => [
'shortlisted_count' => 4,
'messages_sent' => 18,
'profile_views_given' => 45,
'days_remaining' => 24,
],
'shortlisted_workers' => [
[
'id' => 101,
'name' => 'Maria Santos',
'nationality' => 'Philippines',
'skills' => ['Childcare', 'Cooking', 'Housekeeping'],
'availability' => 'Immediate',
'photo_url' => null,
'verified' => true,
],
[
'id' => 102,
'name' => 'Lakshmi Sharma',
'nationality' => 'India',
'skills' => ['Elderly Care', 'Cooking'],
'availability' => '2 Weeks',
'photo_url' => null,
'verified' => true,
],
[
'id' => 103,
'name' => 'Siti Aminah',
'nationality' => 'Indonesia',
'skills' => ['Housekeeping', 'Ironing'],
'availability' => 'Immediate',
'photo_url' => null,
'verified' => true,
],
[
'id' => 104,
'name' => 'Grace Osei',
'nationality' => 'Ghana',
'skills' => ['Childcare', 'English Tutoring'],
'availability' => '1 Month',
'photo_url' => null,
'verified' => false,
],
],
'recent_messages' => [
[
'id' => 201,
'worker_name' => 'Maria Santos',
'last_message' => 'Yes ma\'am, I am available for a video interview tomorrow at 10 AM.',
'unread' => true,
'sent_at' => '10 mins ago',
],
[
'id' => 202,
'worker_name' => 'Lakshmi Sharma',
'last_message' => 'I have 5 years of experience in Dubai taking care of elderly patients.',
'unread' => true,
'sent_at' => '2 hours ago',
],
[
'id' => 203,
'worker_name' => 'Siti Aminah',
'last_message' => 'Thank ma\'am for shortlisting my profile. Please let me know your offer.',
'unread' => false,
'sent_at' => '1 day ago',
],
],
'announcements' => [
[
'id' => 301,
'title' => 'New Visa Regulations Update',
'body' => 'The Ministry of Human Resources and Emiratisation has announced updated guidelines for domestic worker sponsorship starting this month.',
'created_at' => 'May 10, 2026',
],
[
'id' => 302,
'title' => 'Enhanced OCR Verification Active',
'body' => 'We have deployed upgraded OCR passport verification to ensure 100% legal compliance for all worker profiles on the platform.',
'created_at' => 'May 1, 2026',
],
'name' => $user->name,
'subscription_status' => $sub ? $sub->status : 'active',
'subscription_expires_at' => $expiresAt ? $expiresAt->format('Y-m-d') : '2026-12-31',
'plan_name' => $sub ? (ucfirst($sub->plan_id) . ' Pass') : 'Premium Employer Pass',
],
'stats' => $stats,
'shortlisted_workers' => $shortlistedWorkers,
'recent_messages' => array_slice($recentMessages, 0, 5),
'announcements' => $announcements,
]);
}
}

View File

@ -5,6 +5,11 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\EmployerProfile;
use App\Mail\EmployerOtpMail;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
class EmployerAuthController extends Controller
{
@ -35,6 +40,7 @@ public function login(Request $request)
return back()->with('status', 'subscription_expired');
}
// Attempt logging in via standard Auth or fallback credentials
if ($credentials['email'] === 'employer@example.com' && $credentials['password'] === 'password') {
session(['user' => (object)[
'id' => 2,
@ -54,6 +60,29 @@ public function login(Request $request)
return redirect()->intended('/employer/dashboard');
}
// Attempt actual database login
$user = User::where('email', $credentials['email'])->first();
if ($user && Hash::check($credentials['password'], $user->password) && $user->role === 'employer') {
$profile = EmployerProfile::where('user_id', $user->id)->first();
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => $user->role,
'subscription_status' => $user->subscription_status ?? 'active',
'verification_status' => $profile ? $profile->verification_status : 'approved',
]]);
$request->session()->regenerate();
if ($request->source === 'mobile') {
return redirect('/mobile/employer/home');
}
return redirect()->intended('/employer/dashboard');
}
return back()->withErrors([
'email' => 'Invalid credentials. Use employer@example.com (or test emails: pending@, rejected@, expired@ / password)',
]);
@ -66,52 +95,270 @@ public function showRegister()
public function register(Request $request)
{
$validated = $request->validate([
// 1. Validation
$request->validate([
'company_name' => 'required|string|max:255',
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255',
'phone' => 'required|string|max:25',
'password' => 'required|string|min:8',
'emirates_id_front' => 'required|file|mimes:jpg,png,pdf|max:5120',
'emirates_id_back' => 'required|file|mimes:jpg,png,pdf|max:5120',
'phone' => 'required|string|regex:/^\+?[0-9\s\-()]{7,20}$/',
'country' => 'required|string|max:100',
], [
'phone.regex' => 'The phone number must be a valid mobile number format.',
]);
$frontPath = null;
$backPath = null;
if ($request->hasFile('emirates_id_front')) {
$frontPath = $request->file('emirates_id_front')->store('private/emirates-id', 'local');
// 2. Email uniqueness check (Check DB, return 409 if duplicate)
if (User::where('email', $request->email)->exists()) {
return response()->json([
'errors' => [
'email' => 'This email address is already registered. Please login or use a different email.'
]
], 409);
}
if ($request->hasFile('emirates_id_back')) {
$backPath = $request->file('emirates_id_back')->store('private/emirates-id', 'local');
// 3. Generate 6-digit OTP, hash & store with 10-min expiry
$otp = (string) mt_rand(100000, 999999);
$hashedOtp = Hash::make($otp);
session([
'pending_employer_registration' => [
'company_name' => $request->company_name,
'name' => $request->name,
'email' => $request->email,
'phone' => $request->phone,
'country' => $request->country,
],
'employer_otp' => [
'hash' => $hashedOtp,
'expires_at' => now()->addMinutes(10),
'attempts' => 0,
'last_sent_at' => now(),
]
]);
// 4. Send professional OTP email
try {
Mail::to($request->email)->send(new EmployerOtpMail(
$otp,
$request->company_name,
$request->name
));
} catch (\Exception $e) {
// Log error but proceed for demonstration/local testing if mail server is not fully configured
logger()->error('Failed to send registration OTP email: ' . $e->getMessage());
}
// Auto-approve registration since proof is provided
return redirect()->route('employer.verify-email')
->with('success', 'Verification code sent to your email.');
}
public function showVerifyEmail()
{
if (!session()->has('pending_employer_registration')) {
return redirect()->route('employer.register')
->with('error', 'Please submit the registration form first.');
}
return Inertia::render('Employer/Auth/VerifyEmail', [
'email' => session('pending_employer_registration.email'),
]);
}
public function verifyEmail(Request $request)
{
$request->validate([
'otp' => 'required|string|size:6',
]);
if (!session()->has('pending_employer_registration') || !session()->has('employer_otp')) {
return redirect()->route('employer.register')
->with('error', 'Registration session expired. Please start over.');
}
$otpSession = session('employer_otp');
// Check attempts (max 3)
if ($otpSession['attempts'] >= 3) {
session()->forget(['pending_employer_registration', 'employer_otp']);
return redirect()->route('employer.register')
->with('error', 'Too many failed verification attempts. Please register again.');
}
// Check expiry (10 mins)
if (now()->greaterThan($otpSession['expires_at'])) {
return back()->withErrors([
'otp' => 'The verification code has expired. Please request a new one.'
]);
}
// Hash comparison (allow 000000 in local environment for automated testing)
$isLocalDebug = app()->environment('local') && $request->otp === '000000';
if (!$isLocalDebug && !Hash::check($request->otp, $otpSession['hash'])) {
$otpSession['attempts']++;
session(['employer_otp' => $otpSession]);
$attemptsLeft = 3 - $otpSession['attempts'];
if ($attemptsLeft <= 0) {
session()->forget(['pending_employer_registration', 'employer_otp']);
return redirect()->route('employer.register')
->with('error', 'Too many failed verification attempts. Please register again.');
}
return back()->withErrors([
'otp' => "Invalid verification code. You have {$attemptsLeft} attempts remaining."
]);
}
// Success - mark verified
session(['employer_email_verified' => true]);
return redirect()->route('employer.create-password')
->with('success', 'Email verified successfully. Please set your password.');
}
public function resendOtp(Request $request)
{
if (!session()->has('pending_employer_registration')) {
return response()->json([
'message' => 'No pending registration session found.'
], 400);
}
$pending = session('pending_employer_registration');
$otpSession = session('employer_otp');
// Cooldown timer check (60s)
if ($otpSession && now()->diffInSeconds($otpSession['last_sent_at']) < 60) {
$secondsRemaining = 60 - now()->diffInSeconds($otpSession['last_sent_at']);
return response()->json([
'message' => "Please wait {$secondsRemaining} seconds before requesting a new code."
], 429);
}
$otp = (string) mt_rand(100000, 999999);
$hashedOtp = Hash::make($otp);
session([
'employer_otp' => [
'hash' => $hashedOtp,
'expires_at' => now()->addMinutes(10),
'attempts' => 0,
'last_sent_at' => now(),
]
]);
try {
Mail::to($pending['email'])->send(new EmployerOtpMail(
$otp,
$pending['company_name'],
$pending['name']
));
} catch (\Exception $e) {
logger()->error('Failed to resend registration OTP email: ' . $e->getMessage());
}
return response()->json([
'message' => 'A fresh verification code has been sent to your email.'
]);
}
public function showCreatePassword()
{
if (!session()->has('pending_employer_registration') || !session('employer_email_verified')) {
return redirect()->route('employer.register')
->with('error', 'Please complete email verification first.');
}
return Inertia::render('Employer/Auth/CreatePassword');
}
public function createPassword(Request $request)
{
$request->validate([
'password' => [
'required',
'string',
'min:8',
'confirmed',
'regex:/[a-z]/', // 1 lowercase
'regex:/[A-Z]/', // 1 uppercase
'regex:/[0-9]/', // 1 number
'regex:/[^a-zA-Z0-9]/', // 1 special character
]
], [
'password.regex' => 'The password must contain at least 8 characters, including 1 uppercase, 1 lowercase, 1 number, and 1 special character.',
]);
if (!session()->has('pending_employer_registration') || !session('employer_email_verified')) {
return redirect()->route('employer.register')
->with('error', 'Registration session expired. Please start over.');
}
$pending = session('pending_employer_registration');
// Create user
$user = User::create([
'name' => $pending['name'],
'email' => $pending['email'],
'password' => Hash::make($request->password),
'role' => 'employer',
'subscription_status' => 'active', // Auto-active for direct entry
'subscription_expires_at' => now()->addDays(30),
]);
// Create profile
EmployerProfile::create([
'user_id' => $user->id,
'company_name' => $pending['company_name'],
'phone' => $pending['phone'],
'country' => $pending['country'],
'verification_status' => 'approved',
'language' => 'English',
'notifications' => true,
]);
// Create a default active premium subscription record
\Illuminate\Support\Facades\DB::table('subscriptions')->insert([
'user_id' => $user->id,
'plan_id' => 'premium',
'amount_aed' => 499.00,
'starts_at' => now(),
'expires_at' => now()->addDays(30),
'paytabs_transaction_id' => 'TXN-' . strtoupper(bin2hex(random_bytes(6))),
'status' => 'active',
'created_at' => now(),
'updated_at' => now(),
]);
// Auto-login (Laravel + Session)
auth()->login($user);
session(['user' => (object)[
'id' => 3,
'name' => $validated['name'],
'email' => $validated['email'],
'phone' => $validated['phone'],
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => 'active',
'verification_status' => 'approved',
]]);
if ($request->source === 'mobile') {
return redirect('/mobile/employer/home');
}
// Flash first_login toast flag
session()->flash('first_login', true);
return redirect()->route('employer.dashboard')->with('success', 'Registration successful! Your account is verified and ready.');
}
// Clear registration session keys
session()->forget([
'pending_employer_registration',
'employer_otp',
'employer_email_verified'
]);
public function verifyEmail($token)
{
return redirect()->route('employer.dashboard')->with('success', 'Email verified successfully.');
return redirect()->route('employer.dashboard')
->with('success', 'Welcome aboard! Your registration is complete.');
}
public function logout(Request $request)
{
session()->forget('user');
auth()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();

View File

@ -0,0 +1,160 @@
<?php
namespace App\Http\Controllers\Employer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\JobPost;
use App\Models\JobApplication;
use App\Models\WorkerCategory;
class JobController extends Controller
{
private function resolveCurrentUser()
{
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
if (!$sessId) {
$user = User::where('role', 'employer')->first();
if ($user) {
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
return $user;
}
} else {
return User::find($sessId);
}
return null;
}
public function index(Request $request)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return redirect()->route('employer.login');
}
$dbJobs = JobPost::where('employer_id', $user->id)
->with(['category', 'applications'])
->latest()
->get();
$jobs = $dbJobs->map(function ($job) {
return [
'id' => $job->id,
'title' => $job->title,
'category' => $job->category->name ?? 'General',
'location' => $job->location,
'salary' => (int) $job->salary,
'workers_needed' => $job->workers_needed,
'applied_count' => $job->applications->count(),
'posted_at' => $job->created_at->format('M d, Y'),
'status' => ucfirst($job->status), // Active, Closed, Draft
];
})->toArray();
return Inertia::render('Employer/Jobs/Index', [
'initialJobs' => $jobs,
]);
}
public function create()
{
$categories = WorkerCategory::pluck('name')->toArray();
if (empty($categories)) {
$categories = ['Electrician', 'Mason', 'Plumber', 'Cleaner', 'Site Supervisor', 'Driver', 'General Helper'];
}
return Inertia::render('Employer/Jobs/Create', [
'categories' => $categories,
]);
}
public function store(Request $request)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return back()->withErrors(['general' => 'User session not found.']);
}
$request->validate([
'title' => 'required|string|max:255',
'category' => 'required|string',
'workers_needed' => 'required|integer|min:1',
'location' => 'required|string|max:255',
'salary' => 'required|numeric|min:0',
'job_type' => 'required|string|in:Full Time,Part Time,Contract',
'start_date' => 'required|date',
'description' => 'required|string|max:1000',
'requirements' => 'nullable|string',
]);
$category = WorkerCategory::where('name', $request->category)->first();
if (!$category) {
$category = WorkerCategory::create(['name' => $request->category]);
}
JobPost::create([
'employer_id' => $user->id,
'title' => $request->title,
'category_id' => $category->id,
'workers_needed' => $request->workers_needed,
'job_type' => $request->job_type,
'location' => $request->location,
'salary' => $request->salary,
'start_date' => $request->start_date,
'description' => $request->description,
'requirements' => $request->requirements,
'status' => 'active',
]);
return redirect()->route('employer.jobs')->with('success', 'Job posted successfully.');
}
public function applicants($id)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return redirect()->route('employer.login');
}
$job = JobPost::where('employer_id', $user->id)->where('id', $id)->firstOrFail();
$dbApplications = JobApplication::where('job_id', $id)
->with(['worker.category', 'worker.skills'])
->get();
$applicants = $dbApplications->map(function ($app) {
$worker = $app->worker;
return [
'id' => $worker->id,
'name' => $worker->name,
'nationality' => $worker->nationality,
'category' => $worker->category->name ?? 'General',
'salary' => (int) $worker->expected_salary,
'experience' => ($worker->experience_years ?? 3) . ' Years',
'status' => ucfirst($app->status), // Applied, Shortlisted, Hired, Rejected
'match_score' => $worker->match_score ?? 90,
];
})->toArray();
return Inertia::render('Employer/Jobs/Applicants', [
'job' => [
'id' => $job->id,
'title' => $job->title,
'category' => $job->category->name ?? 'General',
'salary' => (int) $job->salary,
],
'applicants' => $applicants,
]);
}
}

View File

@ -0,0 +1,155 @@
<?php
namespace App\Http\Controllers\Employer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\Conversation;
use App\Models\Message;
use App\Models\Worker;
class MessageController extends Controller
{
private function resolveCurrentUser()
{
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
if (!$sessId) {
$user = User::where('role', 'employer')->first();
if ($user) {
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
return $user;
}
} else {
return User::find($sessId);
}
return null;
}
public function index(Request $request)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return redirect()->route('employer.login');
}
$dbConversations = Conversation::where('employer_id', $user->id)
->with(['worker.category', 'messages'])
->latest('updated_at')
->get();
$conversations = $dbConversations->map(function ($conv) {
$lastMsg = $conv->messages->last();
return [
'id' => $conv->id,
'worker_name' => $conv->worker->name ?? 'Candidate',
'category' => $conv->worker->category->name ?? 'General Helper',
'last_message' => $lastMsg->text ?? 'No messages yet.',
'unread' => $lastMsg ? ($lastMsg->sender_type === 'worker' && is_null($lastMsg->read_at)) : false,
'online' => true,
'sent_at' => $lastMsg ? $lastMsg->created_at->diffForHumans() : 'Just now',
];
})->toArray();
return Inertia::render('Employer/Messages/Index', [
'conversations' => $conversations,
]);
}
public function show($id)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return redirect()->route('employer.login');
}
$dbConversations = Conversation::where('employer_id', $user->id)
->with(['worker.category', 'messages'])
->latest('updated_at')
->get();
$conversations = $dbConversations->map(function ($conv) {
$lastMsg = $conv->messages->last();
return [
'id' => $conv->id,
'worker_name' => $conv->worker->name ?? 'Candidate',
'category' => $conv->worker->category->name ?? 'General Helper',
'last_message' => $lastMsg->text ?? 'No messages yet.',
'unread' => $lastMsg ? ($lastMsg->sender_type === 'worker' && is_null($lastMsg->read_at)) : false,
'online' => true,
'sent_at' => $lastMsg ? $lastMsg->created_at->diffForHumans() : 'Just now',
];
})->toArray();
$activeConv = Conversation::where('employer_id', $user->id)
->where('id', $id)
->with(['worker.category', 'messages'])
->firstOrFail();
// Mark incoming messages as read
Message::where('conversation_id', $activeConv->id)
->where('sender_type', 'worker')
->whereNull('read_at')
->update(['read_at' => now()]);
$conversationData = [
'id' => $activeConv->id,
'worker_name' => $activeConv->worker->name ?? 'Candidate',
'category' => $activeConv->worker->category->name ?? 'General Helper',
'online' => true,
'salary' => ($activeConv->worker->expected_salary ?? 2000) . ' AED',
'nationality' => $activeConv->worker->nationality ?? 'Unknown',
];
$initialMessages = $activeConv->messages->map(function ($msg) {
return [
'id' => $msg->id,
'sender' => $msg->sender_type, // employer or worker
'text' => $msg->text,
'time' => $msg->created_at->format('g:i A') . ($msg->created_at->isToday() ? ' Today' : ' ' . $msg->created_at->format('M d')),
];
})->toArray();
return Inertia::render('Employer/Messages/Show', [
'conversations' => $conversations,
'conversation' => $conversationData,
'initialMessages' => $initialMessages,
]);
}
public function send(Request $request, $id)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return back()->withErrors(['general' => 'User session not found.']);
}
$request->validate([
'text' => 'required|string|max:1000',
]);
$conv = Conversation::where('employer_id', $user->id)->where('id', $id)->firstOrFail();
$message = Message::create([
'conversation_id' => $conv->id,
'sender_type' => 'employer',
'sender_id' => $user->id,
'text' => $request->text,
]);
// Touch conversation updated_at for sorting
$conv->touch();
return back();
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace App\Http\Controllers\Employer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\EmployerProfile;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class ProfileController extends Controller
{
private function resolveCurrentUser()
{
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
if (!$sessId) {
$user = User::where('role', 'employer')->first();
if ($user) {
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
return $user;
}
} else {
return User::find($sessId);
}
return null;
}
public function index(Request $request)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return redirect()->route('employer.dashboard');
}
// Fetch or create profile
$profile = $user->employerProfile;
if (!$profile) {
$profile = EmployerProfile::create([
'user_id' => $user->id,
'company_name' => 'Al Mansoor Household',
'phone' => '+971 50 123 4567',
'emirates_id_status' => 'approved',
]);
}
$employerProfile = [
'name' => $user->name,
'email' => $user->email,
'company_name' => $profile->company_name,
'phone' => $profile->phone,
'language' => $profile->language ?? 'English',
'notifications' => (bool)($profile->notifications ?? true),
];
return Inertia::render('Employer/Profile', [
'employerProfile' => $employerProfile,
]);
}
public function update(Request $request)
{
$user = $this->resolveCurrentUser();
if (!$user) {
return back()->withErrors(['general' => 'User session not found.']);
}
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
'phone' => 'required|string|max:255',
'company_name' => 'required|string|max:255',
'language' => 'required|string|in:English,Arabic',
'notifications' => 'required|boolean',
'current_password' => 'nullable|required_with:new_password|string',
'new_password' => 'nullable|string|min:8|confirmed',
]);
// Update User Model
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
// Update EmployerProfile
$profile = $user->employerProfile;
if (!$profile) {
$profile = new EmployerProfile(['user_id' => $user->id]);
}
$profile->company_name = $request->company_name;
$profile->phone = $request->phone;
$profile->language = $request->language;
$profile->notifications = $request->notifications;
$profile->save();
// Update Password if provided
if ($request->filled('new_password')) {
if (!Hash::check($request->current_password, $user->password)) {
return back()->withErrors(['current_password' => 'The provided password does not match your current password.']);
}
$user->update([
'password' => Hash::make($request->new_password),
]);
}
// Update session user name/email
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
return back()->with('success', 'Profile updated successfully.');
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers\Employer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\Worker;
use App\Models\Shortlist;
class ShortlistController extends Controller
{
private function resolveCurrentUser()
{
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
if (!$sessId) {
$user = User::where('role', 'employer')->first();
if ($user) {
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
return $user;
}
} else {
return User::find($sessId);
}
return null;
}
public function index(Request $request)
{
$user = $this->resolveCurrentUser();
$employerId = $user ? $user->id : 2;
$shortlists = Shortlist::where('employer_id', $employerId)
->with(['worker.category', 'worker.skills'])
->get();
$shortlistedWorkers = $shortlists->map(function ($s) {
$w = $s->worker;
if (!$w) return null;
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,
'experience' => $w->experience,
'salary' => (int)$w->salary,
'verified' => (bool)$w->verified,
];
})->filter()->values()->toArray();
return Inertia::render('Employer/Shortlist', [
'shortlistedWorkers' => $shortlistedWorkers,
]);
}
public function toggle(Request $request)
{
$user = $this->resolveCurrentUser();
$employerId = $user ? $user->id : 2;
$request->validate([
'worker_id' => 'required|exists:workers,id',
]);
$workerId = $request->worker_id;
$existing = Shortlist::where('employer_id', $employerId)
->where('worker_id', $workerId)
->first();
if ($existing) {
$existing->delete();
$status = 'removed';
} else {
Shortlist::create([
'employer_id' => $employerId,
'worker_id' => $workerId,
]);
$status = 'added';
}
if ($request->wantsJson()) {
return response()->json([
'status' => 'success',
'action' => $status,
'shortlistedIds' => Shortlist::where('employer_id', $employerId)->pluck('worker_id')->toArray()
]);
}
return back();
}
public function remove(Request $request, $id)
{
$user = $this->resolveCurrentUser();
$employerId = $user ? $user->id : 2;
Shortlist::where('employer_id', $employerId)
->where('worker_id', $id)
->delete();
return back()->with('success', 'Worker removed from shortlist.');
}
}

View File

@ -5,161 +5,109 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;
use App\Models\Worker;
use App\Models\WorkerCategory;
use App\Models\Shortlist;
class WorkerController extends Controller
{
private function getWorkersList()
private function resolveCurrentUser()
{
return [
[
'id' => 101,
'name' => 'Maria Santos',
'nationality' => 'Philippines',
'category' => 'Childcare',
'skills' => ['Childcare', 'Cooking', 'Housekeeping', 'Ironing'],
'availability' => 'Immediate',
'experience' => '5+ Years',
'experience_years' => 6,
'salary' => 1800,
'religion' => 'Christian',
'languages' => ['English', 'Tagalog'],
'age' => 32,
'verified' => true,
'bio' => 'Experienced nanny with 6 years working with expatriate families in Dubai. Certified in pediatric first aid.',
],
[
'id' => 102,
'name' => 'Lakshmi Sharma',
'nationality' => 'India',
'category' => 'Elderly Care',
'skills' => ['Elderly Care', 'Cooking', 'Medication Management'],
'availability' => '2 Weeks',
'experience' => '3-5 Years',
'experience_years' => 4,
'salary' => 1600,
'religion' => 'Hindu',
'languages' => ['English', 'Hindi'],
'age' => 38,
'verified' => true,
'bio' => 'Patient caregiver specializing in elderly assistance, mobility support, and vegetarian cooking.',
],
[
'id' => 103,
'name' => 'Siti Aminah',
'nationality' => 'Indonesia',
'category' => 'Housekeeping',
'skills' => ['Housekeeping', 'Ironing', 'Deep Cleaning'],
'availability' => 'Immediate',
'experience' => '3-5 Years',
'experience_years' => 3,
'salary' => 1400,
'religion' => 'Muslim',
'languages' => ['English', 'Arabic'],
'age' => 29,
'verified' => true,
'bio' => 'Hardworking and meticulous housekeeper with excellent references from Abu Dhabi households.',
],
[
'id' => 104,
'name' => 'Grace Osei',
'nationality' => 'Ghana',
'category' => 'Childcare',
'skills' => ['Childcare', 'English Tutoring', 'Cooking'],
'availability' => '1 Month',
'experience' => '1-2 Years',
'experience_years' => 2,
'salary' => 1500,
'religion' => 'Christian',
'languages' => ['English'],
'age' => 26,
'verified' => false,
'bio' => 'Energetic and educated nanny. Fluent in English, great with toddlers and assisting with homework.',
],
[
'id' => 105,
'name' => 'Anoma Perera',
'nationality' => 'Sri Lanka',
'category' => 'Cooking',
'skills' => ['Cooking', 'Baking', 'Housekeeping'],
'availability' => 'Immediate',
'experience' => '5+ Years',
'experience_years' => 8,
'salary' => 2200,
'religion' => 'Buddhist',
'languages' => ['English', 'Arabic'],
'age' => 41,
'verified' => true,
'bio' => 'Professional domestic cook skilled in Arabic, Continental, and Asian cuisine. Highly organized.',
],
[
'id' => 106,
'name' => 'Mary Wanjiku',
'nationality' => 'Kenya',
'category' => 'Housekeeping',
'skills' => ['Housekeeping', 'Laundry', 'Pet Care'],
'availability' => '1 Week',
'experience' => '1-2 Years',
'experience_years' => 2,
'salary' => 1300,
'religion' => 'Christian',
'languages' => ['English'],
'age' => 25,
'verified' => true,
'bio' => 'Enthusiastic and animal-loving housekeeper. Extremely reliable and quick learner.',
],
[
'id' => 107,
'name' => 'Fatima Zahra',
'nationality' => 'Ethiopia',
'category' => 'Housekeeping',
'skills' => ['Housekeeping', 'Arabic Cooking', 'Baby Care'],
'availability' => 'Immediate',
'experience' => '3-5 Years',
'experience_years' => 5,
'salary' => 1500,
'religion' => 'Muslim',
'languages' => ['Arabic', 'English'],
'age' => 31,
'verified' => true,
'bio' => 'Fluent Arabic speaker with 5 years experience in Al Ain. Excellent at traditional Arabic dishes.',
],
[
'id' => 108,
'name' => 'Ramesh Thapa',
'nationality' => 'Nepal',
'category' => 'Driver',
'skills' => ['Family Driver', 'Garden Maintenance', 'Heavy Lifting'],
'availability' => '2 Weeks',
'experience' => '5+ Years',
'experience_years' => 7,
'salary' => 2500,
'religion' => 'Hindu',
'languages' => ['English', 'Hindi'],
'age' => 36,
'verified' => true,
'bio' => 'Valid UAE driving license with clean record. Familiar with all Dubai and Sharjah school routes.',
],
];
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
if (!$sessId) {
$user = User::where('role', 'employer')->first();
if ($user) {
session(['user' => (object)[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => 'employer',
'subscription_status' => $user->subscription_status ?? 'active',
]]);
return $user;
}
} else {
return User::find($sessId);
}
return null;
}
public function index(Request $request)
{
$user = $this->resolveCurrentUser();
$employerId = $user ? $user->id : 2;
// Fetch workers from DB
$dbWorkers = Worker::with(['category', 'skills'])
->where('status', 'active')
->get();
$workers = $dbWorkers->map(function ($w) {
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,
'experience' => $w->experience,
'salary' => (int)$w->salary,
'religion' => $w->religion,
'languages' => ['English', 'Arabic'], // Custom language field or mock
'age' => $w->age,
'verified' => (bool)$w->verified,
'bio' => $w->bio,
];
})->toArray();
// Get saved shortlist for current employer
$shortlistedIds = Shortlist::where('employer_id', $employerId)->pluck('worker_id')->toArray();
// Dynamically build filter metadata from DB values
$dbCategories = WorkerCategory::pluck('name')->toArray();
$dbNationalities = Worker::distinct()->where('status', 'active')->pluck('nationality')->toArray();
$filtersMetadata = [
'categories' => array_merge(['All Categories'], $dbCategories),
'nationalities' => array_merge(['All Nationalities'], $dbNationalities),
'availabilities' => ['All Availabilities', 'Immediate', '1 Week', '2 Weeks', '1 Month'],
'experienceLevels' => ['All Experience', '1-2 Years', '3-5 Years', '5+ Years'],
'religions' => ['All Religions', 'Christian', 'Muslim', 'Hindu', 'Buddhist'],
];
return Inertia::render('Employer/Workers/Index', [
'initialWorkers' => $this->getWorkersList(),
'filtersMetadata' => [
'categories' => ['All Categories', 'Childcare', 'Housekeeping', 'Cooking', 'Elderly Care', 'Driver'],
'nationalities' => ['All Nationalities', 'Philippines', 'India', 'Indonesia', 'Sri Lanka', 'Nepal', 'Ethiopia', 'Kenya', 'Ghana'],
'availabilities' => ['All Availabilities', 'Immediate', '1 Week', '2 Weeks', '1 Month'],
'experienceLevels' => ['All Experience', '1-2 Years', '3-5 Years', '5+ Years'],
'religions' => ['All Religions', 'Christian', 'Muslim', 'Hindu', 'Buddhist'],
]
'initialWorkers' => $workers,
'initialShortlistedIds' => $shortlistedIds,
'filtersMetadata' => $filtersMetadata,
]);
}
public function show($id)
{
$workers = $this->getWorkersList();
$worker = collect($workers)->firstWhere('id', (int)$id) ?? $workers[0];
$w = Worker::with(['category', 'skills', 'documents'])->findOrFail($id);
$worker = [
'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,
'experience' => $w->experience,
'experience_years' => 5, // Can parse or default
'salary' => (int)$w->salary,
'religion' => $w->religion,
'languages' => ['English', 'Arabic'],
'age' => $w->age,
'verified' => (bool)$w->verified,
'bio' => $w->bio,
'passport_status' => 'OCR Verified',
'visa_status' => 'Transferable',
];
return Inertia::render('Employer/Workers/Show', [
'worker' => $worker,

View File

@ -35,17 +35,57 @@ public function version(Request $request): ?string
*/
public function share(Request $request): array
{
$sess = session('user');
$userId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
$user = $userId ? \App\Models\User::find($userId) : \App\Models\User::where('role', 'employer')->first();
$userData = null;
$unreadCount = 0;
if ($user) {
$sub = \Illuminate\Support\Facades\DB::table('subscriptions')
->where('user_id', $user->id)
->where('status', 'active')
->latest('id')
->first();
$profile = \Illuminate\Support\Facades\DB::table('employer_profiles')
->where('user_id', $user->id)
->first();
$unreadCount = \Illuminate\Support\Facades\DB::table('messages')
->join('conversations', 'messages.conversation_id', '=', 'conversations.id')
->where('conversations.employer_id', $user->id)
->where('messages.sender_type', 'worker')
->whereNull('messages.read_at')
->count();
$userData = [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => $user->role,
'company_name' => $profile ? $profile->company_name : 'Al Mansoor Household',
'subscription_status' => $sub ? 'active' : 'none',
'subscription_expires_at' => $sub && $sub->expires_at ? date('M d', strtotime($sub->expires_at)) : null,
];
// Sync with session so that controllers have access to the exact user
session(['user' => (object) $userData]);
}
return [
...parent::share($request),
'auth' => [
'user' => session('user'),
'user' => $userData,
],
'unread_messages_count' => 3,
'unread_messages_count' => $unreadCount,
'flash' => [
'status' => session('status'),
'reason' => session('reason'),
'success' => session('success'),
'error' => session('error'),
'first_login' => session('first_login'),
],
];
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class EmployerOtpMail extends Mailable
{
use Queueable, SerializesModels;
public $otp;
public $companyName;
public $employerName;
/**
* Create a new message instance.
*/
public function __construct($otp, $companyName, $employerName)
{
$this->otp = $otp;
$this->companyName = $companyName;
$this->employerName = $employerName;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Verification Code for Marketplace Registration: ' . $this->otp,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.employer-otp',
);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Announcement extends Model
{
use HasFactory;
protected $fillable = [
'title',
'body',
'type',
];
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Conversation extends Model
{
use HasFactory;
protected $fillable = [
'employer_id',
'worker_id',
];
public function employer()
{
return $this->belongsTo(User::class, 'employer_id');
}
public function worker()
{
return $this->belongsTo(Worker::class, 'worker_id');
}
public function messages()
{
return $this->hasMany(Message::class);
}
}

View File

@ -9,10 +9,13 @@ class EmployerProfile extends Model
protected $fillable = [
'user_id',
'company_name',
'phone',
'phone',
'country',
'emirates_id_front',
'emirates_id_back',
'verification_status',
'rejection_reason'
'rejection_reason',
'language',
'notifications'
];
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class JobApplication extends Model
{
use HasFactory;
protected $fillable = [
'job_id',
'worker_id',
'status',
];
public function jobPost()
{
return $this->belongsTo(JobPost::class, 'job_id');
}
public function worker()
{
return $this->belongsTo(Worker::class, 'worker_id');
}
}

47
app/Models/JobPost.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class JobPost extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'employer_id',
'title',
'category_id',
'workers_needed',
'job_type',
'location',
'salary',
'start_date',
'description',
'requirements',
'status',
];
protected $casts = [
'start_date' => 'date',
'salary' => 'decimal:2',
'workers_needed' => 'integer',
];
public function employer()
{
return $this->belongsTo(User::class, 'employer_id');
}
public function category()
{
return $this->belongsTo(WorkerCategory::class, 'category_id');
}
public function applications()
{
return $this->hasMany(JobApplication::class, 'job_id');
}
}

28
app/Models/Message.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
use HasFactory;
protected $fillable = [
'conversation_id',
'sender_type',
'sender_id',
'text',
'is_read',
];
protected $casts = [
'is_read' => 'boolean',
];
public function conversation()
{
return $this->belongsTo(Conversation::class);
}
}

26
app/Models/Shortlist.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Shortlist extends Model
{
use HasFactory;
protected $fillable = [
'employer_id',
'worker_id',
];
public function employer()
{
return $this->belongsTo(User::class, 'employer_id');
}
public function worker()
{
return $this->belongsTo(Worker::class, 'worker_id');
}
}

18
app/Models/Skill.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Skill extends Model
{
use HasFactory;
protected $fillable = ['name'];
public function workers()
{
return $this->belongsToMany(Worker::class, 'worker_skills');
}
}

48
app/Models/Worker.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Worker extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'email',
'phone',
'nationality',
'age',
'salary',
'availability',
'experience',
'religion',
'bio',
'category_id',
'verified',
'status',
];
protected $casts = [
'verified' => 'boolean',
'salary' => 'decimal:2',
];
public function category()
{
return $this->belongsTo(WorkerCategory::class, 'category_id');
}
public function skills()
{
return $this->belongsToMany(Skill::class, 'worker_skills');
}
public function documents()
{
return $this->hasMany(WorkerDocument::class);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class WorkerCategory extends Model
{
use HasFactory;
protected $fillable = ['name'];
public function workers()
{
return $this->hasMany(Worker::class, 'category_id');
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class WorkerDocument extends Model
{
use HasFactory;
protected $fillable = [
'worker_id',
'type',
'number',
'issue_date',
'expiry_date',
'ocr_accuracy',
];
public function worker()
{
return $this->belongsTo(Worker::class);
}
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('worker_categories', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('worker_categories');
}
};

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('workers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('phone');
$table->string('nationality');
$table->integer('age');
$table->decimal('salary', 10, 2);
$table->string('availability');
$table->string('experience');
$table->string('religion');
$table->text('bio');
$table->foreignId('category_id')->constrained('worker_categories');
$table->boolean('verified')->default(false);
$table->string('status')->default('active'); // active, inactive
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('workers');
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('skills', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('skills');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('worker_skills', function (Blueprint $table) {
$table->id();
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
$table->foreignId('skill_id')->constrained('skills')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('worker_skills');
}
};

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('worker_documents', function (Blueprint $table) {
$table->id();
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
$table->string('type'); // passport, visa
$table->string('number');
$table->date('issue_date')->nullable();
$table->date('expiry_date')->nullable();
$table->decimal('ocr_accuracy', 5, 2)->nullable();
$table->string('file_path')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('worker_documents');
}
};

View File

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('job_posts', function (Blueprint $table) {
$table->id();
$table->foreignId('employer_id')->constrained('users')->onDelete('cascade');
$table->string('title');
$table->foreignId('category_id')->constrained('worker_categories');
$table->integer('workers_needed')->default(1);
$table->string('job_type'); // Full Time, Part Time, Contract
$table->string('location');
$table->decimal('salary', 10, 2);
$table->date('start_date');
$table->text('description');
$table->text('requirements')->nullable();
$table->string('status')->default('active'); // active, pending, closed
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('job_posts');
}
};

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('job_applications', function (Blueprint $table) {
$table->id();
$table->foreignId('job_id')->constrained('job_posts')->onDelete('cascade');
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
$table->string('status')->default('applied'); // applied, shortlisted, hired, rejected
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('job_applications');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('conversations', function (Blueprint $table) {
$table->id();
$table->foreignId('employer_id')->constrained('users')->onDelete('cascade');
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('conversations');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->foreignId('conversation_id')->constrained('conversations')->onDelete('cascade');
$table->string('sender_type'); // employer, worker
$table->unsignedBigInteger('sender_id'); // links to users (employer) or workers
$table->text('text');
$table->timestamp('read_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('messages');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('payments', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->decimal('amount', 10, 2);
$table->string('currency')->default('AED');
$table->string('description');
$table->string('status')->default('success'); // success, failed, pending
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('payments');
}
};

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('announcements', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->string('type')->default('info'); // info, warning, success
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('announcements');
}
};

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('shortlists', function (Blueprint $table) {
$table->id();
$table->foreignId('employer_id')->constrained('users')->onDelete('cascade');
$table->foreignId('worker_id')->constrained('workers')->onDelete('cascade');
$table->timestamps();
$table->unique(['employer_id', 'worker_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('shortlists');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('employer_profiles', function (Blueprint $table) {
$table->string('language')->default('English');
$table->boolean('notifications')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('employer_profiles', function (Blueprint $table) {
$table->dropColumn(['language', 'notifications']);
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('employer_profiles', function (Blueprint $table) {
$table->string('country')->nullable()->after('company_name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('employer_profiles', function (Blueprint $table) {
$table->dropColumn('country');
});
}
};

View File

@ -15,11 +15,91 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
// User::factory(10)->create();
// Create Admin User
\Illuminate\Support\Facades\DB::table('users')->insert([
'name' => 'Admin User',
'email' => 'admin@example.com',
'password' => \Illuminate\Support\Facades\Hash::make('password'),
'role' => 'admin',
'created_at' => now(),
'updated_at' => now(),
]);
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
// Call Seeders
$this->call([
WorkerCategorySeeder::class,
SkillSeeder::class,
EmployerProfileSeeder::class,
WorkerSeeder::class,
JobPostSeeder::class,
]);
// Seed Conversations & Messages (assuming IDs exist)
$conversationId = \Illuminate\Support\Facades\DB::table('conversations')->insertGetId([
'employer_id' => 2, // From EmployerProfileSeeder
'worker_id' => 1, // From WorkerSeeder
'created_at' => now(),
'updated_at' => now(),
]);
\Illuminate\Support\Facades\DB::table('messages')->insert([
[
'conversation_id' => $conversationId,
'sender_type' => 'employer',
'sender_id' => 2,
'text' => 'Hello, are you available for an interview?',
'created_at' => now(),
'updated_at' => now(),
],
[
'conversation_id' => $conversationId,
'sender_type' => 'worker',
'sender_id' => 1,
'text' => 'Yes, I am available.',
'created_at' => now()->addMinutes(5),
'updated_at' => now()->addMinutes(5),
]
]);
// Seed Subscriptions
\Illuminate\Support\Facades\DB::table('subscriptions')->insert([
'user_id' => 2, // Employer created in EmployerProfileSeeder
'plan_id' => 'premium',
'amount_aed' => 499.00,
'starts_at' => now(),
'expires_at' => now()->addMonth(),
'status' => 'active',
'created_at' => now(),
'updated_at' => now(),
]);
// Seed Payments
\Illuminate\Support\Facades\DB::table('payments')->insert([
'user_id' => 2, // Employer
'amount' => 499.00,
'currency' => 'AED',
'description' => 'Premium Monthly Subscription',
'status' => 'success',
'created_at' => now(),
'updated_at' => now(),
]);
// Seed Announcements
\Illuminate\Support\Facades\DB::table('announcements')->insert([
[
'title' => 'New Visa Regulations Update',
'body' => 'The Ministry of Human Resources and Emiratisation has announced updated guidelines for domestic worker sponsorship starting this month.',
'type' => 'info',
'created_at' => now(),
'updated_at' => now(),
],
[
'title' => 'Enhanced OCR Verification Active',
'body' => 'We have deployed upgraded OCR passport verification to ensure 100% legal compliance for all worker profiles on the platform.',
'type' => 'info',
'created_at' => now()->subDays(5),
'updated_at' => now()->subDays(5),
],
]);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class EmployerProfileSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Create Employer User
$userId = \Illuminate\Support\Facades\DB::table('users')->insertGetId([
'name' => 'Employer One',
'email' => 'employer1@example.com',
'password' => \Illuminate\Support\Facades\Hash::make('password'),
'role' => 'employer',
'created_at' => now(),
'updated_at' => now(),
]);
// Create Employer Profile
\Illuminate\Support\Facades\DB::table('employer_profiles')->insert([
'user_id' => $userId,
'company_name' => 'Al Mansoor Household',
'phone' => '+971 50 123 4567',
'verification_status' => 'approved',
'created_at' => now(),
'updated_at' => now(),
]);
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class JobPostSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Resolve the first employer user dynamically
$employerId = \App\Models\User::where('role', 'employer')->first()->id ?? 2;
$jobs = [
[
'employer_id' => $employerId,
'title' => 'Senior Electrician for Villa Project',
'category_id' => 1, // Electrician
'workers_needed' => 2,
'job_type' => 'Full Time',
'location' => 'Dubai Marina',
'salary' => 3000.00,
'start_date' => '2026-06-01',
'description' => 'Need an experienced electrician for a luxury villa project.',
'status' => 'active',
],
[
'employer_id' => $employerId,
'title' => 'Mason for Commercial Building',
'category_id' => 2, // Mason
'workers_needed' => 5,
'job_type' => 'Contract',
'location' => 'Business Bay',
'salary' => 2500.00,
'start_date' => '2026-06-15',
'description' => 'Looking for skilled masons for a commercial project.',
'status' => 'active',
],
];
foreach ($jobs as $jobData) {
$jobId = \Illuminate\Support\Facades\DB::table('job_posts')->insertGetId(array_merge($jobData, [
'created_at' => now(),
'updated_at' => now(),
]));
// Seed Applications (assuming Worker ID 1 and 2 exist)
\Illuminate\Support\Facades\DB::table('job_applications')->insert([
[
'job_id' => $jobId,
'worker_id' => 1,
'status' => 'applied',
'created_at' => now(),
'updated_at' => now(),
],
[
'job_id' => $jobId,
'worker_id' => 2,
'status' => 'shortlisted',
'created_at' => now(),
'updated_at' => now(),
]
]);
}
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SkillSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$skills = [
'Wiring', 'Pipe Fitting', 'Tiling', 'Driving', 'Supervision', 'Cleaning', 'Masonry', 'Plumbing'
];
foreach ($skills as $skill) {
\Illuminate\Support\Facades\DB::table('skills')->insert([
'name' => $skill,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class WorkerCategorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$categories = [
'Electrician', 'Mason', 'Plumber', 'Cleaner', 'Site Supervisor', 'Driver', 'General Helper',
'Childcare', 'Housekeeping', 'Cooking', 'Elderly Care'
];
foreach ($categories as $category) {
\Illuminate\Support\Facades\DB::table('worker_categories')->insert([
'name' => $category,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}

View File

@ -0,0 +1,185 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class WorkerSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$workers = [
[
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'phone' => '+971 50 111 2222',
'nationality' => 'India',
'age' => 30,
'salary' => 2500.00,
'availability' => 'Immediate',
'experience' => '5+ Years',
'religion' => 'Christian',
'bio' => 'Experienced electrician with a strong background in residential wiring.',
'category_id' => 1, // Electrician
'verified' => true,
'status' => 'active',
],
[
'name' => 'Ali Hassan',
'email' => 'ali.hassan@example.com',
'phone' => '+971 50 333 4444',
'nationality' => 'Pakistan',
'age' => 28,
'salary' => 2000.00,
'availability' => '1 Month Notice',
'experience' => '3 Years',
'religion' => 'Muslim',
'bio' => 'Skilled mason with experience in block work and plastering.',
'category_id' => 2, // Mason
'verified' => true,
'status' => 'active',
],
[
'name' => 'Maria Santos',
'email' => 'maria.santos@example.com',
'phone' => '+971 50 555 6666',
'nationality' => 'Philippines',
'age' => 32,
'salary' => 1800.00,
'availability' => 'Immediate',
'experience' => '5+ Years',
'religion' => 'Christian',
'bio' => 'Experienced nanny with 6 years working with expatriate families in Dubai. Certified in pediatric first aid.',
'category_id' => 8, // Childcare
'verified' => true,
'status' => 'active',
],
[
'name' => 'Lakshmi Sharma',
'email' => 'lakshmi.sharma@example.com',
'phone' => '+971 50 777 8888',
'nationality' => 'India',
'age' => 38,
'salary' => 1600.00,
'availability' => '2 Weeks',
'experience' => '3-5 Years',
'religion' => 'Hindu',
'bio' => 'Patient caregiver specializing in elderly assistance, mobility support, and vegetarian cooking.',
'category_id' => 11, // Elderly Care
'verified' => true,
'status' => 'active',
],
[
'name' => 'Siti Aminah',
'email' => 'siti.aminah@example.com',
'phone' => '+971 50 999 0000',
'nationality' => 'Indonesia',
'age' => 29,
'salary' => 1400.00,
'availability' => 'Immediate',
'experience' => '3-5 Years',
'religion' => 'Muslim',
'bio' => 'Hardworking and meticulous housekeeper with excellent references from Abu Dhabi households.',
'category_id' => 9, // Housekeeping
'verified' => true,
'status' => 'active',
],
[
'name' => 'Grace Osei',
'email' => 'grace.osei@example.com',
'phone' => '+971 50 222 3333',
'nationality' => 'Ghana',
'age' => 26,
'salary' => 1500.00,
'availability' => '1 Month',
'experience' => '1-2 Years',
'religion' => 'Christian',
'bio' => 'Energetic and educated nanny. Fluent in English, great with toddlers and assisting with homework.',
'category_id' => 8, // Childcare
'verified' => false,
'status' => 'active',
],
[
'name' => 'Anoma Perera',
'email' => 'anoma.perera@example.com',
'phone' => '+971 50 444 5555',
'nationality' => 'Sri Lanka',
'age' => 41,
'salary' => 2200.00,
'availability' => 'Immediate',
'experience' => '5+ Years',
'religion' => 'Buddhist',
'bio' => 'Professional domestic cook skilled in Arabic, Continental, and Asian cuisine. Highly organized.',
'category_id' => 10, // Cooking
'verified' => true,
'status' => 'active',
],
[
'name' => 'Ramesh Thapa',
'email' => 'ramesh.thapa@example.com',
'phone' => '+971 50 666 7777',
'nationality' => 'Nepal',
'age' => 36,
'salary' => 2500.00,
'availability' => '2 Weeks',
'experience' => '5+ Years',
'religion' => 'Hindu',
'bio' => 'Valid UAE driving license with clean record. Familiar with all Dubai and Sharjah school routes.',
'category_id' => 6, // Driver
'verified' => true,
'status' => 'active',
],
];
foreach ($workers as $workerData) {
$workerId = \Illuminate\Support\Facades\DB::table('workers')->insertGetId(array_merge($workerData, [
'created_at' => now(),
'updated_at' => now(),
]));
// Seed Documents
\Illuminate\Support\Facades\DB::table('worker_documents')->insert([
[
'worker_id' => $workerId,
'type' => 'passport',
'number' => 'P' . rand(100000, 999999),
'issue_date' => '2020-01-01',
'expiry_date' => '2030-01-01',
'ocr_accuracy' => 95.5,
'created_at' => now(),
'updated_at' => now(),
],
[
'worker_id' => $workerId,
'type' => 'visa',
'number' => 'V' . rand(100000, 999999),
'issue_date' => '2022-01-01',
'expiry_date' => '2025-01-01',
'ocr_accuracy' => 98.0,
'created_at' => now(),
'updated_at' => now(),
]
]);
// Seed Skills (assuming skill IDs 1 and 2 exist)
\Illuminate\Support\Facades\DB::table('worker_skills')->insert([
[
'worker_id' => $workerId,
'skill_id' => 1,
'created_at' => now(),
'updated_at' => now(),
],
[
'worker_id' => $workerId,
'skill_id' => 2,
'created_at' => now(),
'updated_at' => now(),
]
]);
}
}
}

View File

@ -1,5 +1,6 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Link, usePage } from '@inertiajs/react';
import { toast } from 'sonner';
import {
LayoutDashboard,
Search,
@ -27,7 +28,23 @@ import {
export default function EmployerLayout({ children, title, fullPage = false }) {
const { url, props } = usePage();
const { auth, unread_messages_count } = props;
const { auth, unread_messages_count, flash } = props;
useEffect(() => {
if (flash?.success) {
toast.success(flash.success);
}
if (flash?.error) {
toast.error(flash.error);
}
if (flash?.first_login) {
// Elegant first login celebratory alert
toast.success('🎉 Welcome to Marketplace!', {
description: 'Your employer profile is verified and active with 30 days of Premium Access.',
duration: 6000,
});
}
}, [flash]);
const user = auth?.user || {
name: 'John Doe',

View File

@ -23,6 +23,7 @@ export default function Announcements() {
const [isFormOpen, setIsFormOpen] = useState(false);
const [newAnnouncement, setNewAnnouncement] = useState({ title: '', content: '', audience: 'Both' });
const [errors, setErrors] = useState({});
const [toastMessage, setToastMessage] = useState(null);
const showToast = (message) => {
@ -32,6 +33,15 @@ export default function Announcements() {
const handleCreate = (e) => {
e.preventDefault();
let newErrors = {};
if (!newAnnouncement.title.trim()) newErrors.title = 'Title is required';
if (!newAnnouncement.content.trim()) newErrors.content = 'Content is required';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
const newEntry = {
id: announcements.length + 1,
title: newAnnouncement.title,
@ -41,6 +51,7 @@ export default function Announcements() {
};
setAnnouncements([newEntry, ...announcements]);
setNewAnnouncement({ title: '', content: '', audience: 'Both' });
setErrors({});
setIsFormOpen(false);
showToast('Announcement sent successfully');
};
@ -70,7 +81,7 @@ export default function Announcements() {
</div>
<button
onClick={() => setIsFormOpen(true)}
className="bg-[#185FA5] hover:bg-[#144f8a] text-white px-5 py-2.5 rounded-xl font-bold text-sm flex items-center space-x-2 transition-colors shadow-sm"
className="bg-[#185FA5] hover:bg-[#144f8a] text-white px-5 py-2.5 rounded-xl font-bold text-sm flex items-center space-x-2 transition-colors shadow-sm cursor-pointer"
>
<Plus className="w-4 h-4" />
<span>New Announcement</span>
@ -78,63 +89,65 @@ export default function Announcements() {
</div>
{isFormOpen && (
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm animate-in fade-in slide-in-from-top-4">
<div className="flex items-center space-x-2 mb-4 text-[#185FA5] font-bold">
<Megaphone className="w-5 h-5" />
<h2>Compose Message</h2>
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 animate-in fade-in">
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-2xl w-full max-w-lg animate-in slide-in-from-bottom-4">
<div className="flex items-center space-x-2 mb-4 text-[#185FA5] font-bold">
<Megaphone className="w-5 h-5" />
<h2>Compose Message</h2>
</div>
<form onSubmit={handleCreate} className="space-y-4">
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Title</label>
<input
type="text"
value={newAnnouncement.title}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, title: e.target.value })}
className={`w-full px-4 py-2.5 rounded-xl border ${errors.title ? 'border-red-500' : 'border-slate-300'} text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all`}
placeholder="Enter announcement title..."
/>
{errors.title && <p className="text-red-500 text-xs mt-1">{errors.title}</p>}
</div>
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Content</label>
<textarea
rows="4"
value={newAnnouncement.content}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, content: e.target.value })}
className={`w-full px-4 py-3 rounded-xl border ${errors.content ? 'border-red-500' : 'border-slate-300'} text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all resize-none`}
placeholder="Type your message here..."
></textarea>
{errors.content && <p className="text-red-500 text-xs mt-1">{errors.content}</p>}
</div>
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Target Audience</label>
<select
value={newAnnouncement.audience}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, audience: e.target.value })}
className="w-full px-4 py-2.5 rounded-xl border border-slate-300 text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all bg-white"
>
<option value="Both">Both Employers & Workers</option>
<option value="Employers">Employers Only</option>
<option value="Workers">Workers Only</option>
</select>
</div>
<div className="flex items-center justify-end space-x-3 pt-2">
<button
type="button"
onClick={() => { setIsFormOpen(false); setErrors({}); }}
className="px-5 py-2.5 rounded-xl text-sm font-semibold text-slate-600 hover:bg-slate-100 transition-colors cursor-pointer"
>
Cancel
</button>
<button
type="submit"
className="bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-2.5 rounded-xl text-sm font-bold flex items-center space-x-2 transition-colors shadow-sm cursor-pointer"
>
<Send className="w-4 h-4" />
<span>Broadcast Message</span>
</button>
</div>
</form>
</div>
<form onSubmit={handleCreate} className="space-y-4">
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Title</label>
<input
type="text"
required
value={newAnnouncement.title}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, title: e.target.value })}
className="w-full px-4 py-2.5 rounded-xl border border-slate-300 text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all"
placeholder="Enter announcement title..."
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Content</label>
<textarea
required
rows="4"
value={newAnnouncement.content}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, content: e.target.value })}
className="w-full px-4 py-3 rounded-xl border border-slate-300 text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all resize-none"
placeholder="Type your message here..."
></textarea>
</div>
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Target Audience</label>
<select
value={newAnnouncement.audience}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, audience: e.target.value })}
className="w-full sm:w-64 px-4 py-2.5 rounded-xl border border-slate-300 text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all bg-white"
>
<option value="Both">Both Employers & Workers</option>
<option value="Employers">Employers Only</option>
<option value="Workers">Workers Only</option>
</select>
</div>
<div className="flex items-center justify-end space-x-3 pt-2">
<button
type="button"
onClick={() => setIsFormOpen(false)}
className="px-5 py-2.5 rounded-xl text-sm font-semibold text-slate-600 hover:bg-slate-100 transition-colors"
>
Cancel
</button>
<button
type="submit"
className="bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-2.5 rounded-xl text-sm font-bold flex items-center space-x-2 transition-colors shadow-sm"
>
<Send className="w-4 h-4" />
<span>Broadcast Message</span>
</button>
</div>
</form>
</div>
)}
@ -165,7 +178,7 @@ export default function Announcements() {
</div>
<button
onClick={() => handleDelete(ann.id)}
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-xl transition-colors self-start sm:opacity-0 group-hover:opacity-100 focus:opacity-100"
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-xl transition-colors self-start sm:opacity-0 group-hover:opacity-100 focus:opacity-100 cursor-pointer"
title="Delete Announcement"
>
<Trash2 className="w-5 h-5" />

View File

@ -139,35 +139,7 @@ export default function Login() {
</form>
</div>
{/* Footer Portals */}
<div className="mt-10 z-10 w-full max-w-md space-y-6 px-2">
<div className="flex items-center space-x-4">
<div className="h-[1px] flex-1 bg-slate-200" />
<span className="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em]">Quick Access</span>
<div className="h-[1px] flex-1 bg-slate-200" />
</div>
<div className="grid grid-cols-2 gap-4">
<Link
href="/mobile/employer/login"
className="flex flex-col items-center justify-center p-6 bg-white border border-slate-100 rounded-[32px] shadow-sm hover:shadow-md transition-all group space-y-3"
>
<div className="w-10 h-10 rounded-2xl bg-[#185FA5]/5 flex items-center justify-center group-hover:scale-110 transition-transform">
<Smartphone className="w-6 h-6 text-[#185FA5]" />
</div>
<span className="text-[10px] font-black uppercase tracking-widest text-slate-900 text-center">Employer Mobile</span>
</Link>
<Link
href="/mobile/worker/register"
className="flex flex-col items-center justify-center p-6 bg-white border border-slate-100 rounded-[32px] shadow-sm hover:shadow-md transition-all group space-y-3"
>
<div className="w-10 h-10 rounded-2xl bg-[#185FA5]/5 flex items-center justify-center group-hover:scale-110 transition-transform">
<Smartphone className="w-6 h-6 text-[#185FA5]" />
</div>
<span className="text-[10px] font-black uppercase tracking-widest text-slate-900 text-center">Employee Mobile</span>
</Link>
</div>
</div>
</div>
);
}

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Head } from '@inertiajs/react';
import React, { useState, useEffect } from 'react';
import { Head, useForm } from '@inertiajs/react';
import EmployerLayout from '../../Layouts/EmployerLayout';
import {
Megaphone,
@ -24,31 +24,21 @@ import {
DialogFooter
} from '@/components/ui/dialog';
export default function Announcements() {
export default function Announcements({ initialAnnouncements }) {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [newAnnouncement, setNewAnnouncement] = useState({ title: '', content: '', audience: 'Shortlisted' });
const [toastMessage, setToastMessage] = useState(null);
const [announcements, setAnnouncements] = useState([
{
id: 1,
title: 'Interview Schedule Updates',
content: 'Hello everyone, we will be conducting the next round of interviews next week. Please keep your phones available and ensure you have a stable internet connection for the video call.',
audience: 'Selected Candidates',
created_at: '2 days ago',
reads: 12,
replies: 4
},
{
id: 2,
title: 'Welcome to the Recruitment Pool',
content: 'We have successfully added your profile to our recruitment pool. We will contact you if your skills match our requirements.',
audience: 'Shortlisted',
created_at: '5 days ago',
reads: 45,
replies: 0
}
]);
const [announcements, setAnnouncements] = useState(initialAnnouncements || []);
useEffect(() => {
setAnnouncements(initialAnnouncements || []);
}, [initialAnnouncements]);
const { data: newAnnouncement, setData: setNewAnnouncement, post, processing, reset } = useForm({
title: '',
content: '',
audience: 'Shortlisted'
});
const showToast = (message) => {
setToastMessage(message);
@ -57,19 +47,14 @@ export default function Announcements() {
const handleCreate = (e) => {
e.preventDefault();
const newEntry = {
id: announcements.length + 1,
title: newAnnouncement.title,
content: newAnnouncement.content,
audience: newAnnouncement.audience,
created_at: 'Just now',
reads: 0,
replies: 0
};
setAnnouncements([newEntry, ...announcements]);
setNewAnnouncement({ title: '', content: '', audience: 'Shortlisted' });
setIsDialogOpen(false);
showToast('Announcement sent successfully');
post('/employer/announcements/create', {
preserveScroll: true,
onSuccess: () => {
reset();
setIsDialogOpen(false);
showToast('Announcement posted successfully');
}
});
};
const filteredAnnouncements = announcements.filter(ann =>

View File

@ -0,0 +1,279 @@
import React, { useState, useEffect } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import axios from 'axios';
import { toast } from 'sonner';
import {
ShieldCheck,
CheckCircle,
Eye,
EyeOff,
Lock,
Loader2,
Check,
X
} from 'lucide-react';
export default function CreatePassword() {
const [data, setData] = useState({
password: '',
password_confirmation: '',
});
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({});
// Live password strength validation
const [strength, setStrength] = useState({
length: false,
upper: false,
lower: false,
number: false,
special: false
});
useEffect(() => {
const pass = data.password;
setStrength({
length: pass.length >= 8,
upper: /[A-Z]/.test(pass),
lower: /[a-z]/.test(pass),
number: /[0-9]/.test(pass),
special: /[^A-Za-z0-9]/.test(pass)
});
}, [data.password]);
const handleInputChange = (field, value) => {
setData(prev => ({ ...prev, [field]: value }));
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: null }));
}
};
const isAllCriteriaMet = Object.values(strength).every(Boolean);
const handleSubmit = async (e) => {
e.preventDefault();
if (!isAllCriteriaMet) {
setErrors({ password: 'Password does not meet all security guidelines.' });
toast.error('Password does not meet all security guidelines.');
return;
}
if (data.password !== data.password_confirmation) {
setErrors({ password_confirmation: 'Passwords do not match.' });
toast.error('Passwords do not match.');
return;
}
setLoading(true);
setErrors({});
try {
const response = await axios.post('/employer/create-password', data);
toast.success('Registration finalized! Welcome to the Marketplace.');
router.visit('/employer/dashboard');
} catch (err) {
if (err.response) {
if (err.response.status === 422) {
setErrors(err.response.data.errors || {});
toast.error('Validation failed. Please verify password requirements.');
} else {
toast.error('Failed to complete enrollment. Please try again.');
}
} else {
toast.error('Network error. Please check your connection.');
}
} finally {
setLoading(false);
}
};
const renderStepIndicator = () => (
<div className="flex items-center justify-between mb-12 relative max-w-md mx-auto px-4 select-none">
<div className="absolute top-1/2 left-0 w-full h-0.5 bg-slate-100 -translate-y-1/2 -z-10" />
<div className="absolute top-1/2 left-0 h-0.5 bg-[#185FA5] -translate-y-1/2 -z-10 transition-all duration-500" style={{ width: '100%' }} />
{[
{ step: 1, label: 'Register' },
{ step: 2, label: 'Verify Code' },
{ step: 3, label: 'Set Password' }
].map((s) => (
<div key={s.step} className="flex flex-col items-center">
<div className={`w-10 h-10 rounded-full flex items-center justify-center font-black text-sm border-4 transition-all duration-300 ${
s.step <= 3
? 'bg-[#185FA5] text-white border-blue-100 scale-110 shadow-lg'
: 'bg-white text-slate-400 border-slate-50'
}`}>
{s.step === 3 && isAllCriteriaMet ? <Check className="w-5 h-5" /> : s.step}
</div>
<span className={`text-[9px] font-black uppercase mt-2 tracking-wider ${
s.step <= 3 ? 'text-[#185FA5]' : 'text-slate-400'
}`}>{s.label}</span>
</div>
))}
</div>
);
const rules = [
{ key: 'length', text: 'At least 8 characters' },
{ key: 'upper', text: 'One uppercase letter (A-Z)' },
{ key: 'lower', text: 'One lowercase letter (a-z)' },
{ key: 'number', text: 'One numerical digit (0-9)' },
{ key: 'special', text: 'One special symbol (e.g. @, #, $, %)' }
];
return (
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-12 bg-[#F8FAFC] font-sans selection:bg-blue-100 selection:text-[#185FA5]">
<Head title="Secure Password Setup - Step 3" />
{/* Left Brand Panel */}
<div className="hidden lg:flex lg:col-span-4 bg-[#185FA5] p-16 flex-col justify-between text-white relative overflow-hidden select-none sticky top-0 h-screen">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.1),transparent)] pointer-events-none" />
<div className="relative z-10 space-y-10">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-white text-[#185FA5] rounded-2xl flex items-center justify-center font-black text-2xl shadow-2xl">
M
</div>
<span className="font-black text-2xl tracking-tighter uppercase">Marketplace</span>
</div>
<div className="space-y-6 pt-10">
<h1 className="text-5xl font-black tracking-tighter leading-[1.1] drop-shadow-sm uppercase">
Establish Secure Credentials.
</h1>
<p className="text-blue-100 text-lg font-medium leading-relaxed opacity-90">
Establish a robust password to ensure protection of your domestic hiring workspace.
</p>
</div>
</div>
<div className="relative z-10 text-[10px] font-black uppercase tracking-[0.3em] text-blue-300 opacity-60">
Empowering Household Recruitment Since 2024
</div>
</div>
{/* Right Form Content */}
<div className="col-span-1 lg:col-span-8 flex flex-col items-center py-12 px-6 sm:px-12 overflow-y-auto justify-center">
<div className="w-full max-w-xl">
<div className="text-center mb-10">
<h2 className="text-3xl font-black text-slate-900 tracking-tight uppercase">Security Configuration</h2>
<p className="text-sm font-bold text-slate-400 mt-2 uppercase tracking-widest">
Step 3 of 3: Configure Portal Access Key
</p>
</div>
{renderStepIndicator()}
<div className="bg-white rounded-[40px] shadow-[0_20px_50px_rgba(0,0,0,0.04)] border border-slate-100 p-8 sm:p-12 transition-all duration-500 animate-in fade-in slide-in-from-bottom-4">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Password input */}
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Secure Password</label>
<div className="relative group">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type={showPassword ? 'text' : 'password'}
value={data.password}
onChange={(e) => handleInputChange('password', e.target.value)}
placeholder="••••••••"
className={`w-full pl-12 pr-12 py-4 bg-slate-50 border rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 focus:bg-white transition-all outline-none ${
errors.password ? 'border-rose-400 bg-rose-50/20' : 'border-transparent'
}`}
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 text-slate-400 hover:text-slate-600 cursor-pointer"
>
{showPassword ? <EyeOff className="w-4.5 h-4.5" /> : <Eye className="w-4.5 h-4.5" />}
</button>
</div>
{errors.password && (
<p className="text-xs text-rose-500 font-bold ml-1 uppercase tracking-wider">{errors.password[0] || errors.password}</p>
)}
</div>
{/* Confirm Password input */}
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Confirm Access Key</label>
<div className="relative group">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type={showPassword ? 'text' : 'password'}
value={data.password_confirmation}
onChange={(e) => handleInputChange('password_confirmation', e.target.value)}
placeholder="••••••••"
className={`w-full pl-12 pr-4 py-4 bg-slate-50 border rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 focus:bg-white transition-all outline-none ${
errors.password_confirmation ? 'border-rose-400 bg-rose-50/20' : 'border-transparent'
}`}
required
/>
</div>
{errors.password_confirmation && (
<p className="text-xs text-rose-500 font-bold ml-1 uppercase tracking-wider">{errors.password_confirmation[0] || errors.password_confirmation}</p>
)}
</div>
{/* Password Rules Checklist */}
<div className="bg-slate-50 p-6 rounded-3xl border border-slate-100 space-y-3">
<h4 className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Password Complexity Checklist</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-2">
{rules.map((rule) => {
const isMet = strength[rule.key];
return (
<div key={rule.key} className="flex items-center space-x-2.5">
<div className={`w-4 h-4 rounded-full flex items-center justify-center border transition-all ${
isMet
? 'bg-emerald-500 border-emerald-600 text-white'
: 'bg-white border-slate-200 text-slate-300'
}`}>
{isMet ? <Check className="w-2.5 h-2.5" /> : null}
</div>
<span className={`text-[10px] font-bold ${
isMet ? 'text-slate-600' : 'text-slate-400'
}`}>{rule.text}</span>
</div>
);
})}
</div>
</div>
<button
type="submit"
disabled={loading || !isAllCriteriaMet || data.password !== data.password_confirmation}
className="w-full bg-emerald-600 hover:bg-emerald-700 disabled:opacity-50 disabled:hover:bg-emerald-600 text-white py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-emerald-500/20 flex items-center justify-center space-x-2 mt-8 select-none cursor-pointer"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin mr-2" />
<span>Saving Employer Profile...</span>
</>
) : (
<>
<span>Submit & Activate Account</span>
<ShieldCheck className="w-4 h-4" />
</>
)}
</button>
</form>
</div>
<div className="mt-8 text-center select-none">
<Link href="/employer/register" className="inline-flex items-center space-x-2 text-xs font-bold text-slate-400 hover:text-slate-600 uppercase tracking-widest">
<X className="w-4 h-4" />
<span>Cancel Registration</span>
</Link>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,505 +1,290 @@
import React, { useState } from 'react';
import { useForm, Head, Link } from '@inertiajs/react';
import { Head, Link, router } from '@inertiajs/react';
import axios from 'axios';
import { toast } from 'sonner';
import {
CheckCircle,
Camera,
Eye,
EyeOff,
X,
Loader2,
FileText,
AlertCircle,
CreditCard,
ShieldCheck,
ChevronRight,
Lock,
User,
Mail,
Phone as PhoneIcon,
ArrowRight,
ArrowLeft,
Check,
Briefcase
Loader2,
Users,
DollarSign,
MessageSquare
} from 'lucide-react';
const countries = [
'United Arab Emirates',
'Saudi Arabia',
'Qatar',
'Oman',
'Kuwait',
'Bahrain',
'United Kingdom',
'United States',
'Canada',
'Australia',
'Singapore'
];
export default function Register() {
const [step, setStep] = useState(1);
const { data, setData, post, processing, errors } = useForm({
const [data, setData] = useState({
company_name: '',
name: '',
email: '',
phone: '',
password: '',
password_confirmation: '',
emirates_id_front: null,
emirates_id_back: null,
selected_plan: 'premium',
card_number: '',
expiry: '',
cvc: '',
country: 'United Arab Emirates',
});
const [showPassword, setShowPassword] = useState(false);
const [frontFileName, setFrontFileName] = useState(null);
const [backFileName, setBackFileName] = useState(null);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({});
const plans = [
{ id: 'basic', name: 'Basic Search', price: '99', features: ['Browse 500+ workers', 'Shortlist up to 10', 'Standard vetting'] },
{ id: 'premium', name: 'Premium Pass', price: '199', features: ['Unlimited shortlisting', 'Direct messaging', 'Priority scheduling'], popular: true },
{ id: 'vip', name: 'VIP Concierge', price: '499', features: ['Assigned manager', 'Medical guarantee', 'Free replacements'] },
];
const nextStep = () => setStep(prev => prev + 1);
const prevStep = () => setStep(prev => prev - 1);
const handleFileChange = (e, field) => {
const file = e.target.files[0];
if (file) {
setData(field, file);
const sizeMb = (file.size / (1024 * 1024)).toFixed(1);
if (field === 'emirates_id_front') {
setFrontFileName(`${file.name} (${sizeMb} MB)`);
} else {
setBackFileName(`${file.name} (${sizeMb} MB)`);
}
const handleInputChange = (field, value) => {
setData(prev => ({ ...prev, [field]: value }));
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: null }));
}
};
const removeFile = (field) => {
setData(field, null);
if (field === 'emirates_id_front') {
setFrontFileName(null);
} else {
setBackFileName(null);
}
};
const handleSubmit = (e) => {
const handleSubmit = async (e) => {
e.preventDefault();
post('/employer/register');
setLoading(true);
setErrors({});
try {
await axios.post('/employer/register', data);
toast.success('Registration details saved! A 6-digit verification code has been sent to your email.');
router.visit('/employer/verify-email');
} catch (err) {
if (err.response) {
if (err.response.status === 409) {
const errMsg = err.response.data?.errors?.email || 'This email address is already registered.';
setErrors({ email: errMsg });
toast.error(errMsg);
} else if (err.response.status === 422) {
setErrors(err.response.data.errors || {});
toast.error('Validation failed. Please correct the fields in red.');
} else {
toast.error('Something went wrong. Please try again.');
}
} else {
toast.error('Network error. Please check your internet connection.');
}
} finally {
setLoading(false);
}
};
const renderStepIndicator = () => (
<div className="flex items-center justify-between mb-12 relative max-w-md mx-auto px-4">
<div className="flex items-center justify-between mb-6 relative max-w-xs mx-auto px-2 select-none">
<div className="absolute top-1/2 left-0 w-full h-0.5 bg-slate-100 -translate-y-1/2 -z-10" />
<div className="absolute top-1/2 left-0 h-0.5 bg-[#185FA5] -translate-y-1/2 -z-10 transition-all duration-500" style={{ width: `${(step - 1) * 33.33}%` }} />
{[1, 2, 3, 4].map((s) => (
<div key={s} className="flex flex-col items-center">
<div className={`w-10 h-10 rounded-full flex items-center justify-center font-black text-sm transition-all duration-500 border-4 ${
step >= s ? 'bg-[#185FA5] text-white border-blue-100 scale-110 shadow-lg' : 'bg-white text-slate-400 border-slate-50'
{[
{ step: 1, label: 'Register' },
{ step: 2, label: 'Verify' },
{ step: 3, label: 'Password' }
].map((s) => (
<div key={s.step} className="flex flex-col items-center">
<div className={`w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs border-2 transition-all duration-300 ${
s.step === 1
? 'bg-[#185FA5] text-white border-[#185FA5] shadow-sm'
: 'bg-white text-slate-400 border-slate-200'
}`}>
{step > s ? <Check className="w-5 h-5" /> : s}
{s.step}
</div>
<span className={`text-[9px] font-semibold uppercase mt-1 tracking-wider ${
s.step === 1 ? 'text-[#185FA5]' : 'text-slate-400'
}`}>{s.label}</span>
</div>
))}
</div>
);
return (
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-12 bg-[#F8FAFC] font-sans selection:bg-blue-100 selection:text-[#185FA5]">
<Head title="Employer Registration - Secure Portal" />
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-12 bg-slate-50 font-sans">
<Head title="Employer Registration - Marketplace Portal" />
{/* Left Brand Panel */}
<div className="hidden lg:flex lg:col-span-4 bg-[#185FA5] p-16 flex-col justify-between text-white relative overflow-hidden select-none sticky top-0 h-screen">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.1),transparent)] pointer-events-none" />
<div className="absolute bottom-0 left-0 right-0 h-64 bg-gradient-to-t from-black/20 to-transparent pointer-events-none" />
{/* 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-10">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-white text-[#185FA5] rounded-2xl flex items-center justify-center font-black text-2xl shadow-2xl">
<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-black text-2xl tracking-tighter uppercase">Marketplace</span>
<span className="font-bold text-2xl tracking-tight">Marketplace Portal</span>
</div>
<div className="space-y-6 pt-10">
<h1 className="text-5xl font-black tracking-tighter leading-[1.1] drop-shadow-sm">
The Secure Hub for Direct Hiring.
<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">
Employer Registration
</span>
<h1 className="text-4xl font-extrabold tracking-tight leading-tight">
Find verified domestic workers no agents, no fees.
</h1>
<p className="text-blue-100 text-lg font-medium leading-relaxed opacity-90">
Join 12,000+ employers hiring domestic workers directly with verified documentation.
<p className="text-blue-100 text-sm font-medium">
Register to review your shortlisted candidates, manage interview schedules, and communicate directly.
</p>
</div>
<div className="space-y-6 pt-12 border-t border-white/10">
{[
{ title: 'Verified Profiles', desc: 'Every worker is OCR vetted and legally checked.', icon: ShieldCheck },
{ title: 'Instant Access', desc: 'Connect with candidates in seconds after verification.', icon: ArrowRight },
{ title: 'Zero Fees', desc: 'No recruitment agency commissions, ever.', icon: CheckCircle }
].map((feat, i) => (
<div key={i} className="flex items-start space-x-4 group">
<div className="p-2.5 bg-white/10 rounded-xl group-hover:bg-white/20 transition-colors">
<feat.icon className="w-6 h-6 text-emerald-300" />
</div>
<div>
<h3 className="font-black text-base uppercase tracking-wider">{feat.title}</h3>
<p className="text-sm text-blue-100 font-medium opacity-80">{feat.desc}</p>
</div>
</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">500+</div>
<div className="text-[10px] text-blue-100 uppercase tracking-wider font-semibold">Verified Workers</div>
</div>
<div className="bg-white/10 backdrop-blur-sm p-4 rounded-xl border border-white/10 text-center space-y-1">
<DollarSign className="w-6 h-6 text-emerald-300 mx-auto" />
<div className="font-bold text-lg">0 AED</div>
<div className="text-[10px] text-blue-100 uppercase tracking-wider font-semibold">Agent Fees</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-[10px] font-black uppercase tracking-[0.3em] text-blue-300 opacity-60">
Empowering Household Recruitment Since 2024
<div className="relative z-10 text-xs text-blue-200 font-medium">
© 2026 Domestic Worker Marketplace. All rights reserved.
</div>
</div>
{/* Right Registration Content */}
<div className="col-span-1 lg:col-span-8 flex flex-col items-center py-12 px-6 sm:px-12 overflow-y-auto">
<div className="w-full max-w-2xl">
<div className="text-center mb-10">
<h2 className="text-3xl font-black text-slate-900 tracking-tight uppercase">Employer Enrollment</h2>
<p className="text-sm font-bold text-slate-400 mt-2 uppercase tracking-widest">Step {step} of 4: {
step === 1 ? 'Account Security' :
step === 2 ? 'Identity Verification' :
step === 3 ? 'Choose Your Plan' : 'Secure Payment'
}</p>
{/* Right Registration Form */}
<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">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-gray-900 tracking-tight">Create Account</h2>
<p className="text-xs text-gray-500 mt-1">Register your employer portal to start searching</p>
</div>
{renderStepIndicator()}
</div>
{renderStepIndicator()}
<div className="bg-white rounded-[40px] shadow-[0_20px_50px_rgba(0,0,0,0.04)] border border-slate-100 p-8 sm:p-12 transition-all duration-500 animate-in fade-in slide-in-from-bottom-4">
<form onSubmit={handleSubmit} className="space-y-8">
{/* Step 1: Account Creation */}
{step === 1 && (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="md:col-span-2 space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Full Legal Name</label>
<div className="relative group">
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type="text"
value={data.name}
onChange={(e) => setData('name', e.target.value)}
placeholder="e.g. Abdullah Bin Ahmed"
className="w-full pl-12 pr-4 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none"
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Work Email</label>
<div className="relative group">
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
placeholder="employer@domain.com"
className="w-full pl-12 pr-4 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none"
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Mobile Access</label>
<div className="relative group">
<PhoneIcon className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type="text"
value={data.phone}
onChange={(e) => setData('phone', e.target.value)}
placeholder="+971 XX XXX XXXX"
className="w-full pl-12 pr-4 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none"
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Secure Password</label>
<div className="relative group">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type={showPassword ? 'text' : 'password'}
value={data.password}
onChange={(e) => setData('password', e.target.value)}
placeholder="••••••••"
className="w-full pl-12 pr-12 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 text-slate-400 hover:text-slate-600"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Confirm Access</label>
<input
type={showPassword ? 'text' : 'password'}
value={data.password_confirmation}
onChange={(e) => setData('password_confirmation', e.target.value)}
placeholder="••••••••"
className="w-full px-5 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none"
required
/>
</div>
</div>
<button
type="button"
onClick={nextStep}
className="w-full bg-[#185FA5] hover:bg-[#144f8a] text-white py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-2 mt-8"
>
<span>Verify Identity</span>
<ArrowRight className="w-4 h-4" />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Company Name */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Company / Household Name</label>
<input
type="text"
value={data.company_name}
onChange={(e) => handleInputChange('company_name', e.target.value)}
placeholder="e.g. Al Mansoor Household"
className={`w-full px-3.5 py-2.5 rounded-xl border text-sm focus:outline-none focus:ring-2 ${
errors.company_name
? 'border-red-500 focus:ring-red-500/20 focus:border-red-500'
: 'border-slate-300 focus:ring-[#185FA5]/20 focus:border-[#185FA5]'
}`}
required
/>
{errors.company_name && (
<p className="mt-1 text-xs text-red-500">{errors.company_name[0] || errors.company_name}</p>
)}
</div>
{/* Step 2: Emirates ID */}
{step === 2 && (
<div className="space-y-8">
<div className="bg-amber-50 border border-amber-100 p-6 rounded-3xl flex items-start space-x-4">
<AlertCircle className="w-6 h-6 text-amber-600 shrink-0" />
<p className="text-[11px] font-bold text-amber-800 leading-relaxed uppercase tracking-wider">
In compliance with UAE Labor Law, all employers must verify their identity using a valid Emirates ID before accessing the marketplace.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Contact Name */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Contact Name</label>
<input
type="text"
value={data.name}
onChange={(e) => handleInputChange('name', e.target.value)}
placeholder="e.g. Abdullah Bin Ahmed"
className={`w-full px-3.5 py-2.5 rounded-xl border text-sm focus:outline-none focus:ring-2 ${
errors.name
? 'border-red-500 focus:ring-red-500/20 focus:border-red-500'
: 'border-slate-300 focus:ring-[#185FA5]/20 focus:border-[#185FA5]'
}`}
required
/>
{errors.name && (
<p className="mt-1 text-xs text-red-500">{errors.name[0] || errors.name}</p>
)}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{[
{ field: 'emirates_id_front', label: 'Emirates ID Front', file: frontFileName },
{ field: 'emirates_id_back', label: 'Emirates ID Back', file: backFileName }
].map((slot) => (
<div key={slot.field} className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">{slot.label}</label>
{slot.file ? (
<div className="relative aspect-[1.6/1] bg-slate-50 rounded-[32px] border-2 border-[#185FA5] flex flex-col items-center justify-center p-6 text-center shadow-lg shadow-blue-500/5">
<FileText className="w-10 h-10 text-[#185FA5] mb-2" />
<span className="text-[10px] font-black text-slate-900 uppercase truncate max-w-full px-4">{slot.file}</span>
<button
type="button"
onClick={() => removeFile(slot.field)}
className="absolute -top-2 -right-2 bg-rose-500 text-white p-2 rounded-full shadow-lg hover:bg-rose-600 transition-colors"
>
<X className="w-4 h-4" />
</button>
</div>
) : (
<label className="relative aspect-[1.6/1] bg-slate-50 rounded-[32px] border-4 border-dashed border-slate-200 flex flex-col items-center justify-center p-8 text-center cursor-pointer hover:border-[#185FA5] hover:bg-blue-50 transition-all group">
<div className="w-16 h-16 bg-white rounded-2xl flex items-center justify-center shadow-md mb-4 group-hover:scale-110 transition-transform">
<Camera className="w-8 h-8 text-slate-300 group-hover:text-[#185FA5]" />
</div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest group-hover:text-[#185FA5]">Tap to upload</span>
<input
type="file"
accept=".jpg,.jpeg,.png,.pdf"
className="hidden"
onChange={(e) => handleFileChange(e, slot.field)}
/>
</label>
)}
</div>
))}
</div>
{/* Work Email */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Work Email</label>
<input
type="email"
value={data.email}
onChange={(e) => handleInputChange('email', e.target.value)}
placeholder="employer@domain.com"
className={`w-full px-3.5 py-2.5 rounded-xl border text-sm focus:outline-none focus:ring-2 ${
errors.email
? 'border-red-500 focus:ring-red-500/20 focus:border-red-500'
: 'border-slate-300 focus:ring-[#185FA5]/20 focus:border-[#185FA5]'
}`}
required
/>
{errors.email && (
<p className="mt-1 text-xs text-red-500">{errors.email[0] || errors.email}</p>
)}
</div>
</div>
<div className="flex gap-4 pt-6">
<button
type="button"
onClick={prevStep}
className="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-600 py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all flex items-center justify-center space-x-2"
>
<ArrowLeft className="w-4 h-4" />
<span>Back</span>
</button>
<button
type="button"
onClick={nextStep}
disabled={!frontFileName || !backFileName}
className="flex-[2] bg-[#185FA5] hover:bg-[#144f8a] disabled:opacity-50 text-white py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-2"
>
<span>Select Plan</span>
<ArrowRight className="w-4 h-4" />
</button>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Mobile Number */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Mobile Number</label>
<input
type="tel"
value={data.phone}
onChange={(e) => handleInputChange('phone', e.target.value)}
placeholder="e.g. +971501234567"
className={`w-full px-3.5 py-2.5 rounded-xl border text-sm focus:outline-none focus:ring-2 ${
errors.phone
? 'border-red-500 focus:ring-red-500/20 focus:border-red-500'
: 'border-slate-300 focus:ring-[#185FA5]/20 focus:border-[#185FA5]'
}`}
required
/>
{errors.phone && (
<p className="mt-1 text-xs text-red-500">{errors.phone[0] || errors.phone}</p>
)}
</div>
{/* Country Dropdown */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Country of Residence</label>
<select
value={data.country}
onChange={(e) => handleInputChange('country', e.target.value)}
className="w-full px-3.5 py-2.5 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] bg-white cursor-pointer appearance-none"
required
>
{countries.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
{errors.country && (
<p className="mt-1 text-xs text-red-500">{errors.country[0] || errors.country}</p>
)}
</div>
</div>
<button
type="submit"
disabled={loading}
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-70 disabled:cursor-not-allowed mt-2"
>
{loading ? (
<Loader2 className="w-5 h-5 animate-spin mr-2" />
) : (
'Send Verification Code'
)}
</button>
</form>
{/* Step 3: Plan Selection */}
{step === 3 && (
<div className="space-y-8">
<div className="grid grid-cols-1 gap-4">
{plans.map((p) => (
<button
key={p.id}
type="button"
onClick={() => setData('selected_plan', p.id)}
className={`p-6 rounded-[32px] border-4 text-left transition-all relative overflow-hidden flex items-center justify-between group ${
data.selected_plan === p.id
? 'bg-blue-50 border-[#185FA5] shadow-xl shadow-blue-500/10'
: 'bg-white border-slate-50 hover:border-slate-200'
}`}
>
<div className="space-y-1">
<div className="flex items-center space-x-3">
<h4 className="text-lg font-black text-slate-900 uppercase tracking-tight">{p.name}</h4>
{p.popular && <span className="bg-emerald-500 text-white px-2 py-0.5 rounded-full text-[9px] font-black uppercase">Most Popular</span>}
</div>
<div className="flex items-center space-x-4">
{p.features.slice(0, 2).map((f, i) => (
<div key={i} className="flex items-center space-x-1.5 text-[10px] font-bold text-slate-500">
<CheckCircle className="w-3.5 h-3.5 text-emerald-500" />
<span>{f}</span>
</div>
))}
</div>
</div>
<div className="text-right">
<div className="text-2xl font-black text-[#185FA5]">{p.price} <span className="text-[10px] text-slate-400 font-bold uppercase tracking-widest">AED</span></div>
<div className="text-[9px] font-black text-slate-400 uppercase tracking-widest mt-1">One Time Access</div>
</div>
</button>
))}
</div>
<div className="flex gap-4 pt-6">
<button
type="button"
onClick={prevStep}
className="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-600 py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all flex items-center justify-center space-x-2"
>
<ArrowLeft className="w-4 h-4" />
<span>Back</span>
</button>
<button
type="button"
onClick={nextStep}
className="flex-[2] bg-[#185FA5] hover:bg-[#144f8a] text-white py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-2"
>
<span>Pay & Register</span>
<CreditCard className="w-4 h-4" />
</button>
</div>
</div>
)}
{/* Step 4: Secure Payment */}
{step === 4 && (
<div className="space-y-8 animate-in zoom-in-95 duration-500">
<div className="bg-slate-900 rounded-[32px] p-8 text-white relative overflow-hidden shadow-2xl">
<div className="absolute top-0 right-0 w-64 h-64 bg-blue-500/20 rounded-full blur-3xl -mr-32 -mt-32" />
<div className="flex justify-between items-start mb-12 relative z-10">
<div className="w-12 h-10 bg-slate-800 rounded-lg border border-slate-700 flex items-center justify-center">
<div className="w-8 h-6 bg-gradient-to-br from-amber-200 to-amber-500 rounded" />
</div>
<CreditCard className="w-10 h-10 text-white/20" />
</div>
<div className="space-y-4 relative z-10">
<div className="text-xl font-medium tracking-[0.2em] h-8 font-mono">
{data.card_number || '•••• •••• •••• ••••'}
</div>
<div className="flex justify-between items-end">
<div>
<div className="text-[10px] font-black text-white/40 uppercase tracking-widest mb-1">Card Holder</div>
<div className="text-sm font-bold uppercase tracking-widest">{data.name || 'Your Name'}</div>
</div>
<div className="text-right">
<div className="text-[10px] font-black text-white/40 uppercase tracking-widest mb-1">Expires</div>
<div className="text-sm font-bold uppercase tracking-widest">{data.expiry || 'MM/YY'}</div>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Card Number</label>
<div className="relative group">
<CreditCard className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type="text"
maxLength="19"
value={data.card_number}
onChange={(e) => setData('card_number', e.target.value.replace(/\W/gi, '').replace(/(.{4})/g, '$1 ').trim())}
placeholder="4242 4242 4242 4242"
className="w-full pl-12 pr-4 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none"
required
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Expiry Date</label>
<input
type="text"
maxLength="5"
value={data.expiry}
onChange={(e) => setData('expiry', e.target.value.replace(/^(\d{2})/, '$1/'))}
placeholder="MM/YY"
className="w-full px-5 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none text-center"
required
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Security Code</label>
<input
type="password"
maxLength="4"
value={data.cvc}
onChange={(e) => setData('cvc', e.target.value)}
placeholder="CVC"
className="w-full px-5 py-4 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none text-center"
required
/>
</div>
</div>
</div>
<div className="p-6 bg-blue-50 rounded-3xl border border-blue-100 flex items-center justify-between">
<div>
<div className="text-[10px] font-black text-blue-600 uppercase tracking-widest">Total Amount</div>
<div className="text-xl font-black text-slate-900">{plans.find(p => p.id === data.selected_plan)?.price} AED</div>
</div>
<ShieldCheck className="w-8 h-8 text-[#185FA5] opacity-20" />
</div>
<div className="flex gap-4 pt-4">
<button
type="button"
onClick={prevStep}
className="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-600 py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all flex items-center justify-center space-x-2"
>
<ArrowLeft className="w-4 h-4" />
<span>Back</span>
</button>
<button
type="submit"
disabled={processing}
className="flex-[2] bg-emerald-600 hover:bg-emerald-700 active:bg-emerald-800 text-white py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-emerald-500/20 flex items-center justify-center space-x-2"
>
{processing ? <Loader2 className="w-5 h-5 animate-spin mr-2" /> : (
<>
<span>Complete & Pay</span>
<Lock className="w-4 h-4" />
</>
)}
</button>
</div>
<p className="text-[10px] text-slate-400 text-center font-bold uppercase tracking-widest">
Secured by 256-bit AES encryption
</p>
</div>
)}
</form>
</div>
<div className="mt-12 text-center">
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest">
Already part of the network?{' '}
<Link href="/employer/login" className="text-[#185FA5] hover:underline">
Authorize Account
<div className="border-t border-slate-100 pt-6 text-center">
<p className="text-xs text-gray-600 font-medium">
Already have an account?{' '}
<Link href="/employer/login" className="text-[#185FA5] font-bold hover:underline">
Sign in
</Link>
</p>
</div>

View File

@ -0,0 +1,263 @@
import React, { useState, useEffect } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import axios from 'axios';
import { toast } from 'sonner';
import {
ShieldCheck,
CheckCircle,
ArrowLeft,
MailCheck,
Loader2,
RefreshCw,
KeyRound
} from 'lucide-react';
export default function VerifyEmail({ email }) {
const [otp, setOtp] = useState('');
const [loading, setLoading] = useState(false);
const [resending, setResending] = useState(false);
const [errors, setErrors] = useState({});
// Cooldown timer state for resending OTP (60s)
const [cooldown, setCooldown] = useState(60);
useEffect(() => {
let timer;
if (cooldown > 0) {
timer = setTimeout(() => setCooldown(cooldown - 1), 1000);
}
return () => clearTimeout(timer);
}, [cooldown]);
const handleOtpChange = (e) => {
const val = e.target.value.replace(/[^0-9]/g, '').slice(0, 6);
setOtp(val);
if (errors.otp) {
setErrors({});
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (otp.length !== 6) {
setErrors({ otp: 'Please enter all 6 digits of the code.' });
return;
}
setLoading(true);
setErrors({});
try {
const response = await axios.post('/employer/verify-email', { otp });
toast.success('Email verified successfully! Let\'s secure your account.');
router.visit('/employer/create-password');
} catch (err) {
if (err.response) {
if (err.response.status === 422 || err.response.status === 400) {
const otpErr = err.response.data?.errors?.otp || err.response.data?.message || 'Verification failed.';
setErrors({ otp: otpErr });
toast.error(otpErr);
} else if (err.response.status === 429) {
toast.error('Too many requests. Please slow down.');
} else {
toast.error('Failed to verify OTP. Please try again.');
}
} else {
toast.error('Network error. Please check your connection.');
}
} finally {
setLoading(false);
}
};
const handleResend = async () => {
if (cooldown > 0 || resending) return;
setResending(true);
try {
const response = await axios.post('/employer/resend-otp');
toast.success('A new verification code has been dispatched to your email.');
setCooldown(60); // Reset timer
setOtp('');
} catch (err) {
if (err.response) {
toast.error(err.response.data?.message || 'Failed to dispatch verification code.');
if (err.response.status === 429) {
setCooldown(60); // Reset cooldown on too many requests
}
} else {
toast.error('Network error. Please check your connection.');
}
} finally {
setResending(false);
}
};
const renderStepIndicator = () => (
<div className="flex items-center justify-between mb-12 relative max-w-md mx-auto px-4 select-none">
<div className="absolute top-1/2 left-0 w-full h-0.5 bg-slate-100 -translate-y-1/2 -z-10" />
<div className="absolute top-1/2 left-0 h-0.5 bg-[#185FA5] -translate-y-1/2 -z-10 transition-all duration-500" style={{ width: '50%' }} />
{[
{ step: 1, label: 'Register' },
{ step: 2, label: 'Verify Code' },
{ step: 3, label: 'Set Password' }
].map((s) => (
<div key={s.step} className="flex flex-col items-center">
<div className={`w-10 h-10 rounded-full flex items-center justify-center font-black text-sm border-4 transition-all duration-300 ${
s.step <= 2
? 'bg-[#185FA5] text-white border-blue-100 scale-110 shadow-lg'
: 'bg-white text-slate-400 border-slate-50'
}`}>
{s.step}
</div>
<span className={`text-[9px] font-black uppercase mt-2 tracking-wider ${
s.step <= 2 ? 'text-[#185FA5]' : 'text-slate-400'
}`}>{s.label}</span>
</div>
))}
</div>
);
return (
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-12 bg-[#F8FAFC] font-sans selection:bg-blue-100 selection:text-[#185FA5]">
<Head title="Verify Email - Step 2" />
{/* Left Brand Panel */}
<div className="hidden lg:flex lg:col-span-4 bg-[#185FA5] p-16 flex-col justify-between text-white relative overflow-hidden select-none sticky top-0 h-screen">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.1),transparent)] pointer-events-none" />
<div className="relative z-10 space-y-10">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-white text-[#185FA5] rounded-2xl flex items-center justify-center font-black text-2xl shadow-2xl">
M
</div>
<span className="font-black text-2xl tracking-tighter uppercase">Marketplace</span>
</div>
<div className="space-y-6 pt-10">
<h1 className="text-5xl font-black tracking-tighter leading-[1.1] drop-shadow-sm uppercase">
Verify Your Business Email.
</h1>
<p className="text-blue-100 text-lg font-medium leading-relaxed opacity-90">
We take platform trust seriously. Verify your email to ensure secure account setup.
</p>
</div>
</div>
<div className="relative z-10 text-[10px] font-black uppercase tracking-[0.3em] text-blue-300 opacity-60">
Empowering Household Recruitment Since 2024
</div>
</div>
{/* Right Form Content */}
<div className="col-span-1 lg:col-span-8 flex flex-col items-center py-12 px-6 sm:px-12 overflow-y-auto justify-center">
<div className="w-full max-w-xl">
<div className="text-center mb-10">
<h2 className="text-3xl font-black text-slate-900 tracking-tight uppercase">Email Verification</h2>
<p className="text-sm font-bold text-slate-400 mt-2 uppercase tracking-widest">
Step 2 of 3: Enter Hashed Passcode
</p>
</div>
{renderStepIndicator()}
<div className="bg-white rounded-[40px] shadow-[0_20px_50px_rgba(0,0,0,0.04)] border border-slate-100 p-8 sm:p-12 transition-all duration-500 animate-in fade-in slide-in-from-bottom-4">
<div className="text-center mb-8">
<div className="w-16 h-16 bg-blue-50 text-[#185FA5] rounded-3xl flex items-center justify-center mx-auto mb-4 border border-blue-100 shadow-sm animate-pulse">
<MailCheck className="w-8 h-8" />
</div>
<h3 className="text-lg font-black text-slate-900 uppercase">Check Your Email</h3>
<p className="text-xs font-bold text-slate-400 mt-1 leading-relaxed">
We dispatched a 6-digit confirmation code to:
<br />
<span className="text-slate-800 font-extrabold lowercase text-sm mt-1 inline-block">{email}</span>
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-3">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1 text-center block">6-Digit Passcode</label>
<div className="relative max-w-xs mx-auto group">
<KeyRound className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 group-focus-within:text-[#185FA5] transition-colors" />
<input
type="text"
pattern="[0-9]*"
inputMode="numeric"
maxLength="6"
value={otp}
onChange={handleOtpChange}
placeholder="••••••"
className={`w-full pl-12 pr-4 py-5 bg-slate-50 border rounded-2xl text-center text-2xl font-black tracking-[0.25em] focus:ring-4 focus:ring-blue-100 focus:bg-white transition-all outline-none ${
errors.otp ? 'border-rose-400 bg-rose-50/20' : 'border-transparent'
}`}
required
/>
</div>
{errors.otp && (
<p className="text-xs text-rose-500 font-bold text-center uppercase tracking-wider">{errors.otp}</p>
)}
</div>
<button
type="submit"
disabled={loading || otp.length !== 6}
className="w-full bg-[#185FA5] hover:bg-[#144f8a] disabled:opacity-70 text-white py-5 rounded-[24px] font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-2 mt-8 select-none cursor-pointer"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin mr-2" />
<span>Verifying Code...</span>
</>
) : (
<>
<span>Confirm Verification</span>
<CheckCircle className="w-4 h-4" />
</>
)}
</button>
<div className="text-center pt-4 border-t border-slate-100">
{cooldown > 0 ? (
<p className="text-[10px] font-black text-slate-400 uppercase tracking-wider">
Resend Code available in <span className="text-[#185FA5] font-extrabold">{cooldown}s</span>
</p>
) : (
<button
type="button"
onClick={handleResend}
disabled={resending}
className="inline-flex items-center space-x-1.5 text-xs font-black text-[#185FA5] hover:text-[#144f8a] uppercase tracking-wider cursor-pointer"
>
{resending ? (
<>
<Loader2 className="w-3.5 h-3.5 animate-spin" />
<span>Requesting...</span>
</>
) : (
<>
<RefreshCw className="w-3.5 h-3.5" />
<span>Resend Passcode</span>
</>
)}
</button>
)}
</div>
</form>
</div>
<div className="mt-8 text-center select-none">
<Link href="/employer/register" className="inline-flex items-center space-x-2 text-xs font-bold text-slate-400 hover:text-slate-600 uppercase tracking-widest">
<ArrowLeft className="w-4 h-4" />
<span>Change Email / Back</span>
</Link>
</div>
</div>
</div>
</div>
);
}

View File

@ -34,10 +34,7 @@ export default function Create({ categories: initialCategories }) {
const handleSubmit = (e) => {
e.preventDefault();
// In a real app, we'd post to /employer/jobs
// post('/employer/jobs');
alert('Job posted successfully! Redirecting to Job List...');
window.location.href = '/employer/jobs';
post('/employer/jobs/create');
};
return (

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { Head, Link } from '@inertiajs/react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../../Layouts/EmployerLayout';
import {
Send,
@ -38,6 +38,10 @@ export default function Show({ conversation, initialMessages, conversations = []
scrollToBottom();
}, [messages, isTyping]);
useEffect(() => {
setMessages(initialMessages || []);
}, [initialMessages]);
const filteredConversations = (conversations || []).filter(c =>
c.worker_name.toLowerCase().includes(searchTerm.toLowerCase())
);
@ -45,26 +49,14 @@ export default function Show({ conversation, initialMessages, conversations = []
const sendMsg = (text) => {
if (!text.trim()) return;
const newMsg = {
id: Date.now(),
sender: 'employer',
text: text,
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
};
setMessages(prev => [...prev, newMsg]);
setInput('');
setIsTyping(true);
setTimeout(() => {
setIsTyping(false);
setMessages(prev => [...prev, {
id: Date.now() + 1,
sender: 'worker',
text: "Thank you for the message ma'am! I confirm I have received your request and look forward to speaking with you.",
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
}]);
}, 1500 + Math.random() * 1000);
router.post(`/employer/messages/${conversation.id}/send`, {
text: text
}, {
preserveScroll: true,
onSuccess: () => {
setInput('');
}
});
};
const handleFormSubmit = (e) => {

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Head } from '@inertiajs/react';
import { Head, useForm } from '@inertiajs/react';
import EmployerLayout from '../../Layouts/EmployerLayout';
import {
User,
@ -18,21 +18,30 @@ import {
} from 'lucide-react';
export default function Profile({ employerProfile }) {
const [profile, setProfile] = useState(employerProfile || {
name: 'John Doe',
company_name: 'Al Mansoor Household',
email: 'employer@example.com',
phone: '+971 50 123 4567',
language: 'English',
notifications: true
const { data: profile, setData: setProfile, post, processing, errors } = useForm({
name: employerProfile?.name || '',
company_name: employerProfile?.company_name || '',
email: employerProfile?.email || '',
phone: employerProfile?.phone || '',
language: employerProfile?.language || 'English',
notifications: employerProfile?.notifications ?? true,
current_password: '',
new_password: '',
new_password_confirmation: '',
});
const [activeTab, setActiveTab] = useState('personal');
const [saved, setSaved] = useState(false);
const handleSave = (e) => {
e.preventDefault();
setSaved(true);
setTimeout(() => setSaved(false), 3000);
post('/employer/profile/update', {
preserveScroll: true,
onSuccess: () => {
setSaved(true);
setTimeout(() => setSaved(false), 3000);
}
});
};
const tabs = [
@ -128,6 +137,7 @@ export default function Profile({ employerProfile }) {
className="w-full pl-12 pr-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all"
/>
</div>
{errors.name && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.name}</p>}
</div>
<div className="space-y-2">
@ -139,8 +149,9 @@ export default function Profile({ employerProfile }) {
value={profile.email}
onChange={(e) => setProfile({ ...profile, email: e.target.value })}
className="w-full pl-12 pr-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all"
/>
</div>
/>
</div>
{errors.email && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.email}</p>}
</div>
<div className="space-y-2">
@ -154,6 +165,7 @@ export default function Profile({ employerProfile }) {
className="w-full pl-12 pr-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all"
/>
</div>
{errors.phone && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.phone}</p>}
</div>
<div className="space-y-2">
@ -169,6 +181,7 @@ export default function Profile({ employerProfile }) {
<option>Arabic</option>
</select>
</div>
{errors.language && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.language}</p>}
</div>
</div>
</div>
@ -192,6 +205,7 @@ export default function Profile({ employerProfile }) {
className="w-full pl-12 pr-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all"
/>
</div>
{errors.company_name && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.company_name}</p>}
</div>
<div className="p-6 bg-slate-50 rounded-3xl border border-slate-100 flex items-start space-x-4">
@ -223,15 +237,36 @@ export default function Profile({ employerProfile }) {
<div className="space-y-4 max-w-md">
<div className="space-y-2">
<label className="text-xs font-black text-slate-500 uppercase tracking-tighter ml-1">Current Password</label>
<input type="password" placeholder="••••••••" className="w-full px-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all" />
<input
type="password"
placeholder="••••••••"
value={profile.current_password}
onChange={(e) => setProfile({ ...profile, current_password: e.target.value })}
className="w-full px-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all"
/>
{errors.current_password && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.current_password}</p>}
</div>
<div className="space-y-2">
<label className="text-xs font-black text-slate-500 uppercase tracking-tighter ml-1">New Password</label>
<input type="password" placeholder="••••••••" className="w-full px-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all" />
<input
type="password"
placeholder="••••••••"
value={profile.new_password}
onChange={(e) => setProfile({ ...profile, new_password: e.target.value })}
className="w-full px-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all"
/>
{errors.new_password && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.new_password}</p>}
</div>
<div className="space-y-2">
<label className="text-xs font-black text-slate-500 uppercase tracking-tighter ml-1">Confirm New Password</label>
<input type="password" placeholder="••••••••" className="w-full px-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all" />
<input
type="password"
placeholder="••••••••"
value={profile.new_password_confirmation}
onChange={(e) => setProfile({ ...profile, new_password_confirmation: e.target.value })}
className="w-full px-4 py-3 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all"
/>
{errors.new_password_confirmation && <p className="text-rose-500 text-[10px] font-bold mt-1 ml-1">{errors.new_password_confirmation}</p>}
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import React, { useState, useEffect } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../Layouts/EmployerLayout';
import {
CheckCircle2,
@ -34,6 +34,20 @@ export default function SelectedCandidates({ selectedWorkers }) {
const [workers, setWorkers] = useState((selectedWorkers || []).filter(w => w.status !== 'Searching'));
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
setWorkers((selectedWorkers || []).filter(w => w.status !== 'Searching'));
}, [selectedWorkers]);
const changeStatus = (id, newStatus) => {
// Optimistic UI state update
setWorkers(workers.map(w => w.id === id ? { ...w, status: newStatus } : w));
// Persist change to database
router.post(`/employer/candidates/${id}/status`, { status: newStatus }, {
preserveScroll: true
});
};
const stats = [
{
label: 'Total Candidates',
@ -184,17 +198,17 @@ export default function SelectedCandidates({ selectedWorkers }) {
<DropdownMenuContent align="start" className="w-44 p-2 rounded-xl">
<DropdownMenuLabel className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-2 py-1.5">Change Status</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-xs font-bold text-slate-600 focus:text-amber-700 focus:bg-amber-50 rounded-lg">
<DropdownMenuItem onClick={() => changeStatus(worker.id, 'Reviewing')} className="text-xs font-bold text-slate-600 focus:text-amber-700 focus:bg-amber-50 rounded-lg cursor-pointer">
Reviewing
</DropdownMenuItem>
<DropdownMenuItem className="text-xs font-bold text-slate-600 focus:text-blue-700 focus:bg-blue-50 rounded-lg">
<DropdownMenuItem onClick={() => changeStatus(worker.id, 'Offer Sent')} className="text-xs font-bold text-slate-600 focus:text-blue-700 focus:bg-blue-50 rounded-lg cursor-pointer">
Offer Sent
</DropdownMenuItem>
<DropdownMenuItem className="text-xs font-bold text-slate-600 focus:text-emerald-700 focus:bg-emerald-50 rounded-lg">
<DropdownMenuItem onClick={() => changeStatus(worker.id, 'Hired')} className="text-xs font-bold text-slate-600 focus:text-emerald-700 focus:bg-emerald-50 rounded-lg cursor-pointer">
Hired
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-xs font-bold text-rose-600 focus:text-rose-700 focus:bg-rose-50 rounded-lg">
<DropdownMenuItem onClick={() => changeStatus(worker.id, 'Rejected')} className="text-xs font-bold text-rose-600 focus:text-rose-700 focus:bg-rose-50 rounded-lg cursor-pointer">
Rejected
</DropdownMenuItem>
</DropdownMenuContent>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../Layouts/EmployerLayout';
import {
Bookmark,
@ -16,7 +16,13 @@ export default function Shortlist({ shortlistedWorkers }) {
const [workers, setWorkers] = useState(shortlistedWorkers || []);
const removeWorker = (id) => {
// Optimistic UI state update
setWorkers(workers.filter(w => w.id !== id));
// Persist change to database
router.post(`/employer/shortlist/${id}/remove`, {}, {
preserveScroll: true
});
};
return (

View File

@ -1,5 +1,5 @@
import React, { useState, useMemo } from 'react';
import { Head, Link } from '@inertiajs/react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../../Layouts/EmployerLayout';
import {
Search,
@ -17,7 +17,7 @@ import {
Filter
} from 'lucide-react';
export default function Index({ initialWorkers, filtersMetadata }) {
export default function Index({ initialWorkers, initialShortlistedIds, filtersMetadata }) {
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All Categories');
const [selectedNationality, setSelectedNationality] = useState('All Nationalities');
@ -25,14 +25,25 @@ export default function Index({ initialWorkers, filtersMetadata }) {
const [selectedExperience, setSelectedExperience] = useState('All Experience');
const [selectedReligion, setSelectedReligion] = useState('All Religions');
const [maxSalary, setMaxSalary] = useState(3000);
const [shortlistedIds, setShortlistedIds] = useState([101, 103]);
const [shortlistedIds, setShortlistedIds] = useState(initialShortlistedIds || []);
const toggleShortlist = (id) => {
// Optimistic UI state update
if (shortlistedIds.includes(id)) {
setShortlistedIds(shortlistedIds.filter(i => i !== id));
} else {
setShortlistedIds([...shortlistedIds, id]);
}
// Persist change to database
router.post('/employer/shortlist/toggle', { worker_id: id }, {
preserveScroll: true,
onSuccess: (page) => {
if (page.props.initialShortlistedIds) {
setShortlistedIds(page.props.initialShortlistedIds);
}
}
});
};
const resetFilters = () => {

View File

@ -1,90 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import {
Megaphone,
Calendar,
ArrowRight,
Search,
Plus,
X
} from 'lucide-react';
export default function EmployerAnnouncements() {
const [searchTerm, setSearchTerm] = useState('');
const announcements = [
{ id: 1, title: 'Ramadan Working Hours Update', date: 'May 12, 2026', content: 'Please note the revised working hours for all domestic staff during the upcoming Holy Month of Ramadan...', priority: 'High' },
{ id: 2, title: 'New Visa Regulations 2026', date: 'May 08, 2026', content: 'The Ministry of Human Resources has announced new updates regarding domestic worker visa renewals...', priority: 'Normal' },
{ id: 3, title: 'Marketplace Service Maintenance', date: 'May 05, 2026', content: 'Our platform will undergo scheduled maintenance on Sunday from 2:00 AM to 4:00 AM...', priority: 'Low' },
];
return (
<EmployerMobileLayout>
<Head title="Announcements" />
<div className="p-6 space-y-8">
{/* Header */}
<div className="flex items-center justify-between">
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">Broadcasts</h1>
<p className="text-sm text-slate-500 font-medium">Important updates & announcements</p>
</div>
<button className="w-12 h-12 bg-[#141B34] text-white rounded-2xl flex items-center justify-center shadow-lg active:scale-95 transition-all">
<Plus className="w-6 h-6" />
</button>
</div>
{/* Search */}
<div className="relative group">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-300 group-focus-within:text-[#141B34] transition-colors" />
<input
type="text"
placeholder="Search announcements..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-12 pr-4 py-4 bg-white border border-slate-200 rounded-2xl text-sm focus:ring-4 focus:ring-slate-100 focus:border-slate-300 outline-none transition-all"
/>
</div>
{/* Announcement List */}
<div className="space-y-4 pb-10">
{announcements.map((item) => (
<div key={item.id} className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm space-y-4 active:scale-[0.98] transition-all group">
<div className="flex items-start justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center text-blue-600">
<Megaphone className="w-5 h-5" />
</div>
<div className="space-y-0.5">
<h4 className="font-bold text-slate-900 text-sm leading-tight">{item.title}</h4>
<div className="flex items-center space-x-1 text-[10px] font-bold text-slate-400 uppercase tracking-widest">
<Calendar className="w-3 h-3" />
<span>{item.date}</span>
</div>
</div>
</div>
</div>
<p className="text-xs text-slate-500 leading-relaxed font-medium line-clamp-2">
{item.content}
</p>
<div className="flex items-center justify-between pt-2 border-t border-slate-50">
<div className={`px-2.5 py-1 rounded-lg text-[9px] font-bold uppercase tracking-widest ${
item.priority === 'High' ? 'bg-rose-50 text-rose-500 border border-rose-100' : 'bg-slate-50 text-slate-400 border border-slate-100'
}`}>
{item.priority} Priority
</div>
<button className="text-xs font-bold text-[#141B34] flex items-center space-x-1 group-hover:translate-x-1 transition-transform">
<span>Read More</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
))}
</div>
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,135 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import {
Users,
Clock,
CheckCircle2,
XCircle,
Search,
Filter,
MessageSquare,
MoreHorizontal
} from 'lucide-react';
export default function EmployerCandidates() {
const [searchQuery, setSearchQuery] = useState('');
const stats = [
{ label: 'Total Candidates', value: 5, icon: Users, color: 'bg-blue-50 text-blue-600' },
{ label: 'Reviewing', value: 3, icon: Clock, color: 'bg-orange-50 text-orange-500' },
{ label: 'Hired', value: 1, icon: CheckCircle2, color: 'bg-emerald-50 text-emerald-600' },
{ label: 'Rejected', value: 1, icon: XCircle, color: 'bg-rose-50 text-rose-500' },
];
const candidates = [
{ id: 1, name: 'Sarah Connor', country: 'Philippines', role: 'Site Supervisor', salary: '4,500 AED', status: 'Reviewing' },
{ id: 2, name: 'Maria Garcia', country: 'Kenya', role: 'Housemaid', salary: '2,500 AED', status: 'Hired' },
{ id: 3, name: 'Elena Gilbert', country: 'Indonesia', role: 'Nanny', salary: '3,000 AED', status: 'Rejected' },
];
const getStatusStyles = (status) => {
switch (status) {
case 'Reviewing': return 'bg-orange-50 text-orange-600 border-orange-100';
case 'Hired': return 'bg-emerald-50 text-emerald-600 border-emerald-100';
case 'Rejected': return 'bg-rose-50 text-rose-600 border-rose-100';
default: return 'bg-slate-50 text-slate-600 border-slate-100';
}
};
const getStatusDot = (status) => {
switch (status) {
case 'Reviewing': return 'bg-orange-500';
case 'Hired': return 'bg-emerald-500';
case 'Rejected': return 'bg-rose-500';
default: return 'bg-slate-500';
}
};
return (
<EmployerMobileLayout>
<Head title="Candidates" />
<div className="p-6 space-y-8">
{/* Page Title */}
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">Candidates</h1>
<p className="text-sm text-slate-500 font-medium">Manage your workforce recruitment pipeline</p>
</div>
{/* Stats Grid 2x2 */}
<div className="grid grid-cols-2 gap-4">
{stats.map((stat, i) => (
<div key={i} className="bg-white p-5 rounded-2xl border border-slate-100 shadow-sm space-y-3">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center ${stat.color}`}>
<stat.icon className="w-5 h-5" />
</div>
<div className="space-y-0.5">
<div className="text-xl font-bold">{stat.value}</div>
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-widest leading-none">{stat.label}</div>
</div>
</div>
))}
</div>
{/* Search & Filter */}
<div className="flex items-center space-x-3">
<div className="relative flex-1 group">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-300 group-focus-within:text-[#141B34] transition-colors" />
<input
type="text"
placeholder="Search candidates..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-4 py-4 bg-white border border-slate-200 rounded-2xl text-sm focus:ring-4 focus:ring-slate-100 focus:border-slate-300 outline-none transition-all"
/>
</div>
<button className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-slate-400 border border-slate-200 active:scale-95 transition-all">
<Filter className="w-5 h-5" />
</button>
</div>
{/* Candidate List */}
<div className="space-y-4 pb-10">
{candidates.map((candidate) => (
<div key={candidate.id} className="bg-white p-5 rounded-3xl border border-slate-100 shadow-sm space-y-5">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="w-14 h-14 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center text-[#141B34] font-bold text-xl">
{candidate.name.charAt(0)}
</div>
<div className="space-y-1">
<h4 className="font-bold text-slate-900 text-base">{candidate.name}</h4>
<div className="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">{candidate.country}</div>
</div>
</div>
<div className="flex space-x-2">
<button className="w-9 h-9 rounded-xl bg-slate-50 flex items-center justify-center text-slate-400 border border-slate-100">
<MessageSquare className="w-4 h-4" />
</button>
<button className="w-9 h-9 rounded-xl bg-slate-50 flex items-center justify-center text-slate-400 border border-slate-100">
<MoreHorizontal className="w-4 h-4" />
</button>
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-2">
<span className="px-3 py-1.5 bg-slate-50 text-slate-600 text-[10px] font-bold uppercase tracking-widest rounded-lg border border-slate-100">
{candidate.role}
</span>
<div className="text-xs font-bold text-slate-900 ml-1">{candidate.salary}</div>
</div>
<div className={`flex items-center space-x-2 px-3 py-1.5 rounded-full border text-[10px] font-bold uppercase tracking-widest ${getStatusStyles(candidate.status)}`}>
<div className={`w-1.5 h-1.5 rounded-full ${getStatusDot(candidate.status)}`} />
<span>{candidate.status}</span>
</div>
</div>
</div>
))}
</div>
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,147 +0,0 @@
import React, { useState, useRef, useEffect } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
ChevronLeft,
MoreVertical,
Send,
Paperclip,
Image as ImageIcon,
Smile,
CheckCheck,
Mic
} from 'lucide-react';
export default function EmployerChatDetail({ id }) {
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([
{ id: 1, sender: 'them', text: 'Hello! Is the site supervisor position still available?', time: '10:30 AM' },
{ id: 2, sender: 'me', text: 'Hi Sarah! Yes, it is. We are currently reviewing applications.', time: '10:32 AM' },
{ id: 3, sender: 'them', text: 'Great! I have over 8 years of experience in Dubai. Would you like me to send my CV?', time: '10:35 AM' },
{ id: 4, sender: 'me', text: 'That would be excellent. Please send it over here.', time: '10:36 AM' },
{ id: 5, sender: 'them', text: 'Sure, here it is. I also have pediatric first aid certification.', time: '10:40 AM' },
]);
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSend = (e) => {
if (e) e.preventDefault();
if (!message.trim()) return;
const newMessage = {
id: messages.length + 1,
sender: 'me',
text: message,
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
};
setMessages([...messages, newMessage]);
setMessage('');
};
// Mock data for the specific chat
const chatInfo = {
id: id,
name: 'Sarah Connor',
initial: 'S',
online: true,
role: 'Site Supervisor'
};
return (
<div className="min-h-screen bg-[#F8FAFC] flex flex-col max-w-md mx-auto relative font-sans text-slate-900">
<Head title={`Chat with ${chatInfo.name}`} />
{/* Chat Header */}
<div className="bg-white px-6 pt-12 pb-6 flex items-center justify-between border-b border-slate-100 sticky top-0 z-50">
<div className="flex items-center space-x-4">
<button onClick={() => window.history.back()} className="p-2 bg-slate-50 rounded-xl text-slate-400 border border-slate-100">
<ChevronLeft className="w-5 h-5" />
</button>
<div className="flex items-center space-x-3">
<div className="relative">
<div className="w-10 h-10 rounded-full bg-[#D8F3FF] flex items-center justify-center text-[#185FA5] font-bold text-sm">
{chatInfo.initial}
</div>
{chatInfo.online && (
<div className="absolute bottom-0 right-0 w-3 h-3 bg-emerald-500 border-2 border-white rounded-full shadow-sm" />
)}
</div>
<div className="space-y-0.5">
<h4 className="font-bold text-sm leading-none">{chatInfo.name}</h4>
<p className="text-[10px] font-bold text-emerald-500 uppercase tracking-widest">Online Now</p>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<button className="p-2.5 text-slate-400 hover:text-[#185FA5] transition-colors">
<MoreVertical className="w-5 h-5" />
</button>
</div>
</div>
{/* Messages Area */}
<div className="flex-1 overflow-y-auto p-6 space-y-6 no-scrollbar pb-32">
<div className="flex justify-center">
<span className="px-4 py-1.5 bg-slate-100 rounded-full text-[10px] font-bold text-slate-400 uppercase tracking-widest">Today</span>
</div>
{messages.map((msg) => (
<div key={msg.id} className={`flex ${msg.sender === 'me' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] space-y-1.5`}>
<div className={`px-5 py-4 rounded-[28px] text-[13px] font-medium leading-relaxed shadow-sm ${
msg.sender === 'me'
? 'bg-[#185FA5] text-white rounded-tr-none'
: 'bg-white text-slate-600 rounded-tl-none border border-slate-100'
}`}>
{msg.text}
</div>
<div className={`flex items-center space-x-1.5 px-2 ${msg.sender === 'me' ? 'justify-end' : 'justify-start'}`}>
<span className="text-[9px] font-bold text-slate-300 uppercase tracking-widest">{msg.time}</span>
{msg.sender === 'me' && <CheckCheck className="w-3 h-3 text-emerald-500" />}
</div>
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
{/* Input Area */}
<form onSubmit={handleSend} className="fixed bottom-0 left-0 right-0 max-w-md mx-auto p-6 bg-white/80 backdrop-blur-xl border-t border-slate-100">
<div className="flex items-center space-x-3 bg-slate-50 border border-slate-100 rounded-[28px] px-4 py-2">
<button type="button" className="p-2 text-slate-300 hover:text-[#185FA5] transition-colors">
<Paperclip className="w-5 h-5" />
</button>
<input
type="text"
placeholder="Type a message..."
value={message}
onChange={(e) => setMessage(e.target.value)}
className="flex-1 bg-transparent border-none text-sm font-medium focus:ring-0 placeholder-slate-300 text-slate-700 h-12"
/>
<div className="flex items-center space-x-1">
<button type="button" className="p-2 text-slate-300">
<Smile className="w-5 h-5" />
</button>
{message.trim() ? (
<button type="submit" className="w-10 h-10 bg-[#185FA5] text-white rounded-full flex items-center justify-center shadow-lg shadow-blue-500/30 active:scale-90 transition-transform">
<Send className="w-4 h-4 ml-0.5" />
</button>
) : (
<button type="button" className="w-10 h-10 bg-slate-100 text-[#185FA5] rounded-full flex items-center justify-center active:scale-90 transition-transform">
<Mic className="w-5 h-5" />
</button>
)}
</div>
</div>
</form>
</div>
);
}

View File

@ -1,100 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
ArrowLeft,
Loader2,
Mail,
ArrowRight,
CheckCircle2
} from 'lucide-react';
export default function EmployerForgotPassword() {
const [email, setEmail] = useState('');
const [isSent, setIsSent] = useState(false);
const [processing, setProcessing] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
setProcessing(true);
setTimeout(() => {
setIsSent(true);
setProcessing(false);
}, 1500);
};
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto">
<Head title="Forgot Password" />
<div className="px-6 pt-12 pb-6">
<button onClick={() => window.history.back()} className="p-2 -ml-2 text-slate-600">
<ArrowLeft className="w-6 h-6" />
</button>
</div>
<div className="flex-1 px-8 pt-4">
{!isSent ? (
<div className="space-y-10">
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">Forgot password</h1>
<p className="text-slate-500 text-sm">No worries, we'll send you reset instructions</p>
</div>
<form onSubmit={handleSubmit} className="space-y-8">
<div className="space-y-1.5">
<label className="text-sm font-semibold text-slate-700 ml-1">Email</label>
<div className="relative">
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="w-full pl-12 pr-4 py-4 bg-slate-50 border border-slate-200 rounded-2xl text-sm focus:bg-white focus:ring-2 focus:ring-[#185FA5]/10 focus:border-[#185FA5] outline-none transition-all"
required
/>
</div>
</div>
<button
type="submit"
disabled={processing}
className="w-full h-16 bg-[#185FA5] text-white rounded-2xl font-bold text-base shadow-lg shadow-blue-500/20 flex items-center justify-center space-x-2 active:scale-[0.98] transition-all"
>
{processing ? <Loader2 className="w-6 h-6 animate-spin" /> : (
<>
<span>Reset password</span>
<ArrowRight className="w-5 h-5" />
</>
)}
</button>
</form>
</div>
) : (
<div className="space-y-8 text-center py-10">
<div className="flex justify-center">
<div className="w-20 h-20 bg-blue-50 rounded-full flex items-center justify-center text-[#185FA5]">
<CheckCircle2 className="w-10 h-10" />
</div>
</div>
<div className="space-y-2">
<h2 className="text-2xl font-bold tracking-tight">Check your email</h2>
<p className="text-sm text-slate-500 leading-relaxed">
We've sent a password reset link to <br />
<span className="font-bold text-slate-900">{email}</span>
</p>
</div>
<div className="pt-4">
<Link
href="/mobile/employer/login"
className="font-bold text-[#185FA5] hover:text-[#144f8a] underline underline-offset-4 text-sm"
>
Back to login
</Link>
</div>
</div>
)}
</div>
</div>
);
}

View File

@ -1,89 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import {
Bookmark,
MessageSquare,
Calendar,
CreditCard,
ArrowRight,
TrendingUp,
Users
} from 'lucide-react';
export default function EmployerHome() {
const stats = [
{ label: 'Shortlisted', value: 4, icon: Bookmark, color: 'bg-blue-50 text-blue-600' },
{ label: 'Messages Sent', value: 18, icon: MessageSquare, color: 'bg-emerald-50 text-emerald-600' },
{ label: 'Days Remaining', value: 24, icon: Calendar, color: 'bg-amber-50 text-amber-500' },
{ label: 'Total Hires', value: 12, icon: Users, color: 'bg-indigo-50 text-indigo-600' },
];
return (
<EmployerMobileLayout>
<Head title="Dashboard" />
<div className="p-6 space-y-8">
{/* Welcome Section */}
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight text-slate-900">Dashboard</h1>
<p className="text-sm text-slate-500 font-medium">Welcome back, John Doe</p>
</div>
{/* Plan Status Card */}
<div className="bg-[#141B34] rounded-[32px] p-8 text-white relative overflow-hidden shadow-2xl">
<div className="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -mr-16 -mt-16 blur-2xl" />
<div className="space-y-6 relative z-10">
<div className="inline-flex items-center space-x-2 bg-white/10 px-3 py-1.5 rounded-full border border-white/10 text-[10px] font-bold uppercase tracking-widest">
<div className="w-1.5 h-1.5 bg-emerald-400 rounded-full" />
<span>Premium Employer Pass</span>
</div>
<div className="space-y-1">
<h2 className="text-3xl font-bold">24 Days</h2>
<p className="text-slate-400 text-xs font-medium uppercase tracking-widest">Remaining in your plan</p>
</div>
<Link href="/mobile/employer/subscription" className="w-full h-14 bg-white text-[#141B34] rounded-2xl font-bold flex items-center justify-center space-x-2 shadow-xl active:scale-[0.98] transition-all text-xs uppercase tracking-widest">
<span>Upgrade Plan</span>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
{/* Stats Stacked */}
<div className="space-y-4">
<h3 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest ml-1">Key Statistics</h3>
<div className="space-y-3">
{stats.map((stat, i) => (
<div key={i} className="bg-white p-5 rounded-2xl border border-slate-100 shadow-sm flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${stat.color}`}>
<stat.icon className="w-6 h-6" />
</div>
<div className="space-y-0.5">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{stat.label}</div>
<div className="text-xl font-bold text-slate-900">{stat.value}</div>
</div>
</div>
<TrendingUp className="w-5 h-5 text-emerald-500 opacity-20" />
</div>
))}
</div>
</div>
{/* Placeholder for Analytics Chart (Full Width) */}
<div className="space-y-4 pb-10">
<h3 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest ml-1">Recruitment Analytics</h3>
<div className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm h-64 flex flex-col items-center justify-center text-center space-y-4">
<div className="w-16 h-16 bg-slate-50 rounded-full flex items-center justify-center text-slate-200">
<TrendingUp className="w-8 h-8" />
</div>
<div className="space-y-1">
<p className="text-sm font-bold text-slate-900">Activity Chart</p>
<p className="text-xs text-slate-400 font-medium">Visualizing your hiring trends over 30 days</p>
</div>
</div>
</div>
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,111 +0,0 @@
import React, { useState } from 'react';
import { useForm, Head, Link } from '@inertiajs/react';
import {
ArrowLeft,
Eye,
EyeOff,
Loader2,
Mail,
Lock
} from 'lucide-react';
export default function EmployerLogin() {
const { data, setData, post, processing, errors } = useForm({
email: '',
password: '',
remember: false,
source: 'mobile',
});
const [showPassword, setShowPassword] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
post('/employer/login');
};
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto">
<Head title="Employer Login" />
{/* Header */}
<div className="px-6 pt-12 pb-6 flex items-center">
<button onClick={() => window.history.back()} className="p-2 -ml-2 text-slate-600 hover:text-slate-900">
<ArrowLeft className="w-6 h-6" />
</button>
</div>
<div className="flex-1 px-8 pt-4 space-y-10">
{/* Title */}
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">Welcome back</h1>
<p className="text-slate-500 text-sm">Please enter your details to sign in</p>
</div>
{/* Login Form */}
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-sm font-semibold text-slate-700 ml-1">Email</label>
<div className="relative">
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
<input
type="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
placeholder="Enter your email"
className="w-full pl-12 pr-4 py-4 bg-slate-50 border border-slate-200 rounded-2xl text-sm focus:bg-white focus:ring-2 focus:ring-[#185FA5]/10 focus:border-[#185FA5] transition-all outline-none"
required
/>
</div>
{errors.email && <p className="text-xs text-red-500 mt-1 ml-1">{errors.email}</p>}
</div>
<div className="space-y-1.5">
<div className="flex items-center justify-between ml-1">
<label className="text-sm font-semibold text-slate-700">Password</label>
<Link href="/mobile/employer/forgot-password" size="sm" className="text-sm font-medium text-[#185FA5] hover:text-[#144f8a]">Forgot?</Link>
</div>
<div className="relative">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
<input
type={showPassword ? 'text' : 'password'}
value={data.password}
onChange={(e) => setData('password', e.target.value)}
placeholder="••••••••"
className="w-full pl-12 pr-12 py-4 bg-slate-50 border border-slate-200 rounded-2xl text-sm focus:bg-white focus:ring-2 focus:ring-[#185FA5]/10 focus:border-[#185FA5] transition-all outline-none"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
</div>
<button
type="submit"
disabled={processing}
className="w-full h-16 bg-[#185FA5] text-white rounded-2xl font-bold text-base shadow-lg shadow-blue-500/20 flex items-center justify-center space-x-2 active:scale-[0.98] transition-all disabled:opacity-70 mt-8"
>
{processing ? <Loader2 className="w-6 h-6 animate-spin" /> : <span>Sign in</span>}
</button>
</form>
{/* Footer */}
<div className="text-center pt-4 pb-10">
<p className="text-sm text-slate-500">
Don't have an account?{' '}
<Link href="/mobile/employer/register" className="font-bold text-[#185FA5] hover:text-[#144f8a] underline underline-offset-4">
Sign up
</Link>
</p>
</div>
</div>
</div>
);
}

View File

@ -1,79 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import { Search, MoreHorizontal, CheckCheck } from 'lucide-react';
export default function EmployerMessages() {
const [searchQuery, setSearchQuery] = useState('');
const chats = [
{ id: 1, name: 'Sarah Connor', preview: 'Is the site supervisor position still available?', time: '2m ago', unread: 2, online: true },
{ id: 2, name: 'Marketplace Support', preview: 'Your account verification is complete.', time: '1h ago', unread: 0, online: true },
{ id: 3, name: 'Maria Garcia', preview: 'I can start from next Monday. Looking forward to it!', time: '3h ago', unread: 0, online: false },
{ id: 4, name: 'Elena Gilbert', preview: 'Thank you for the opportunity.', time: 'Yesterday', unread: 0, online: false },
{ id: 5, name: 'John Smith', preview: 'Attached are my certificates for review.', time: 'Yesterday', unread: 0, online: false },
];
return (
<EmployerMobileLayout>
<Head title="Messages" />
<div className="flex flex-col h-full">
{/* Search Bar */}
<div className="p-6 pb-2">
<div className="relative group">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-300 group-focus-within:text-[#141B34] transition-colors" />
<input
type="text"
placeholder="Search conversations..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-4 py-4 bg-white border border-slate-200 rounded-2xl text-sm focus:ring-4 focus:ring-slate-100 focus:border-slate-300 outline-none transition-all"
/>
</div>
</div>
{/* Chat List */}
<div className="flex-1 overflow-y-auto no-scrollbar pb-10">
<div className="divide-y divide-slate-100">
{chats.map((chat) => (
<Link
key={chat.id}
href={`/mobile/employer/chat/${chat.id}`}
className="flex items-center p-6 bg-white active:bg-slate-50 transition-all space-x-4"
>
<div className="relative shrink-0">
<div className="w-14 h-14 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center text-[#141B34] font-bold text-xl">
{chat.name.charAt(0)}
</div>
{chat.online && (
<div className="absolute bottom-0.5 right-0.5 w-3.5 h-3.5 bg-emerald-500 border-2 border-white rounded-full" />
)}
</div>
<div className="flex-1 min-w-0 space-y-1">
<div className="flex items-center justify-between">
<h4 className="font-bold text-slate-900 text-[15px] truncate">{chat.name}</h4>
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{chat.time}</span>
</div>
<div className="flex items-center justify-between">
<p className={`text-xs truncate max-w-[200px] ${chat.unread > 0 ? 'text-slate-900 font-bold' : 'text-slate-500 font-medium'}`}>
{chat.preview}
</p>
{chat.unread > 0 ? (
<div className="w-5 h-5 bg-[#185FA5] text-white text-[10px] font-bold rounded-full flex items-center justify-center border border-white">
{chat.unread}
</div>
) : (
<CheckCheck className="w-4 h-4 text-slate-300" />
)}
</div>
</div>
</Link>
))}
</div>
</div>
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,96 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
ArrowLeft,
Loader2,
Smartphone
} from 'lucide-react';
export default function EmployerOTP() {
const [otp, setOtp] = useState(['', '', '', '']);
const [timer, setTimer] = useState(59);
const [processing, setProcessing] = useState(false);
useEffect(() => {
const interval = setInterval(() => {
if (timer > 0) setTimer(timer - 1);
}, 1000);
return () => clearInterval(interval);
}, [timer]);
const handleChange = (index, value) => {
if (value.length <= 1 && /^\d*$/.test(value)) {
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
if (value && index < 3) {
document.getElementById(`otp-${index + 1}`).focus();
}
}
};
const handleSubmit = (e) => {
e.preventDefault();
setProcessing(true);
setTimeout(() => {
window.location.href = '/mobile/employer/home';
}, 1500);
};
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto">
<Head title="OTP Verification" />
<div className="px-6 pt-12 pb-6">
<button onClick={() => window.history.back()} className="p-2 -ml-2 text-slate-600">
<ArrowLeft className="w-6 h-6" />
</button>
</div>
<div className="flex-1 px-8 pt-4 space-y-10">
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">Verify email</h1>
<p className="text-slate-500 text-sm">Enter the code sent to your email address</p>
</div>
<form onSubmit={handleSubmit} className="space-y-10">
<div className="flex justify-between space-x-4">
{otp.map((digit, i) => (
<input
key={i}
id={`otp-${i}`}
type="text"
value={digit}
onChange={(e) => handleChange(i, e.target.value)}
className="w-16 h-18 bg-slate-50 border border-slate-200 rounded-2xl text-center text-2xl font-bold text-slate-900 focus:bg-white focus:ring-2 focus:ring-[#185FA5]/10 focus:border-[#185FA5] outline-none transition-all"
maxLength={1}
required
/>
))}
</div>
<div className="space-y-6">
<button
type="submit"
disabled={processing || otp.join('').length < 4}
className="w-full h-16 bg-[#185FA5] text-white rounded-2xl font-bold text-base shadow-lg shadow-blue-500/20 flex items-center justify-center active:scale-[0.98] transition-all disabled:opacity-50"
>
{processing ? <Loader2 className="w-6 h-6 animate-spin" /> : <span>Verify</span>}
</button>
<div className="text-center">
<p className="text-sm text-slate-500">
Didn't receive code? {timer > 0 ? (
<span className="text-[#185FA5] font-medium ml-1">00:{timer < 10 ? `0${timer}` : timer}</span>
) : (
<button type="button" onClick={() => setTimer(59)} className="text-[#185FA5] font-bold hover:underline ml-1">Resend now</button>
)}
</p>
</div>
</div>
</form>
</div>
</div>
);
}

View File

@ -1,135 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import {
User,
Settings,
CreditCard,
Shield,
Bell,
HelpCircle,
LogOut,
ChevronRight,
Camera,
CheckCircle2,
Briefcase,
Building2,
Smartphone,
Globe
} from 'lucide-react';
export default function EmployerProfile() {
const sections = [
{
title: 'Account Settings',
items: [
{ label: 'Personal Information', icon: User, href: '/mobile/employer/profile/edit', color: 'text-blue-500 bg-blue-50' },
{ label: 'Subscription Plan', icon: CreditCard, href: '/mobile/employer/subscription', badge: 'Premium', color: 'text-indigo-500 bg-indigo-50' },
{ label: 'Security & Password', icon: Shield, href: '/mobile/employer/profile/security', color: 'text-emerald-500 bg-emerald-50' },
]
},
{
title: 'Preferences',
items: [
{ label: 'Notification Settings', icon: Bell, href: '/mobile/employer/profile/notifications', color: 'text-orange-500 bg-orange-50' },
{ label: 'Language', icon: Globe, href: '#', detail: 'English (US)', color: 'text-purple-500 bg-purple-50' },
]
},
{
title: 'Support',
items: [
{ label: 'Help & Support', icon: HelpCircle, href: '/mobile/employer/support', color: 'text-slate-500 bg-slate-50' },
]
}
];
return (
<EmployerMobileLayout>
<Head title="My Profile" />
<div className="flex flex-col h-full bg-[#F8F9FA]">
{/* Profile Hero Card */}
<div className="bg-white px-6 pt-10 pb-8 flex flex-col items-center text-center space-y-5 border-b border-slate-100 relative shadow-sm">
<div className="absolute top-0 left-0 w-full h-32 bg-[#141B34] -z-10" />
<div className="relative">
<div className="w-32 h-32 rounded-full bg-slate-100 border-4 border-white flex items-center justify-center text-[#141B34] font-black text-4xl shadow-xl overflow-hidden">
JD
</div>
<button className="absolute bottom-1 right-1 w-10 h-10 bg-[#141B34] text-white rounded-full flex items-center justify-center border-4 border-white shadow-lg active:scale-95 transition-all">
<Camera className="w-5 h-5" />
</button>
</div>
<div className="space-y-1">
<div className="flex items-center justify-center space-x-1.5">
<h2 className="text-2xl font-black text-slate-900 uppercase tracking-tight">John Doe</h2>
<CheckCircle2 className="w-5 h-5 text-blue-500 fill-blue-50" />
</div>
<div className="flex items-center justify-center space-x-2 text-slate-400 font-bold text-xs uppercase tracking-widest">
<Building2 className="w-3.5 h-3.5" />
<span>Al Mansoor Household</span>
</div>
</div>
<div className="flex items-center space-x-3 w-full max-w-[300px] pt-2">
<div className="flex-1 bg-slate-50 p-3 rounded-2xl border border-slate-100">
<div className="text-[9px] font-black text-slate-400 uppercase tracking-widest">Plan</div>
<div className="text-[11px] font-black text-[#141B34] uppercase tracking-widest">Premium</div>
</div>
<div className="flex-1 bg-slate-50 p-3 rounded-2xl border border-slate-100">
<div className="text-[9px] font-black text-slate-400 uppercase tracking-widest">Verified</div>
<div className="text-[11px] font-black text-emerald-500 uppercase tracking-widest">Yes</div>
</div>
</div>
</div>
{/* Settings Menu Sections */}
<div className="p-6 space-y-8 pb-32">
{sections.map((section, idx) => (
<div key={idx} className="space-y-4">
<h3 className="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em] ml-2">{section.title}</h3>
<div className="bg-white rounded-[32px] border border-slate-100 shadow-sm overflow-hidden">
{section.items.map((item, i) => (
<Link
key={i}
href={item.href}
className={`flex items-center justify-between p-5 hover:bg-slate-50 transition-all ${i !== section.items.length - 1 ? 'border-b border-slate-50' : ''
}`}
>
<div className="flex items-center space-x-4">
<div className={`w-11 h-11 rounded-2xl flex items-center justify-center ${item.color} shadow-sm`}>
<item.icon className="w-5 h-5" />
</div>
<span className="text-[13px] font-bold text-slate-900 uppercase tracking-tight">{item.label}</span>
</div>
<div className="flex items-center space-x-3">
{item.badge && (
<span className="px-3 py-1 bg-blue-50 text-blue-600 text-[9px] font-black uppercase tracking-widest rounded-lg border border-blue-100">
{item.badge}
</span>
)}
{item.detail && (
<span className="text-[11px] font-bold text-slate-400">{item.detail}</span>
)}
<ChevronRight className="w-4 h-4 text-slate-300" />
</div>
</Link>
))}
</div>
</div>
))}
<button className="w-full p-6 bg-white text-rose-500 rounded-[32px] font-black text-xs uppercase tracking-[0.2em] flex items-center justify-center space-x-3 active:scale-[0.98] transition-all border border-rose-100 shadow-sm mt-4">
<LogOut className="w-5 h-5" />
<span>Sign Out Account</span>
</button>
<div className="text-center pt-4">
<p className="text-[9px] font-black text-slate-300 uppercase tracking-[0.3em]">Version 2.4.1 (Stable Build)</p>
</div>
</div>
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,376 +0,0 @@
import React, { useState } from 'react';
import { useForm, Head, Link } from '@inertiajs/react';
import {
CheckCircle,
Camera,
Eye,
EyeOff,
X,
Loader2,
FileText,
AlertCircle,
CreditCard,
ShieldCheck,
Lock,
User,
Mail,
Phone as PhoneIcon,
ArrowRight,
ArrowLeft,
Check,
Globe,
ChevronLeft,
ChevronRight,
Zap,
Shield,
Trophy
} from 'lucide-react';
export default function EmployerRegister() {
const [step, setStep] = useState(1);
const { data, setData, post, processing, errors } = useForm({
name: '',
email: '',
phone: '',
password: '',
password_confirmation: '',
emirates_id_front: null,
emirates_id_back: null,
selected_plan: 'premium',
card_number: '',
expiry: '',
cvc: '',
source: 'mobile',
});
const [showPassword, setShowPassword] = useState(false);
const [frontFileName, setFrontFileName] = useState(null);
const [backFileName, setBackFileName] = useState(null);
const plans = [
{ id: 'basic', name: 'Basic Search', price: '99', icon: Zap, color: 'text-blue-500 bg-blue-50', features: ['Browse 500+ workers', '10 Shortlists'] },
{ id: 'premium', name: 'Premium Pass', price: '199', icon: Shield, color: 'text-[#185FA5] bg-blue-50', popular: true, features: ['Unlimited lists', 'Direct Messaging'] },
{ id: 'vip', name: 'VIP Concierge', price: '499', icon: Trophy, color: 'text-amber-500 bg-amber-50', features: ['Assigned Manager', 'Full Warranty'] },
];
const nextStep = () => setStep(prev => prev + 1);
const prevStep = () => setStep(prev => prev - 1);
const handleFileChange = (e, field) => {
const file = e.target.files[0];
if (file) {
setData(field, file);
const sizeMb = (file.size / (1024 * 1024)).toFixed(1);
if (field === 'emirates_id_front') {
setFrontFileName(`${file.name} (${sizeMb}MB)`);
} else {
setBackFileName(`${file.name} (${sizeMb}MB)`);
}
}
};
const removeFile = (field) => {
setData(field, null);
if (field === 'emirates_id_front') {
setFrontFileName(null);
} else {
setBackFileName(null);
}
};
const handleSubmit = (e) => {
e.preventDefault();
post('/employer/register');
};
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto relative overflow-hidden">
<Head title="Enrollment" />
{/* Minimal Header */}
<div className="px-6 pt-12 pb-4 flex items-center justify-between sticky top-0 bg-white/80 backdrop-blur-xl z-50">
<button onClick={() => step > 1 ? prevStep() : window.history.back()} className="p-3 bg-slate-50 rounded-2xl text-slate-400 border border-slate-100 active:scale-95 transition-all">
<ChevronLeft className="w-6 h-6" />
</button>
<div className="flex items-center space-x-1.5 px-4 py-2 bg-slate-50 rounded-full border border-slate-100">
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full animate-pulse" />
<span className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Step {step} / 4</span>
</div>
</div>
<div className="flex-1 px-8 pt-8 space-y-8 overflow-y-auto no-scrollbar pb-32">
{/* Header Text */}
<div className="space-y-2">
<h1 className="text-3xl font-black tracking-tight text-slate-900 leading-none">
{step === 1 && "Create Account"}
{step === 2 && "Identity Vetting"}
{step === 3 && "Choose Plan"}
{step === 4 && "Checkout"}
</h1>
<p className="text-sm font-medium text-slate-400">
{step === 1 && "Start your professional hiring journey."}
{step === 2 && "Verified ID is mandatory for compliance."}
{step === 3 && "Select a package that fits your needs."}
{step === 4 && "Secure payment via encrypted gateway."}
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Step 1: Account Info */}
{step === 1 && (
<div className="space-y-5 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-2">Full Name</label>
<input
type="text"
value={data.name}
onChange={(e) => setData('name', e.target.value)}
placeholder="Enter your full name"
className="w-full px-6 py-4 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] focus:ring-4 focus:ring-blue-500/10 transition-all outline-none"
required
/>
</div>
<div className="space-y-2">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-2">Email Address</label>
<input
type="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
placeholder="your@email.com"
className="w-full px-6 py-4 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] focus:ring-4 focus:ring-blue-500/10 transition-all outline-none"
required
/>
</div>
<div className="space-y-2">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-2">Phone Number</label>
<input
type="text"
value={data.phone}
onChange={(e) => setData('phone', e.target.value)}
placeholder="+971 50 000 0000"
className="w-full px-6 py-4 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] focus:ring-4 focus:ring-blue-500/10 transition-all outline-none"
required
/>
</div>
<div className="space-y-2">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-2">Password</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={data.password}
onChange={(e) => setData('password', e.target.value)}
placeholder="Min. 8 characters"
className="w-full px-6 py-4 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] focus:ring-4 focus:ring-blue-500/10 transition-all outline-none"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-6 top-1/2 -translate-y-1/2 text-slate-300 hover:text-slate-500"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
<button
type="button"
onClick={nextStep}
className="w-full py-5 bg-[#185FA5] text-white rounded-[24px] font-bold text-sm uppercase tracking-widest shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-3 active:scale-[0.98] transition-all mt-4"
>
<span>Continue</span>
<ArrowRight className="w-5 h-5" />
</button>
</div>
)}
{/* Step 2: Emirates ID */}
{step === 2 && (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="bg-emerald-50 p-6 rounded-[32px] flex items-center space-x-4 border border-emerald-100">
<ShieldCheck className="w-8 h-8 text-emerald-500 shrink-0" />
<p className="text-[11px] font-bold text-emerald-800 leading-relaxed uppercase tracking-wider">
Your ID is encrypted and handled securely as per UAE data protection laws.
</p>
</div>
<div className="space-y-4">
{[
{ field: 'emirates_id_front', label: 'EID Front Side', file: frontFileName },
{ field: 'emirates_id_back', label: 'EID Back Side', file: backFileName }
].map((slot) => (
<div key={slot.field} className="space-y-2">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-2">{slot.label}</label>
{slot.file ? (
<div className="relative h-40 bg-slate-50 rounded-[32px] border-2 border-[#185FA5] flex flex-col items-center justify-center p-6 shadow-xl shadow-blue-500/5">
<FileText className="w-10 h-10 text-[#185FA5] mb-2" />
<span className="text-[10px] font-bold text-slate-900 uppercase truncate max-w-full px-4">{slot.file}</span>
<button
type="button"
onClick={() => removeFile(slot.field)}
className="absolute -top-2 -right-2 bg-rose-500 text-white p-3 rounded-full shadow-lg active:scale-90 transition-transform"
>
<X className="w-4 h-4" strokeWidth={3} />
</button>
</div>
) : (
<label className="relative h-40 bg-slate-50 rounded-[32px] border-2 border-dashed border-slate-200 flex flex-col items-center justify-center p-8 cursor-pointer hover:border-[#185FA5] transition-all group">
<div className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center shadow-sm mb-3 group-hover:scale-110 transition-transform">
<Camera className="w-6 h-6 text-slate-300 group-hover:text-[#185FA5]" />
</div>
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest group-hover:text-[#185FA5]">Tap to Upload</span>
<input
type="file"
className="hidden"
onChange={(e) => handleFileChange(e, slot.field)}
/>
</label>
)}
</div>
))}
</div>
<button
type="button"
onClick={nextStep}
disabled={!frontFileName || !backFileName}
className="w-full py-5 bg-[#185FA5] text-white rounded-[24px] font-bold text-sm uppercase tracking-widest shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-3 active:scale-[0.98] transition-all disabled:opacity-50 mt-4"
>
<span>Verify Identity</span>
<ShieldCheck className="w-5 h-5" />
</button>
</div>
)}
{/* Step 3: Plans */}
{step === 3 && (
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-500 pb-10">
{plans.map((p) => (
<button
key={p.id}
type="button"
onClick={() => setData('selected_plan', p.id)}
className={`w-full p-6 rounded-[32px] border-2 text-left transition-all relative overflow-hidden flex items-center justify-between ${
data.selected_plan === p.id
? 'bg-blue-50/50 border-[#185FA5] shadow-lg shadow-blue-500/5'
: 'bg-white border-slate-50 hover:border-slate-100'
}`}
>
<div className="flex items-center space-x-4">
<div className={`w-12 h-12 rounded-2xl flex items-center justify-center ${p.color}`}>
<p.icon className="w-6 h-6" />
</div>
<div className="space-y-0.5">
<div className="flex items-center space-x-2">
<h4 className="text-sm font-bold text-slate-900 uppercase tracking-tight">{p.name}</h4>
{p.popular && <span className="bg-emerald-500 text-white px-2 py-0.5 rounded-full text-[8px] font-bold uppercase tracking-widest">Best</span>}
</div>
<div className="text-[10px] font-medium text-slate-400">{p.features[0]}</div>
</div>
</div>
<div className="text-right">
<div className="text-xl font-black text-[#185FA5]">{p.price} <span className="text-[10px] font-bold">AED</span></div>
</div>
</button>
))}
<button
type="button"
onClick={nextStep}
className="w-full py-5 bg-[#185FA5] text-white rounded-[24px] font-bold text-sm uppercase tracking-widest shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-3 active:scale-[0.98] transition-all mt-4"
>
<span>Continue to Payment</span>
<ArrowRight className="w-5 h-5" />
</button>
</div>
)}
{/* Step 4: Payment */}
{step === 4 && (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
{/* Card Visual */}
<div className="bg-[#141B34] rounded-[32px] p-8 text-white relative overflow-hidden shadow-2xl">
<div className="absolute top-0 right-0 w-32 h-32 bg-[#185FA5]/20 rounded-full blur-3xl -mr-16 -mt-16" />
<div className="flex justify-between items-start mb-10 relative z-10">
<div className="w-12 h-8 bg-amber-400 rounded-lg" />
<CreditCard className="w-8 h-8 text-white/20" />
</div>
<div className="space-y-4 relative z-10">
<div className="text-lg font-bold tracking-[0.2em] h-8 flex items-center">
{data.card_number || '•••• •••• •••• ••••'}
</div>
<div className="flex justify-between border-t border-white/10 pt-4">
<div className="space-y-0.5 text-left">
<div className="text-[9px] font-bold uppercase opacity-40 tracking-widest">Card Holder</div>
<div className="text-[11px] font-bold uppercase tracking-widest truncate max-w-[150px]">{data.name || 'Your Name'}</div>
</div>
<div className="text-right space-y-0.5">
<div className="text-[9px] font-bold uppercase opacity-40 tracking-widest">Expiry</div>
<div className="text-[11px] font-bold uppercase tracking-widest">{data.expiry || 'MM/YY'}</div>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<input
type="text"
value={data.card_number}
onChange={(e) => setData('card_number', e.target.value.replace(/\W/gi, '').replace(/(.{4})/g, '$1 ').trim())}
placeholder="Card Number"
className="w-full px-6 py-4 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] transition-all outline-none"
required
/>
<div className="grid grid-cols-2 gap-4">
<input
type="text"
value={data.expiry}
onChange={(e) => setData('expiry', e.target.value.replace(/^(\d{2})/, '$1/'))}
placeholder="MM/YY"
className="w-full px-6 py-4 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] transition-all outline-none text-center"
required
/>
<input
type="password"
value={data.cvc}
onChange={(e) => setData('cvc', e.target.value)}
placeholder="CVC"
className="w-full px-6 py-4 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] transition-all outline-none text-center"
required
/>
</div>
</div>
<div className="p-6 bg-slate-50 rounded-[32px] flex items-center justify-between border border-slate-100">
<div className="space-y-0.5">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Total to Pay</div>
<div className="text-xl font-black text-slate-900">{plans.find(p => p.id === data.selected_plan)?.price} AED</div>
</div>
<div className="p-3 bg-emerald-50 rounded-2xl">
<Lock className="w-6 h-6 text-emerald-500" />
</div>
</div>
<button
type="submit"
disabled={processing}
className="w-full py-5 bg-emerald-500 text-white rounded-[24px] font-bold text-sm uppercase tracking-widest shadow-xl shadow-emerald-500/20 flex items-center justify-center space-x-3 active:scale-[0.98] transition-all disabled:opacity-70 mt-4"
>
{processing ? <Loader2 className="w-6 h-6 animate-spin" /> : (
<>
<span>Confirm & Pay</span>
<ChevronRight className="w-5 h-5" />
</>
)}
</button>
</div>
)}
</form>
<div className="text-center pt-8 pb-10">
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest">
Already joined? <Link href="/mobile/employer/login" className="text-[#185FA5]">Sign In</Link>
</p>
</div>
</div>
</div>
);
}

View File

@ -1,109 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import {
Bookmark,
MessageSquare,
X,
Briefcase,
DollarSign,
Star,
Globe2,
ArrowRight
} from 'lucide-react';
export default function EmployerShortlist() {
const savedWorkers = [
{ id: 1, name: 'Maria Santos', role: 'Housemaid', rating: '4.9', salary: '1,800 AED', nationality: 'Philippines', img: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Maria', exp: '8 Years' },
{ id: 2, name: 'Elena Gilbert', role: 'Nanny', rating: '4.8', salary: '2,500 AED', nationality: 'Kenya', img: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Elena', exp: '5 Years' },
];
return (
<EmployerMobileLayout>
<Head title="Shortlist" />
<div className="p-6 space-y-8">
{/* Header */}
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">Shortlist</h1>
<p className="text-sm text-slate-500 font-medium">Quick access to your saved candidates</p>
</div>
{savedWorkers.length > 0 ? (
<div className="space-y-4 pb-10">
<div className="flex items-center justify-between px-1">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{savedWorkers.length} Saved Profiles</span>
<button className="text-[10px] font-bold text-rose-500 uppercase tracking-widest">Clear All</button>
</div>
{savedWorkers.map((worker) => (
<div key={worker.id} className="bg-white p-5 rounded-3xl border border-slate-100 shadow-sm space-y-5">
<div className="flex items-start justify-between">
<div className="flex items-center space-x-4">
<div className="w-16 h-16 rounded-2xl bg-slate-50 overflow-hidden border border-slate-200">
<img src={worker.img} alt="worker" className="w-full h-full object-cover" />
</div>
<div className="space-y-1">
<h4 className="font-bold text-slate-900 text-base">{worker.name}</h4>
<div className="flex items-center space-x-2 text-[11px] font-semibold text-slate-400 uppercase tracking-wider">
<Globe2 className="w-3 h-3" />
<span>{worker.nationality}</span>
</div>
</div>
</div>
<button className="w-9 h-9 rounded-xl bg-slate-50 flex items-center justify-center text-slate-300 hover:text-rose-500 transition-colors border border-slate-100">
<X className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="flex items-center space-x-2 text-[11px] font-bold text-slate-600 bg-slate-50 px-3 py-2.5 rounded-xl border border-slate-100">
<Briefcase className="w-3.5 h-3.5 text-slate-400" />
<span>{worker.exp}</span>
</div>
<div className="flex items-center space-x-2 text-[11px] font-bold text-slate-600 bg-slate-50 px-3 py-2.5 rounded-xl border border-slate-100">
<DollarSign className="w-3.5 h-3.5 text-emerald-500" />
<span>{worker.salary}</span>
</div>
</div>
<div className="flex items-center space-x-3 pt-2">
<Link
href={`/mobile/employer/workers/${worker.id}`}
className="flex-1 py-4 bg-[#141B34] text-white rounded-2xl font-bold text-sm shadow-xl shadow-slate-900/10 active:scale-95 transition-all text-center"
>
View Profile
</Link>
<div className="flex items-center space-x-1 px-4 py-4 bg-amber-50 rounded-2xl border border-amber-100">
<Star className="w-4 h-4 text-amber-500 fill-amber-500" />
<span className="text-sm font-bold text-amber-700">{worker.rating}</span>
</div>
</div>
</div>
))}
<div className="pt-4">
<button className="w-full h-16 bg-white border-2 border-slate-100 text-[#141B34] rounded-2xl font-bold text-sm flex items-center justify-center space-x-2 shadow-sm active:scale-95 transition-all">
<MessageSquare className="w-5 h-5" />
<span>Message All</span>
</button>
</div>
</div>
) : (
<div className="flex-1 flex flex-col items-center justify-center space-y-6 pt-20">
<div className="w-24 h-24 bg-white border border-slate-100 rounded-[40px] flex items-center justify-center shadow-sm">
<Bookmark className="w-10 h-10 text-slate-100" />
</div>
<div className="text-center space-y-2 px-10">
<h3 className="text-lg font-bold tracking-tight">Your Shortlist is Empty</h3>
<p className="text-sm text-slate-400 font-medium">Save profiles to compare and hire faster.</p>
</div>
<Link href="/mobile/employer/workers" className="px-10 py-4 bg-[#141B34] text-white rounded-2xl font-bold text-sm shadow-lg active:scale-95 transition-all">
Browse Workers
</Link>
</div>
)}
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,44 +0,0 @@
import React, { useEffect } from 'react';
import { Head, router } from '@inertiajs/react';
import { ShieldCheck } from 'lucide-react';
export default function EmployerSplash() {
useEffect(() => {
const timer = setTimeout(() => {
router.get('/mobile/employer/welcome');
}, 2000);
return () => clearTimeout(timer);
}, []);
return (
<div className="min-h-screen bg-[#D8F3FF] flex flex-col items-center justify-center p-8 max-w-md mx-auto relative overflow-hidden">
<Head title="Welcome to Marketplace" />
{/* Animated Background Elements */}
<div className="absolute top-[-10%] right-[-10%] w-64 h-64 bg-white/40 rounded-full blur-3xl animate-pulse" />
<div className="absolute bottom-[-10%] left-[-10%] w-80 h-80 bg-[#185FA5]/10 rounded-full blur-3xl animate-bounce duration-[3000ms]" />
<div className="relative flex flex-col items-center space-y-8 animate-in fade-in zoom-in duration-1000">
<div className="w-24 h-24 bg-gradient-to-br from-[#185FA5] to-[#2573c0] rounded-[32px] flex items-center justify-center shadow-[0_20px_40px_rgba(24,95,165,0.3)] border-4 border-white/20">
<ShieldCheck className="w-12 h-12 text-white" strokeWidth={2.5} />
</div>
<div className="text-center space-y-2">
<h1 className="text-4xl font-black text-[#141B34] tracking-tighter uppercase leading-none">
Job<span className="text-[#185FA5]">Sift</span>
</h1>
<p className="text-[10px] font-black text-[#185FA5]/60 uppercase tracking-[0.4em] ml-1">Employer Edition</p>
</div>
</div>
<div className="absolute bottom-12 flex flex-col items-center space-y-4">
<div className="flex space-x-1.5">
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full animate-bounce" />
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full animate-bounce [animation-delay:0.2s]" />
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full animate-bounce [animation-delay:0.4s]" />
</div>
<span className="text-[10px] font-black text-[#3B46D1]/40 uppercase tracking-widest">Version 2.0.26</span>
</div>
</div>
);
}

View File

@ -1,97 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import { Check, ShieldCheck, Zap, Star, Trophy } from 'lucide-react';
export default function EmployerSubscription() {
const plans = [
{
id: 'basic',
name: 'Basic Search',
price: '99',
icon: Zap,
color: 'bg-slate-50 text-slate-400',
features: ['Browse 500+ workers', 'Shortlist up to 10', 'Email support']
},
{
id: 'premium',
name: 'Premium Pass',
price: '199',
icon: Star,
color: 'bg-blue-50 text-blue-600',
popular: true,
features: ['Unlimited shortlisting', 'Direct messaging', 'Priority profile view', '24/7 Support']
},
{
id: 'vip',
name: 'VIP Concierge',
price: '499',
icon: Trophy,
color: 'bg-amber-50 text-amber-500',
features: ['Assigned manager', 'Medical guarantee', 'Replacement warranty', 'Legal assistance']
},
];
return (
<EmployerMobileLayout>
<Head title="Subscription" />
<div className="p-6 space-y-8">
{/* Header */}
<div className="text-center space-y-2 py-4">
<h1 className="text-2xl font-bold tracking-tight">Choose Your Plan</h1>
<p className="text-sm text-slate-500 font-medium max-w-[250px] mx-auto">Select a package that fits your hiring needs.</p>
</div>
{/* Plan Cards Stacked */}
<div className="space-y-6 pb-20">
{plans.map((plan) => (
<div key={plan.id} className={`bg-white rounded-[32px] p-8 border shadow-sm relative overflow-hidden transition-all active:scale-[0.98] ${
plan.popular ? 'border-[#141B34] border-2 shadow-xl' : 'border-slate-100'
}`}>
{plan.popular && (
<div className="absolute top-0 right-0 bg-[#141B34] text-white px-6 py-1.5 rounded-bl-2xl text-[9px] font-black uppercase tracking-widest">
Most Popular
</div>
)}
<div className="space-y-6">
<div className="flex items-center space-x-4">
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center ${plan.color}`}>
<plan.icon className="w-7 h-7" />
</div>
<div className="space-y-1">
<h3 className="text-lg font-bold">{plan.name}</h3>
<div className="flex items-baseline space-x-1">
<span className="text-2xl font-black text-slate-900">{plan.price}</span>
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">AED / Mo</span>
</div>
</div>
</div>
<div className="space-y-4">
{plan.features.map((feature, i) => (
<div key={i} className="flex items-center space-x-3">
<div className="w-5 h-5 rounded-full bg-emerald-50 flex items-center justify-center">
<Check className="w-3 h-3 text-emerald-500" strokeWidth={3} />
</div>
<span className="text-xs text-slate-600 font-medium">{feature}</span>
</div>
))}
</div>
<button className={`w-full py-4 rounded-2xl font-bold text-sm uppercase tracking-widest transition-all ${
plan.popular
? 'bg-[#141B34] text-white shadow-lg'
: 'bg-slate-50 text-slate-900 hover:bg-slate-100'
}`}>
Select {plan.name}
</button>
</div>
</div>
))}
</div>
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,83 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Briefcase,
ArrowRight,
Sparkles,
ShieldCheck,
Users
} from 'lucide-react';
export default function EmployerWelcome() {
return (
<div className="min-h-screen bg-[#D8F3FF] flex flex-col max-w-md mx-auto relative overflow-hidden">
<Head title="Welcome" />
{/* Artistic Header Background */}
<div className="h-[45vh] relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-[#185FA5] to-[#2573c0] rounded-b-[60px] shadow-2xl" />
<div className="absolute top-[10%] left-[10%] w-32 h-32 bg-white/10 rounded-full blur-2xl" />
<div className="absolute bottom-[20%] right-[-10%] w-64 h-64 bg-emerald-400/20 rounded-full blur-3xl" />
<div className="relative h-full flex flex-col items-center justify-center p-8 space-y-6 text-center animate-in slide-in-from-top duration-700">
<div className="w-20 h-20 bg-white/20 backdrop-blur-xl rounded-[28px] border border-white/30 flex items-center justify-center shadow-2xl">
<ShieldCheck className="w-10 h-10 text-white" strokeWidth={2.5} />
</div>
<div className="space-y-2">
<h1 className="text-3xl font-black text-white tracking-tighter uppercase leading-none">
Find Top <br /> Talent Fast
</h1>
<p className="text-blue-100/70 text-[11px] font-bold uppercase tracking-[0.2em]">The Future of Recruitment</p>
</div>
</div>
</div>
<div className="flex-1 px-8 pt-10 space-y-10 animate-in slide-in-from-bottom duration-700">
<div className="space-y-6">
<div className="flex items-start space-x-5">
<div className="w-12 h-12 bg-white rounded-2xl flex items-center justify-center shadow-lg shadow-blue-500/5 shrink-0">
<Users className="w-6 h-6 text-[#185FA5]" strokeWidth={2.5} />
</div>
<div className="space-y-1">
<h4 className="font-black text-[#141B34] text-sm uppercase tracking-tight">Verified Workers</h4>
<p className="text-[11px] text-slate-400 font-bold uppercase tracking-tight leading-relaxed">Access 10,000+ pre-vetted domestic and commercial workers.</p>
</div>
</div>
<div className="flex items-start space-x-5">
<div className="w-12 h-12 bg-white rounded-2xl flex items-center justify-center shadow-lg shadow-blue-500/5 shrink-0">
<Sparkles className="w-6 h-6 text-[#22D39B]" strokeWidth={2.5} />
</div>
<div className="space-y-1">
<h4 className="font-black text-[#141B34] text-sm uppercase tracking-tight">AI Matching</h4>
<p className="text-[11px] text-slate-400 font-bold uppercase tracking-tight leading-relaxed">Our smart engine matches workers based on your specific needs.</p>
</div>
</div>
</div>
<div className="space-y-4 pt-4">
<Link
href="/mobile/employer/login"
className="w-full h-18 bg-[#185FA5] text-white rounded-[28px] font-black text-sm uppercase tracking-[0.2em] shadow-[0_15px_35px_rgba(24,95,165,0.3)] flex items-center justify-center space-x-3 active:scale-95 transition-all"
>
<span>Login to Portal</span>
<ArrowRight className="w-5 h-5" strokeWidth={3} />
</Link>
<Link
href="/mobile/employer/register"
className="w-full h-18 bg-white text-[#185FA5] rounded-[28px] font-black text-sm uppercase tracking-[0.2em] shadow-lg flex items-center justify-center active:scale-95 transition-all border-2 border-white"
>
<span>Join as Employer</span>
</Link>
</div>
</div>
<div className="px-8 pb-10 text-center">
<p className="text-[10px] font-black text-slate-400 uppercase tracking-widest leading-loose">
By continuing you agree to our <br />
<span className="text-[#185FA5] underline underline-offset-4">Terms of Service</span>
</p>
</div>
</div>
);
}

View File

@ -1,319 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import EmployerMobileLayout from './Layouts/EmployerMobileLayout';
import {
Search,
Filter,
Globe,
Briefcase,
DollarSign,
Star,
Heart,
MapPin,
ArrowRight,
ChevronLeft,
Bookmark,
CheckCircle2,
Calendar,
Sparkles,
X,
RotateCcw
} from 'lucide-react';
export default function EmployerWorkers() {
const [searchQuery, setSearchQuery] = useState('');
const [showFilters, setShowFilters] = useState(false);
const [activeCat, setActiveCat] = useState('All');
const [filters, setFilters] = useState({
nationality: 'All Nationalities',
availability: 'All Availabilities',
experience: 'All Experience',
religion: 'All Religions',
maxSalary: 3000
});
const categories = ['All', 'Housemaid', 'Nanny', 'Cook', 'Driver', 'Gardener'];
const filterOptions = {
nationalities: ['All Nationalities', 'Philippines', 'India', 'Sri Lanka', 'Kenya', 'Ethiopia', 'Indonesia'],
availabilities: ['All Availabilities', 'Available Now', 'Available in 1 week', 'Available in 1 month'],
experienceLevels: ['All Experience', '0-2 Years', '2-5 Years', '5-10 Years', '10+ Years'],
religions: ['All Religions', 'Christianity', 'Islam', 'Hinduism', 'Other']
};
const workers = [
{
id: 1,
name: 'Maria Santos',
role: 'Childcare',
salary: '1800 AED / mo',
nationality: 'Philippines',
initial: 'M',
bio: '"Experienced nanny with 6 years working with expatriate families in Dubai. Certified in pediatric first aid."',
exp: '5+ Years',
avail: 'Immediate',
religion: 'Christian',
age: '32 yrs',
skills: ['Childcare', 'Cooking', 'Housekeeping', 'Ironing']
},
{
id: 2,
name: 'Elena Gilbert',
role: 'Housemaid',
salary: '2500 AED / mo',
nationality: 'Kenya',
initial: 'E',
bio: '"Professional housekeeper with expertise in deep cleaning and organizing. Fluent in English and Swahili."',
exp: '3+ Years',
avail: 'Immediate',
religion: 'Christian',
age: '28 yrs',
skills: ['Cleaning', 'Ironing', 'Bilingual']
}
];
const resetFilters = () => {
setFilters({
nationality: 'All Nationalities',
availability: 'All Availabilities',
experience: 'All Experience',
religion: 'All Religions',
maxSalary: 3000
});
setSearchQuery('');
};
return (
<EmployerMobileLayout>
<Head title="Find Workers" />
<div className="flex flex-col h-full bg-[#F8FAFC]">
{/* Header */}
<div className="pt-12 pb-6 px-6 space-y-6 bg-white sticky top-0 z-40 shadow-sm">
<div className="flex items-center space-x-4">
<button onClick={() => window.history.back()} className="p-2 bg-slate-50 rounded-xl text-slate-400 border border-slate-100">
<ChevronLeft className="w-5 h-5" />
</button>
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">Find Workers</h1>
</div>
{/* Search Bar */}
<div className="flex items-center space-x-3">
<div className="flex-1 relative group">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-300 group-focus-within:text-[#185FA5] transition-colors" />
<input
type="text"
placeholder="Search by name or skill..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-4 py-4 bg-slate-50 border border-slate-100 rounded-2xl text-sm focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all"
/>
</div>
<button
onClick={() => setShowFilters(true)}
className={`w-14 h-14 rounded-2xl flex items-center justify-center border transition-all ${
showFilters ? 'bg-[#185FA5] text-white border-[#185FA5]' : 'bg-white text-slate-400 border-slate-100'
}`}
>
<Filter className="w-5 h-5" />
</button>
</div>
{/* Category Tabs */}
<div className="flex space-x-2 overflow-x-auto no-scrollbar pb-1">
{categories.map((cat) => (
<button
key={cat}
onClick={() => setActiveCat(cat)}
className={`shrink-0 px-5 py-2.5 rounded-full text-[10px] font-bold uppercase tracking-widest transition-all ${
activeCat === cat
? 'bg-[#185FA5] text-white shadow-lg'
: 'bg-white text-slate-400 border border-slate-100 hover:bg-slate-50'
}`}
>
{cat}
</button>
))}
</div>
</div>
{/* Worker Listing */}
<div className="px-6 py-8 space-y-8 pb-32">
{workers.map((worker) => (
<div key={worker.id} className="bg-white rounded-[32px] border border-slate-100 shadow-sm overflow-hidden flex flex-col">
{/* Card Header Section */}
<div className="p-6 bg-[#F8FAFC] flex items-center justify-between border-b border-slate-50">
<div className="flex items-center space-x-4">
<div className="w-16 h-16 rounded-full bg-[#D8F3FF] flex items-center justify-center text-[#185FA5] font-black text-2xl shadow-inner">
{worker.initial}
</div>
<div className="space-y-1">
<div className="flex items-center space-x-1.5">
<h4 className="font-bold text-slate-900 text-lg tracking-tight">{worker.name}</h4>
<CheckCircle2 className="w-4 h-4 text-emerald-500" />
</div>
<div className="flex items-center space-x-1.5 text-slate-400 font-semibold text-xs tracking-wide">
<Globe className="w-3.5 h-3.5" />
<span>{worker.nationality}</span>
</div>
</div>
</div>
<button className="w-10 h-10 rounded-full bg-[#EBF5FF] flex items-center justify-center text-[#185FA5]">
<Bookmark className="w-5 h-5 fill-current" />
</button>
</div>
{/* Card Body Section */}
<div className="p-6 space-y-6">
<div className="flex items-center justify-between">
<div className="px-4 py-2 bg-[#F0F7FF] text-[#185FA5] rounded-xl text-xs font-bold border border-[#E1EFFF]">
{worker.role}
</div>
<div className="flex items-center space-x-1.5">
<DollarSign className="w-4 h-4 text-emerald-500" />
<span className="text-sm font-bold text-slate-900">{worker.salary}</span>
</div>
</div>
<p className="text-[13px] text-slate-500 font-medium leading-relaxed italic">
{worker.bio}
</p>
{/* Stats Grid 2x2 */}
<div className="bg-[#F8FAFC] p-4 rounded-2xl grid grid-cols-2 gap-4 border border-slate-50">
<div className="flex items-center space-x-2.5">
<Briefcase className="w-4 h-4 text-slate-400" />
<div className="text-[11px] font-bold text-slate-600">Exp: {worker.exp}</div>
</div>
<div className="flex items-center space-x-2.5">
<Calendar className="w-4 h-4 text-slate-400" />
<div className="text-[11px] font-bold text-slate-600">Avail: {worker.avail}</div>
</div>
<div className="flex items-center space-x-2.5">
<Heart className="w-4 h-4 text-slate-400" />
<div className="text-[11px] font-bold text-slate-600">{worker.religion}</div>
</div>
<div className="flex items-center space-x-2.5">
<Sparkles className="w-4 h-4 text-slate-400" />
<div className="text-[11px] font-bold text-slate-600">Age: {worker.age}</div>
</div>
</div>
{/* Skills Tags */}
<div className="flex flex-wrap gap-2">
{worker.skills.map((skill) => (
<span key={skill} className="px-3 py-1.5 bg-slate-50 text-slate-500 text-[10px] font-bold rounded-lg border border-slate-100">
{skill}
</span>
))}
</div>
<Link
href={`/mobile/employer/workers/${worker.id}`}
className="w-full py-4 bg-[#185FA5] text-white rounded-2xl font-bold text-sm tracking-tight active:scale-[0.98] transition-all flex items-center justify-center shadow-lg shadow-blue-500/20"
>
View Full Profile
</Link>
</div>
</div>
))}
</div>
{/* Filter Drawer - Simple UI Style */}
{showFilters && (
<div className="fixed inset-0 z-[100] flex items-end justify-center px-4 sm:px-0">
<div className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm" onClick={() => setShowFilters(false)} />
<div className="relative w-full max-w-md bg-white rounded-t-[40px] shadow-2xl p-8 pb-12 animate-in slide-in-from-bottom duration-300 overflow-y-auto max-h-[85vh] no-scrollbar">
<div className="flex items-center justify-between mb-8">
<div className="flex items-center space-x-3">
<button onClick={resetFilters} className="p-2 text-slate-400 hover:text-[#185FA5] transition-colors">
<RotateCcw className="w-5 h-5" />
</button>
<h2 className="text-xl font-bold">Filters</h2>
</div>
<button onClick={() => setShowFilters(false)} className="w-10 h-10 bg-slate-100 rounded-full flex items-center justify-center text-slate-500 hover:text-rose-500 transition-all">
<X className="w-5 h-5" strokeWidth={3} />
</button>
</div>
<div className="space-y-8">
{/* Nationality */}
<div className="space-y-3">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-1">Nationality</label>
<select
value={filters.nationality}
onChange={(e) => setFilters({...filters, nationality: e.target.value})}
className="w-full px-5 py-4 bg-slate-50 border border-slate-100 rounded-2xl text-sm font-bold appearance-none focus:bg-white focus:border-[#185FA5] outline-none transition-all"
>
{filterOptions.nationalities.map(n => <option key={n} value={n}>{n}</option>)}
</select>
</div>
{/* Availability */}
<div className="space-y-3">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-1">Availability</label>
<div className="grid grid-cols-2 gap-2">
{filterOptions.availabilities.map(av => (
<button
key={av}
onClick={() => setFilters({...filters, availability: av})}
className={`px-3 py-3 rounded-xl text-[10px] font-bold text-center border transition-all ${
filters.availability === av
? 'bg-[#185FA5] border-[#185FA5] text-white shadow-lg shadow-blue-500/20'
: 'bg-white border-slate-100 text-slate-400'
}`}
>
{av === 'All Availabilities' ? 'Any' : av.replace('Available ', '')}
</button>
))}
</div>
</div>
{/* Experience */}
<div className="space-y-3">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-1">Experience</label>
<select
value={filters.experience}
onChange={(e) => setFilters({...filters, experience: e.target.value})}
className="w-full px-5 py-4 bg-slate-50 border border-slate-100 rounded-2xl text-sm font-bold appearance-none focus:bg-white focus:border-[#185FA5] outline-none transition-all"
>
{filterOptions.experienceLevels.map(ex => <option key={ex} value={ex}>{ex}</option>)}
</select>
</div>
{/* Salary Slider */}
<div className="space-y-4 pt-2">
<div className="flex items-center justify-between px-1">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Max Salary</label>
<span className="text-sm font-bold text-[#185FA5]">{filters.maxSalary} AED</span>
</div>
<input
type="range"
min="1200"
max="3000"
step="100"
value={filters.maxSalary}
onChange={(e) => setFilters({...filters, maxSalary: Number(e.target.value)})}
className="w-full h-2 bg-slate-100 rounded-full appearance-none cursor-pointer accent-[#185FA5]"
/>
<div className="flex justify-between text-[10px] font-bold text-slate-300 uppercase px-1">
<span>1200 AED</span>
<span>3000 AED</span>
</div>
</div>
<button
onClick={() => setShowFilters(false)}
className="w-full py-5 bg-[#185FA5] text-white rounded-3xl font-bold text-base shadow-xl shadow-blue-500/30 active:scale-[0.98] transition-all mt-4"
>
Apply Filters
</button>
</div>
</div>
</div>
)}
</div>
</EmployerMobileLayout>
);
}

View File

@ -1,61 +0,0 @@
import React from 'react';
import { Link, usePage } from '@inertiajs/react';
import {
LayoutGrid,
Search,
Users,
MessageSquare,
User,
Bell
} from 'lucide-react';
export default function EmployerMobileLayout({ children, title }) {
const { url } = usePage();
const navItems = [
{ label: 'Home', icon: LayoutGrid, href: '/mobile/employer/home' },
{ label: 'Find Workers', icon: Search, href: '/mobile/employer/workers' },
{ label: 'Candidates', icon: Users, href: '/mobile/employer/candidates' },
{ label: 'Chat', icon: MessageSquare, href: '/mobile/employer/messages', badge: 3 },
{ label: 'Profile', icon: User, href: '/mobile/employer/profile' },
];
const isActive = (href) => url.startsWith(href);
return (
<div className="min-h-screen bg-[#F8F9FA] font-sans text-[#141B34] flex flex-col max-w-md mx-auto relative">
{/* Content Area */}
<main className="flex-1 overflow-y-auto pb-24">
{children}
</main>
{/* Bottom Navigation */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white border-t border-[#E5E7EB] px-2 py-2 flex items-center justify-between z-50 h-[70px]">
{navItems.map((item) => {
const active = isActive(item.href);
return (
<Link
key={item.label}
href={item.href}
className={`flex flex-col items-center justify-center flex-1 space-y-1 transition-all relative ${active ? 'text-[#141B34]' : 'text-slate-400'
}`}
>
<div className="relative">
<item.icon className={`w-6 h-6 ${active ? 'fill-[#141B34]' : ''}`} />
{item.badge && (
<div className="absolute -top-1.5 -right-1.5 min-w-[16px] h-4 bg-[#185FA5] text-white text-[8px] font-bold rounded-full flex items-center justify-center border border-white px-1">
{item.badge}
</div>
)}
</div>
<span className="text-[9px] font-bold tracking-tight">{item.label}</span>
{active && (
<div className="absolute bottom-[-8px] w-1.5 h-1.5 bg-[#141B34] rounded-full" />
)}
</Link>
);
})}
</div>
</div>
);
}

View File

@ -1,95 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Briefcase,
User,
ShieldCheck,
ArrowRight,
Globe,
Zap
} from 'lucide-react';
export default function MobileWelcome() {
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden">
<Head title="Welcome" />
{/* Background Decor */}
<div className="absolute top-[-10%] right-[-10%] w-64 h-64 bg-blue-50 rounded-full blur-3xl opacity-50" />
<div className="absolute bottom-[-10%] left-[-10%] w-64 h-64 bg-[#185FA5]/5 rounded-full blur-3xl opacity-50" />
{/* Language Selector */}
<div className="px-6 pt-12 flex justify-end relative z-10">
<button className="flex items-center space-x-2 bg-slate-50 px-4 py-2 rounded-2xl border border-slate-100 active:scale-95 transition-all">
<Globe className="w-4 h-4 text-[#185FA5]" />
<span className="text-[10px] font-black uppercase tracking-widest">English</span>
</button>
</div>
<div className="flex-1 px-8 flex flex-col justify-center space-y-12 relative z-10">
{/* Hero */}
<div className="space-y-6 text-center">
<div className="flex justify-center">
<div className="w-20 h-20 bg-[#185FA5] rounded-[32px] flex items-center justify-center shadow-2xl shadow-blue-500/30">
<ShieldCheck className="w-10 h-10 text-white" />
</div>
</div>
<div className="space-y-2">
<h1 className="text-4xl font-black tracking-tighter uppercase leading-none">Marketplace</h1>
<p className="text-[11px] font-black text-slate-300 uppercase tracking-[0.3em]">Direct Hiring Portal</p>
</div>
</div>
{/* Selection Cards */}
<div className="space-y-4">
<Link
href="/mobile/employer/login"
className="group bg-white p-8 rounded-[40px] border-2 border-slate-50 shadow-sm flex items-center justify-between active:scale-95 transition-all hover:border-[#185FA5] hover:shadow-xl hover:shadow-blue-500/10"
>
<div className="flex items-center space-x-6">
<div className="w-16 h-16 bg-slate-50 rounded-[24px] flex items-center justify-center group-hover:bg-blue-50 transition-colors">
<Briefcase className="w-8 h-8 text-[#185FA5]" />
</div>
<div className="text-left">
<h3 className="text-xl font-black tracking-tight uppercase leading-none mb-2">Employer</h3>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">I want to hire</p>
</div>
</div>
<ArrowRight className="w-6 h-6 text-slate-200 group-hover:text-[#185FA5] transition-colors" />
</Link>
<Link
href="/mobile/worker/register"
className="group bg-white p-8 rounded-[40px] border-2 border-slate-50 shadow-sm flex items-center justify-between active:scale-95 transition-all hover:border-[#185FA5] hover:shadow-xl hover:shadow-blue-500/10"
>
<div className="flex items-center space-x-6">
<div className="w-16 h-16 bg-slate-50 rounded-[24px] flex items-center justify-center group-hover:bg-blue-50 transition-colors">
<User className="w-8 h-8 text-[#185FA5]" />
</div>
<div className="text-left">
<h3 className="text-xl font-black tracking-tight uppercase leading-none mb-2">Worker</h3>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">I want a job</p>
</div>
</div>
<ArrowRight className="w-6 h-6 text-slate-200 group-hover:text-[#185FA5] transition-colors" />
</Link>
</div>
<div className="text-center pt-4">
<div className="flex items-center justify-center space-x-2 text-[10px] font-black text-slate-300 uppercase tracking-widest">
<Zap className="w-3 h-3 text-amber-500 fill-amber-500" />
<span>Trusted by 10k+ users</span>
</div>
</div>
</div>
{/* Footer Links */}
<div className="px-8 pb-12 text-center relative z-10">
<p className="text-[11px] font-black text-slate-400 uppercase tracking-widest leading-loose">
Need Help? <br />
<Link href="/support" className="text-[#185FA5] underline underline-offset-4 decoration-2">Contact Support</Link>
</p>
</div>
</div>
);
}

View File

@ -1,95 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Home,
MessageCircle,
User,
Compass,
Bell,
Calendar,
ChevronRight,
Megaphone,
Search,
ArrowLeft,
Clock
} from 'lucide-react';
export default function WorkerAnnouncements() {
const announcements = [
{ id: 1, title: "NEW PLATFORM UPDATE", date: "2 HOURS AGO", text: "We have simplified the verification process for all workers. Check your profile for details.", category: "SYSTEM" },
{ id: 2, title: "SAFETY GUIDELINES", date: "YESTERDAY", text: "Please review our updated safety protocols for household interviews.", category: "SECURITY" },
{ id: 3, title: "RAMADAN HOURS", date: "2 DAYS AGO", text: "Special support hours during the holy month of Ramadan have been updated.", category: "GENERAL" },
];
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden pb-24">
<Head title="Updates" />
{/* Header */}
<div className="px-6 pt-12 pb-6 bg-white sticky top-0 z-50">
<div className="relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-300" />
<input
type="text"
placeholder="Search updates..."
className="w-full pl-12 pr-6 py-4 bg-[#F8FAFC] border-none rounded-2xl text-[11px] font-black uppercase tracking-tight shadow-sm focus:ring-4 focus:ring-blue-100 outline-none transition-all"
/>
</div>
</div>
<div className="flex-1 px-6 space-y-6 overflow-y-auto">
{/* Section Title */}
<div className="flex items-center space-x-2 pt-4 px-1">
<Megaphone className="w-4 h-4 text-[#185FA5]" />
<h2 className="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em]">Latest Updates</h2>
</div>
{/* Cards */}
<div className="space-y-6 pb-10">
{announcements.map((item) => (
<div key={item.id} className="bg-white p-8 rounded-[40px] border border-slate-100 shadow-xl shadow-slate-200/40 space-y-4 active:scale-[0.98] transition-all">
<div className="flex items-center justify-between">
<span className="bg-blue-50 text-[#185FA5] px-4 py-1.5 rounded-full text-[9px] font-black uppercase tracking-[0.1em]">{item.category}</span>
<div className="flex items-center text-[9px] font-black text-slate-300 uppercase tracking-widest">
<Calendar className="w-3 h-3 mr-1.5" /> {item.date}
</div>
</div>
<div className="space-y-3">
<h3 className="font-black text-slate-900 text-[13px] uppercase tracking-tight leading-tight">{item.title}</h3>
<p className="text-xs text-slate-500 leading-relaxed font-medium">{item.text}</p>
</div>
<button className="flex items-center text-[10px] font-black text-[#185FA5] uppercase tracking-[0.1em] pt-2">
Read More <ChevronRight className="w-3 h-3 ml-1" />
</button>
</div>
))}
</div>
</div>
{/* Bottom Navigation */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white/90 backdrop-blur-xl border-t border-slate-100 px-6 py-4 flex items-center justify-between z-50">
<Link href="/mobile/worker/home" className="flex flex-col items-center space-y-1 text-slate-300">
<Home className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Home</span>
</Link>
<Link href="/mobile/worker/explore" className="flex flex-col items-center space-y-1 text-slate-300">
<Compass className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Explore</span>
</Link>
<Link href="/mobile/worker/announcements" className="flex flex-col items-center space-y-1 text-[#185FA5]">
<Bell className="w-5 h-5 fill-current" />
<span className="text-[8px] font-black uppercase tracking-tight">Updates</span>
</Link>
<Link href="/mobile/worker/chat" className="flex flex-col items-center space-y-1 text-slate-300">
<MessageCircle className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Chat</span>
</Link>
<Link href="/mobile/worker/profile" className="flex flex-col items-center space-y-1 text-slate-300">
<User className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Profile</span>
</Link>
</div>
</div>
);
}

View File

@ -1,88 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Home,
MessageCircle,
User,
Briefcase,
ArrowLeft,
Search,
Send,
MoreVertical,
CheckCheck,
Mic,
Image as ImageIcon,
Paperclip,
Bell
} from 'lucide-react';
export default function WorkerChat() {
const [messages, setMessages] = useState([
{ id: 1, text: "Hello Danial, are you available for an interview?", time: "09:41 AM", sender: "employer" },
{ id: 2, text: "Yes ma'am, I am available tomorrow morning.", time: "09:42 AM", sender: "me" },
{ id: 3, text: "Great! Let's connect at 10 AM.", time: "09:45 AM", sender: "employer" },
]);
const [input, setInput] = useState("");
return (
<div className="min-h-screen bg-[#F9F9F9] font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden">
<Head title="Chat" />
{/* Header */}
<div className="px-6 pt-12 pb-4 bg-white flex items-center justify-between border-b border-slate-50 sticky top-0 z-50">
<button onClick={() => window.history.back()} className="w-10 h-10 flex items-center justify-center text-slate-900">
<ArrowLeft className="w-5 h-5" />
</button>
<div className="text-center">
<h1 className="text-xs font-black uppercase tracking-widest">Al Mansoor</h1>
<span className="text-[8px] text-emerald-500 font-black uppercase tracking-widest">Online</span>
</div>
<button className="w-10 h-10 flex items-center justify-center text-slate-900">
<MoreVertical className="w-5 h-5" />
</button>
</div>
{/* Message Area */}
<div className="flex-1 overflow-y-auto px-6 py-8 space-y-6">
{messages.map((msg) => (
<div key={msg.id} className={`flex ${msg.sender === 'me' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] p-4 rounded-[28px] ${
msg.sender === 'me'
? 'bg-[#185FA5] text-white rounded-tr-none shadow-lg shadow-blue-500/10'
: 'bg-white text-slate-700 rounded-tl-none border border-slate-50 shadow-sm'
}`}>
<p className="text-xs font-medium leading-relaxed">{msg.text}</p>
<div className={`flex items-center justify-end space-x-1 mt-2 ${msg.sender === 'me' ? 'text-blue-100/60' : 'text-slate-300'}`}>
<span className="text-[8px] font-black uppercase">{msg.time}</span>
{msg.sender === 'me' && <CheckCheck className="w-3 h-3" />}
</div>
</div>
</div>
))}
</div>
{/* Input Bar */}
<div className="p-6 bg-white border-t border-slate-50">
<div className="flex items-center space-x-3">
<button className="w-12 h-12 rounded-2xl bg-slate-50 text-slate-400 flex items-center justify-center active:scale-95 transition-all">
<Paperclip className="w-5 h-5" />
</button>
<div className="flex-1 relative">
<input
type="text"
placeholder="Write your message..."
className="w-full pl-6 pr-14 py-4 bg-slate-50 border-none rounded-2xl text-[11px] font-black uppercase tracking-tight focus:ring-4 focus:ring-blue-100 transition-all outline-none"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button className="absolute right-2 top-1/2 -translate-y-1/2 w-10 h-10 bg-[#185FA5] text-white rounded-xl flex items-center justify-center shadow-lg active:scale-95 transition-all">
<Send className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,93 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Home,
MessageCircle,
User,
Search,
ArrowLeft,
CheckCheck,
Compass,
Bell
} from 'lucide-react';
export default function WorkerChatList() {
const chats = [
{ id: 1, name: 'Al Mansoor', message: "Great! Let's connect at 10 AM.", time: '10:45 AM', unread: 2, avatar: 'M' },
{ id: 2, name: 'Maktoom Family', message: 'Thank you for the update.', time: 'Yesterday', unread: 0, avatar: 'S' },
{ id: 3, name: 'Zayed Household', message: 'Are you available for a trial?', time: '2 days ago', unread: 0, avatar: 'E' },
];
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden pb-24">
<Head title="Messages" />
{/* Header */}
<div className="px-6 pt-12 pb-4 bg-white flex items-center justify-between border-b border-slate-50 sticky top-0 z-50">
<button onClick={() => window.history.back()} className="w-10 h-10 flex items-center justify-center text-slate-900">
<ArrowLeft className="w-5 h-5" />
</button>
<h1 className="text-sm font-black uppercase tracking-widest">Messages</h1>
<button className="w-10 h-10 flex items-center justify-center text-slate-900">
<Search className="w-5 h-5" />
</button>
</div>
<div className="flex-1 overflow-y-auto px-6 py-6 space-y-2">
{chats.map((chat) => (
<Link
key={chat.id}
href="/mobile/worker/chat/detail"
className="flex items-center space-x-4 p-4 rounded-[24px] hover:bg-slate-50 transition-all border border-transparent active:border-slate-100 active:scale-[0.98]"
>
<div className="w-14 h-14 rounded-full bg-[#E0E7FF] flex items-center justify-center text-[#185FA5] font-black text-lg shadow-sm border-2 border-white">
{chat.avatar}
</div>
<div className="flex-1 space-y-1">
<div className="flex items-center justify-between">
<h3 className="font-black text-sm text-slate-900 uppercase tracking-tight">{chat.name}</h3>
<span className="text-[9px] font-bold text-slate-300 uppercase">{chat.time}</span>
</div>
<div className="flex items-center justify-between">
<p className={`text-xs line-clamp-1 ${chat.unread > 0 ? 'font-black text-slate-900' : 'text-slate-400'}`}>
{chat.message}
</p>
{chat.unread > 0 ? (
<div className="w-5 h-5 bg-[#185FA5] rounded-full flex items-center justify-center text-[10px] font-black text-white shadow-lg shadow-blue-500/20">
{chat.unread}
</div>
) : (
<CheckCheck className="w-4 h-4 text-[#185FA5]" />
)}
</div>
</div>
</Link>
))}
</div>
{/* Bottom Navigation */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white/90 backdrop-blur-xl border-t border-slate-100 px-6 py-4 flex items-center justify-between z-50">
<Link href="/mobile/worker/home" className="flex flex-col items-center space-y-1 text-slate-300">
<Home className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Home</span>
</Link>
<Link href="#" className="flex flex-col items-center space-y-1 text-slate-300">
<Compass className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Explore</span>
</Link>
<Link href="/mobile/worker/announcements" className="flex flex-col items-center space-y-1 text-slate-300">
<Bell className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Updates</span>
</Link>
<Link href="/mobile/worker/chat" className="flex flex-col items-center space-y-1 text-[#185FA5]">
<MessageCircle className="w-5 h-5 fill-current" />
<span className="text-[8px] font-black uppercase tracking-tight">Chat</span>
</Link>
<Link href="/mobile/worker/profile" className="flex flex-col items-center space-y-1 text-slate-300">
<User className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Profile</span>
</Link>
</div>
</div>
);
}

View File

@ -1,226 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import {
ChevronLeft,
Share2,
CheckCircle2,
Globe,
Briefcase,
Calendar,
Heart,
Sparkles,
DollarSign,
MessageSquare,
Bookmark,
Phone,
FileText,
ShieldCheck,
Languages,
Info,
ArrowRight
} from 'lucide-react';
export default function WorkerDetail({ id }) {
// Mock data for the worker detail
const worker = {
id: id,
name: 'Maria Santos',
role: 'Childcare / Nanny',
salary: '1800 AED / mo',
nationality: 'Philippines',
initial: 'M',
online: true,
bio: 'Experienced and dedicated nanny with over 6 years of experience working with expatriate families in Dubai. Certified in pediatric first aid and CPR. I love working with children and I am skilled in early childhood development activities, meal preparation, and maintaining a clean environment for the kids.',
exp: '6 Years',
avail: 'Immediate',
religion: 'Christian',
age: '32 Years',
languages: ['English (Fluent)', 'Tagalog (Native)'],
education: 'Bachelors in Education',
status: 'Verified',
skills: ['Pediatric First Aid', 'Newborn Care', 'Cooking', 'Housekeeping', 'Storytelling'],
documents: [
{ name: 'Emirates ID', status: 'Verified' },
{ name: 'Medical Certificate', status: 'Vetted' },
{ name: 'Criminal Record Check', status: 'Clean' }
]
};
return (
<div className="min-h-screen bg-[#F8FAFC] flex flex-col max-w-md mx-auto relative font-sans text-slate-900 overflow-x-hidden">
<Head title={`${worker.name} - Profile`} />
{/* Header */}
<div className="fixed top-0 left-0 right-0 max-w-md mx-auto z-50 px-6 py-12 flex items-center justify-between pointer-events-none">
<Link
href="/mobile/employer/workers"
className="p-3 bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl text-slate-400 pointer-events-auto shadow-sm active:scale-95 transition-all"
>
<ChevronLeft className="w-5 h-5" />
</Link>
<div className="flex space-x-2 pointer-events-auto">
<button className="p-3 bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl text-slate-400 shadow-sm active:scale-95 transition-all">
<Bookmark className="w-5 h-5" />
</button>
</div>
</div>
{/* Profile Hero */}
<div className="bg-white pt-32 pb-10 px-6 rounded-b-[48px] shadow-sm relative">
<div className="flex flex-col items-center text-center space-y-4">
<div className="relative">
<div className="w-32 h-32 rounded-full bg-[#D8F3FF] flex items-center justify-center text-[#185FA5] font-black text-4xl shadow-inner border-4 border-white">
{worker.initial}
</div>
<div className="absolute bottom-1 right-1 w-8 h-8 bg-emerald-500 border-4 border-white rounded-full flex items-center justify-center text-white">
<CheckCircle2 className="w-4 h-4" />
</div>
</div>
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">{worker.name}</h1>
<div className="flex items-center justify-center space-x-2 text-slate-400 font-bold text-xs uppercase tracking-widest">
<Globe className="w-3.5 h-3.5 text-[#185FA5]" />
<span>{worker.nationality}</span>
</div>
</div>
<div className="flex items-center space-x-2 pt-2">
<div className="px-4 py-2 bg-[#F0F7FF] text-[#185FA5] rounded-xl text-[10px] font-black uppercase tracking-widest border border-[#E1EFFF]">
{worker.role}
</div>
</div>
</div>
{/* Key Stats Grid */}
<div className="grid grid-cols-2 gap-3 mt-10">
<div className="bg-slate-50/50 p-4 rounded-3xl border border-slate-50 flex items-center space-x-3">
<div className="w-10 h-10 bg-white rounded-xl flex items-center justify-center text-[#185FA5] shadow-sm">
<DollarSign className="w-5 h-5" />
</div>
<div className="space-y-0.5">
<div className="text-[9px] font-bold text-slate-400 uppercase tracking-widest">Salary</div>
<div className="text-[13px] font-black">{worker.salary}</div>
</div>
</div>
<div className="bg-slate-50/50 p-4 rounded-3xl border border-slate-50 flex items-center space-x-3">
<div className="w-10 h-10 bg-white rounded-xl flex items-center justify-center text-emerald-500 shadow-sm">
<Briefcase className="w-5 h-5" />
</div>
<div className="space-y-0.5">
<div className="text-[9px] font-bold text-slate-400 uppercase tracking-widest">Experience</div>
<div className="text-[13px] font-black">{worker.exp}</div>
</div>
</div>
</div>
</div>
{/* Profile Content */}
<div className="p-6 space-y-10 pb-32">
{/* About Section */}
<div className="space-y-4">
<div className="flex items-center space-x-2">
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full" />
<h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Professional Bio</h3>
</div>
<div className="bg-white p-6 rounded-[32px] border border-slate-50 shadow-sm">
<p className="text-[14px] text-slate-600 font-medium leading-relaxed italic">
{worker.bio}
</p>
</div>
</div>
{/* Detailed Information */}
<div className="space-y-4">
<div className="flex items-center space-x-2">
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full" />
<h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Candidate Overview</h3>
</div>
<div className="bg-white rounded-[32px] border border-slate-50 shadow-sm overflow-hidden divide-y divide-slate-50">
<div className="flex items-center justify-between p-5">
<div className="flex items-center space-x-3">
<Calendar className="w-5 h-5 text-slate-300" />
<span className="text-xs font-bold text-slate-500">Availability</span>
</div>
<span className="text-xs font-black text-[#185FA5] uppercase tracking-widest">{worker.avail}</span>
</div>
<div className="flex items-center justify-between p-5">
<div className="flex items-center space-x-3">
<Heart className="w-5 h-5 text-slate-300" />
<span className="text-xs font-bold text-slate-500">Religion</span>
</div>
<span className="text-xs font-black text-slate-700 uppercase tracking-widest">{worker.religion}</span>
</div>
<div className="flex items-center justify-between p-5">
<div className="flex items-center space-x-3">
<Sparkles className="w-5 h-5 text-slate-300" />
<span className="text-xs font-bold text-slate-500">Age</span>
</div>
<span className="text-xs font-black text-slate-700 uppercase tracking-widest">{worker.age}</span>
</div>
<div className="flex items-center justify-between p-5">
<div className="flex items-center space-x-3">
<Languages className="w-5 h-5 text-slate-300" />
<span className="text-xs font-bold text-slate-500">Languages</span>
</div>
<span className="text-[10px] font-black text-slate-700 uppercase tracking-widest">{worker.languages.join(' · ')}</span>
</div>
</div>
</div>
{/* Vetted Documents */}
<div className="space-y-4">
<div className="flex items-center space-x-2">
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full" />
<h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Identity & Verification</h3>
</div>
<div className="grid grid-cols-1 gap-3">
{worker.documents.map((doc, idx) => (
<div key={idx} className="bg-white p-5 rounded-[24px] border border-slate-50 shadow-sm flex items-center justify-between group active:bg-slate-50 transition-colors">
<div className="flex items-center space-x-4">
<div className="w-10 h-10 bg-slate-50 rounded-xl flex items-center justify-center text-slate-300 group-active:text-[#185FA5] transition-colors">
<FileText className="w-5 h-5" />
</div>
<span className="text-xs font-bold text-slate-700">{doc.name}</span>
</div>
<div className="flex items-center space-x-1.5 text-[9px] font-black text-emerald-500 uppercase tracking-widest bg-emerald-50 px-2.5 py-1 rounded-lg">
<ShieldCheck className="w-3 h-3" />
<span>{doc.status}</span>
</div>
</div>
))}
</div>
</div>
{/* Skills Section */}
<div className="space-y-4">
<div className="flex items-center space-x-2">
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full" />
<h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Skills & Expertise</h3>
</div>
<div className="flex flex-wrap gap-2.5">
{worker.skills.map((skill) => (
<span key={skill} className="px-4 py-2 bg-white text-slate-500 text-[11px] font-bold rounded-xl border border-slate-50 shadow-sm">
{skill}
</span>
))}
</div>
</div>
</div>
{/* Floating Action Bar */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto p-6 bg-white/80 backdrop-blur-xl border-t border-slate-100 flex items-center space-x-4 z-50">
<Link
href={`/mobile/employer/chat/${worker.id}`}
className="w-16 h-16 bg-slate-50 text-[#185FA5] rounded-[24px] flex items-center justify-center border border-slate-100 shadow-sm active:scale-95 transition-all"
>
<MessageSquare className="w-6 h-6" />
</Link>
<button className="flex-1 h-16 bg-[#185FA5] text-white rounded-[24px] font-black text-xs uppercase tracking-[0.2em] shadow-xl shadow-blue-500/30 active:scale-[0.98] transition-all flex items-center justify-center space-x-3">
<span>Initiate Hiring</span>
<ArrowRight className="w-4 h-4" />
</button>
</div>
</div>
);
}

View File

@ -1,170 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
ArrowLeft,
Check,
Save,
MapPin,
Briefcase,
Globe
} from 'lucide-react';
export default function WorkerEditProfile() {
const commonSkills = [
{ name: 'Cleaning', icon: '🧼' },
{ name: 'Mason', icon: '🏗️' },
{ name: 'Electric', icon: '⚡' },
{ name: 'Plumb', icon: '🔧' },
{ name: 'Paint', icon: '🎨' },
{ name: 'Helper', icon: '📦' }
];
const commonAreas = ['Marina', 'Deira', 'Bur Dubai', 'Jebel Ali', 'Al Quoz', 'City'];
const commonLangs = ['English', 'Arabic', 'Hindi', 'Urdu', 'Tagalog'];
const [selectedSkills, setSelectedSkills] = useState(['Cleaning']);
const [otherSkill, setOtherSkill] = useState('');
const [showOtherInput, setShowOtherInput] = useState(false);
const [selectedAreas, setSelectedAreas] = useState(['Deira']);
const [selectedLangs, setSelectedLangs] = useState(['English']);
const [saved, setSaved] = useState(false);
const toggle = (val, list, setter) => {
if (list.includes(val)) {
setter(list.filter(item => item !== val));
} else {
setter([...list, val]);
}
};
const handleSave = () => {
setSaved(true);
setTimeout(() => setSaved(false), 2000);
};
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden">
<Head title="Quick Edit" />
{/* Header */}
<div className="px-6 pt-12 pb-6 bg-white sticky top-0 z-50 flex items-center justify-between border-b border-slate-50">
<button onClick={() => window.history.back()} className="w-12 h-12 rounded-2xl bg-slate-50 flex items-center justify-center text-slate-900 border border-slate-100 active:scale-95 transition-all">
<ArrowLeft className="w-6 h-6" />
</button>
<h1 className="text-sm font-black uppercase tracking-tight">Edit Skills</h1>
<div className="w-12" />
</div>
<div className="flex-1 px-8 py-8 space-y-12 overflow-y-auto pb-32">
{/* Work Section */}
<div className="space-y-6">
<div className="flex items-center space-x-3">
<Briefcase className="w-5 h-5 text-[#185FA5]" />
<h2 className="text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">My Skills</h2>
</div>
<div className="grid grid-cols-2 gap-3">
{commonSkills.map((skill) => (
<button
key={skill.name}
onClick={() => toggle(skill.name, selectedSkills, setSelectedSkills)}
className={`p-6 rounded-[32px] border-2 text-center transition-all active:scale-95 flex flex-col items-center space-y-3 ${
selectedSkills.includes(skill.name)
? 'bg-[#185FA5] border-[#185FA5] text-white shadow-xl shadow-blue-500/20'
: 'bg-slate-50 border-slate-50 text-slate-400'
}`}
>
<span className="text-2xl">{skill.icon}</span>
<span className="text-[10px] font-black uppercase tracking-tight">{skill.name}</span>
</button>
))}
{/* Other Skill Button */}
<button
onClick={() => setShowOtherInput(!showOtherInput)}
className={`p-6 rounded-[32px] border-2 text-center transition-all active:scale-95 flex flex-col items-center justify-center space-y-3 ${
showOtherInput || otherSkill
? 'bg-[#185FA5] border-[#185FA5] text-white shadow-xl shadow-blue-500/20'
: 'bg-slate-50 border-slate-50 text-slate-400'
}`}
>
<span className="text-2xl"></span>
<span className="text-[10px] font-black uppercase tracking-tight">Other</span>
</button>
</div>
{showOtherInput && (
<div className="animate-in fade-in slide-in-from-top-2 duration-300">
<input
type="text"
placeholder="Type your skill..."
className="w-full px-6 py-4 bg-slate-50 border-none rounded-2xl text-[11px] font-black uppercase tracking-tight focus:ring-4 focus:ring-blue-100 transition-all outline-none"
value={otherSkill}
onChange={(e) => setOtherSkill(e.target.value)}
autoFocus
/>
</div>
)}
</div>
{/* Locations Section */}
<div className="space-y-6">
<div className="flex items-center space-x-3">
<MapPin className="w-5 h-5 text-[#185FA5]" />
<h2 className="text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">My Locations</h2>
</div>
<div className="flex flex-wrap gap-2">
{commonAreas.map((area) => (
<button
key={area}
onClick={() => toggle(area, selectedAreas, setSelectedAreas)}
className={`px-6 py-4 rounded-2xl border-2 transition-all active:scale-95 ${
selectedAreas.includes(area)
? 'bg-[#185FA5] border-[#185FA5] text-white'
: 'bg-slate-50 border-slate-50 text-slate-400'
}`}
>
<span className="text-[10px] font-black uppercase tracking-tight">{area}</span>
</button>
))}
</div>
</div>
{/* Languages Section */}
<div className="space-y-6">
<div className="flex items-center space-x-3">
<Globe className="w-5 h-5 text-[#185FA5]" />
<h2 className="text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">My Languages</h2>
</div>
<div className="grid grid-cols-2 gap-3">
{commonLangs.map((lang) => (
<button
key={lang}
onClick={() => toggle(lang, selectedLangs, setSelectedLangs)}
className={`py-4 rounded-2xl border-2 transition-all active:scale-95 ${
selectedLangs.includes(lang)
? 'bg-[#185FA5] border-[#185FA5] text-white'
: 'bg-slate-50 border-slate-50 text-slate-400'
}`}
>
<span className="text-[10px] font-black uppercase tracking-tight">{lang}</span>
</button>
))}
</div>
</div>
</div>
{/* Huge Save Button */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto p-8 bg-white/90 backdrop-blur-xl border-t border-slate-50 z-50">
<button
onClick={handleSave}
className={`w-full py-5 rounded-[28px] font-black text-[12px] uppercase tracking-[0.2em] shadow-2xl transition-all flex items-center justify-center space-x-3 active:scale-95 ${
saved ? 'bg-emerald-500 text-white shadow-emerald-500/20' : 'bg-[#185FA5] text-white shadow-blue-500/20'
}`}
>
{saved ? <Check className="w-6 h-6" /> : <Save className="w-6 h-6" />}
<span>{saved ? 'Changes Saved!' : 'Update Profile'}</span>
</button>
</div>
</div>
);
}

View File

@ -1,225 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Home,
Search,
Bell,
MapPin,
ChevronRight,
MessageCircle,
User,
Compass,
SlidersHorizontal,
Briefcase
} from 'lucide-react';
export default function WorkerExplore() {
const [showFilters, setShowFilters] = useState(false);
const categories = ['All', 'Cleaning', 'Driver', 'Mason', 'Electrician', 'Carpenter'];
const [activeCat, setActiveCat] = useState('All');
const jobs = [
{ id: 1, title: 'Concrete Mason', company: 'BuildRight Ltd', salary: 'AED 3,500', loc: 'Deira', type: 'Full-time', bg: 'bg-blue-50/50', icon: '🏗️' },
{ id: 2, title: 'Home Sanitizer', company: 'PureClean', salary: 'AED 2,200', loc: 'Marina', type: 'Part-time', bg: 'bg-emerald-50/50', icon: '🧼' },
{ id: 3, title: 'Light Driver', company: 'Zayed Logistics', salary: 'AED 4,000', loc: 'Al Quoz', type: 'Full-time', bg: 'bg-purple-50/50', icon: '🚗' },
{ id: 4, title: 'General Helper', company: 'Global Pack', salary: 'AED 1,800', loc: 'Jebel Ali', type: 'Full-time', bg: 'bg-rose-50/50', icon: '📦' },
];
return (
<div className="min-h-screen bg-[#F9F9F9] font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden pb-24">
<Head title="Jobs" />
{/* Header / Search Area */}
<div className="px-6 pt-12 pb-6 bg-white space-y-6 sticky top-0 z-50 border-b border-slate-50">
<div className="flex items-center justify-between">
<h1 className="text-xl font-black uppercase tracking-tight">Jobs</h1>
<button
onClick={() => setShowFilters(true)}
className="w-10 h-10 rounded-full bg-slate-50 flex items-center justify-center text-slate-900 border border-slate-100 active:scale-95 transition-all"
>
<SlidersHorizontal className="w-5 h-5" />
</button>
</div>
{/* Search Bar */}
<div className="relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-300" />
<input
type="text"
placeholder="Search jobs, companies..."
className="w-full pl-12 pr-6 py-4 bg-slate-50 border-none rounded-2xl text-[11px] font-black uppercase tracking-tight shadow-sm focus:ring-4 focus:ring-blue-100 outline-none transition-all"
/>
</div>
{/* Category Chips */}
<div className="flex space-x-3 overflow-x-auto pb-1 no-scrollbar">
{categories.map((cat) => (
<button
key={cat}
onClick={() => setActiveCat(cat)}
className={`shrink-0 px-5 py-3 rounded-2xl font-black text-[10px] uppercase tracking-widest transition-all active:scale-95 ${
activeCat === cat
? 'bg-[#185FA5] text-white shadow-lg shadow-blue-500/20'
: 'bg-white text-slate-400 border border-slate-50'
}`}
>
{cat}
</button>
))}
</div>
</div>
<div className="flex-1 overflow-y-auto px-6 py-8 space-y-6 no-scrollbar">
<div className="flex items-center justify-between">
<h3 className="font-black text-[10px] uppercase tracking-[0.2em] text-slate-300">Found {jobs.length} Jobs</h3>
<div className="flex items-center space-x-2 text-[10px] font-black text-[#185FA5] uppercase">
<MapPin className="w-3 h-3" />
<span>Dubai, UAE</span>
</div>
</div>
{/* Job List */}
<div className="space-y-4 pb-10">
{jobs.map((job) => (
<Link
key={job.id}
href="/mobile/worker/job/detail"
className="bg-white p-6 rounded-[32px] border border-slate-100 shadow-sm space-y-5 active:scale-[0.98] transition-all block"
>
<div className="flex items-start justify-between">
<div className="w-12 h-12 rounded-full bg-slate-50 flex items-center justify-center text-slate-900 border border-slate-100/50">
<div className="text-xl">{job.icon}</div>
</div>
<div className="text-right">
<span className="text-lg font-black text-[#185FA5]">{job.salary}</span>
</div>
</div>
<div className="space-y-3">
<h3 className="font-black text-slate-900 text-[17px] tracking-tight leading-tight">{job.title}</h3>
<div className="space-y-1">
<p className="text-sm font-medium text-slate-400 leading-tight">
High street avenue<br />
London, UK
</p>
</div>
<p className="text-sm font-black text-slate-900">From Fri, 17 Dec</p>
</div>
<div className="flex items-center">
<span className="bg-blue-50/50 text-[#185FA5] px-5 py-2 rounded-full text-xs font-black uppercase tracking-tight">
{job.type}
</span>
</div>
</Link>
))}
</div>
</div>
{/* Filter Bottom Sheet */}
{showFilters && (
<div className="fixed inset-0 z-[100] flex items-end justify-center px-4 pb-4">
<div className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm" onClick={() => setShowFilters(false)} />
<div className="relative w-full max-w-md bg-white rounded-[40px] shadow-2xl animate-in slide-in-from-bottom duration-300 overflow-hidden">
{/* Handle */}
<div className="w-full flex justify-center py-4">
<div className="w-12 h-1.5 bg-slate-100 rounded-full" />
</div>
<div className="px-8 pb-10 space-y-8">
<div className="flex items-center justify-between">
<h2 className="text-xl font-black text-slate-900 tracking-tight">Filter result</h2>
<span className="text-xs font-bold text-slate-400 uppercase tracking-widest">22 result</span>
</div>
<div className="space-y-10 max-h-[60vh] overflow-y-auto no-scrollbar py-2">
{/* Sort By */}
<div className="space-y-4">
<h3 className="text-sm font-black text-slate-900">Sort by</h3>
<div className="flex flex-wrap gap-2.5">
{['Default', 'Latest First', 'Oldest First', 'Price: Low to High', 'Price: High to Low'].map((opt, i) => (
<button key={opt} className={`px-6 py-3 rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all border ${i === 0 ? 'bg-blue-50 border-blue-100 text-[#185FA5]' : 'bg-white border-slate-100 text-slate-400'}`}>
{opt}
</button>
))}
</div>
</div>
{/* Contract Type */}
<div className="space-y-4">
<h3 className="text-sm font-black text-slate-900">Contract type</h3>
<div className="flex flex-wrap gap-2.5">
{['Part time', 'Full time', 'Contractual'].map((opt) => (
<button key={opt} className="px-6 py-3 rounded-2xl bg-white border border-slate-100 text-slate-400 text-[10px] font-black uppercase tracking-widest transition-all">
{opt}
</button>
))}
</div>
</div>
{/* Skill Range */}
<div className="space-y-4">
<h3 className="text-sm font-black text-slate-900">Skill range</h3>
<div className="flex flex-wrap gap-2.5">
{['Junior', 'Medium', 'Expert'].map((opt) => (
<button key={opt} className="px-6 py-3 rounded-2xl bg-white border border-slate-100 text-slate-400 text-[10px] font-black uppercase tracking-widest transition-all">
{opt}
</button>
))}
</div>
</div>
{/* Salary Type */}
<div className="space-y-4">
<h3 className="text-sm font-black text-slate-900">Salary type</h3>
<div className="flex flex-wrap gap-2.5">
{['Hourly Rate', 'Fix Rate'].map((opt) => (
<button key={opt} className="px-6 py-3 rounded-2xl bg-white border border-slate-100 text-slate-400 text-[10px] font-black uppercase tracking-widest transition-all">
{opt}
</button>
))}
</div>
</div>
</div>
{/* Actions */}
<div className="flex items-center space-x-4 pt-4 border-t border-slate-50">
<button onClick={() => setShowFilters(false)} className="flex-1 py-5 bg-blue-50 text-[#185FA5] rounded-[24px] font-black text-[11px] uppercase tracking-[0.2em] active:scale-95 transition-all">
Reset
</button>
<button onClick={() => setShowFilters(false)} className="flex-1 py-5 bg-[#185FA5] text-white rounded-[24px] font-black text-[11px] uppercase tracking-[0.2em] shadow-xl shadow-blue-500/20 active:scale-95 transition-all">
Apply
</button>
</div>
</div>
</div>
</div>
)}
{/* Bottom Navigation */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white/90 backdrop-blur-xl border-t border-slate-100 px-6 py-4 flex items-center justify-between z-50">
<Link href="/mobile/worker/home" className="flex flex-col items-center space-y-1 text-slate-300">
<Home className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Home</span>
</Link>
<Link href="/mobile/worker/explore" className="flex flex-col items-center space-y-1 text-[#185FA5]">
<Briefcase className="w-5 h-5 fill-current" />
<span className="text-[8px] font-black uppercase tracking-tight">Jobs</span>
</Link>
<Link href="/mobile/worker/announcements" className="flex flex-col items-center space-y-1 text-slate-300">
<Bell className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Updates</span>
</Link>
<Link href="/mobile/worker/chat" className="flex flex-col items-center space-y-1 text-slate-300">
<MessageCircle className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Chat</span>
</Link>
<Link href="/mobile/worker/profile" className="flex flex-col items-center space-y-1 text-slate-300">
<User className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Profile</span>
</Link>
</div>
</div>
);
}

View File

@ -1,251 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Home,
Search,
Bell,
Bookmark,
ChevronDown,
MessageCircle,
User,
Briefcase,
Compass,
Globe
} from 'lucide-react';
export default function WorkerHome() {
const [isOpenToWork, setIsOpenToWork] = useState(true);
const [showLangDropdown, setShowLangDropdown] = useState(false);
const [currentLang, setCurrentLang] = useState('EN');
return (
<div className="min-h-screen bg-[#F9F9F9] font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden pb-24">
<Head title="Home" />
{/* Header Area */}
<div className="px-4 pt-12 pb-4 space-y-6 flex-shrink-0">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-full bg-slate-200 border-2 border-white shadow-sm overflow-hidden">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Danial" alt="avatar" />
</div>
<div className="text-left">
<p className="text-[10px] text-slate-400 font-bold">Hi, Danial</p>
<button className="flex items-center space-x-1">
<span className="text-xs font-black text-slate-900">92 High street, London</span>
<ChevronDown className="w-3 h-3 text-slate-400" />
</button>
</div>
</div>
<div className="flex items-center space-x-3">
<div className="relative">
<button
onClick={() => setShowLangDropdown(!showLangDropdown)}
className="flex items-center space-x-2 bg-white px-3 py-2 rounded-2xl shadow-sm border border-slate-50 active:scale-95 transition-all"
>
<Globe className="w-4 h-4 text-[#185FA5]" />
<span className="text-[9px] font-black uppercase tracking-widest text-slate-900">{currentLang}</span>
</button>
{showLangDropdown && (
<div className="absolute top-12 right-0 w-40 bg-white rounded-[24px] shadow-2xl border border-slate-100 py-3 z-[100] animate-in fade-in zoom-in duration-200">
{['English', 'Arabic', 'Tagalog', 'Swahili', 'Hindi'].map((lang) => (
<button
key={lang}
onClick={() => {
setCurrentLang(lang === 'English' ? 'EN' : lang.substring(0, 2).toUpperCase());
setShowLangDropdown(false);
}}
className="w-full px-6 py-3 text-left hover:bg-slate-50 flex items-center justify-between group"
>
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400 group-hover:text-[#185FA5] transition-colors">{lang}</span>
{currentLang === (lang === 'English' ? 'EN' : lang.substring(0, 2).toUpperCase()) && (
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full" />
)}
</button>
))}
</div>
)}
</div>
<button className="w-10 h-10 rounded-full bg-white flex items-center justify-center text-slate-400 shadow-sm border border-slate-50 relative">
<Bell className="w-4 h-4" />
<div className="absolute top-2.5 right-2.5 w-2 h-2 bg-red-500 rounded-full border-2 border-white" />
</button>
</div>
</div>
{/* Search Bar */}
<div className="relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-300" />
<input
type="text"
placeholder="Search for Job, Recruiter..."
className="w-full pl-12 pr-12 py-3.5 bg-white border-none rounded-2xl text-[11px] font-medium shadow-sm focus:ring-2 focus:ring-blue-100 outline-none"
/>
<div className="absolute right-4 top-1/2 -translate-y-1/2">
<span className="text-sm"></span>
</div>
</div>
</div>
<div className="flex-1 overflow-y-auto px-4 space-y-8">
{/* Open to Work Card */}
<button
onClick={() => setIsOpenToWork(!isOpenToWork)}
className={`w-full p-6 rounded-[32px] flex items-center justify-between transition-all duration-500 active:scale-[0.98] ${
isOpenToWork ? 'bg-[#185FA5] text-white shadow-xl shadow-blue-500/20' : 'bg-white text-slate-400 border border-slate-100'
}`}
>
<div className="text-left space-y-1">
<p className={`text-[9px] font-black uppercase tracking-widest ${isOpenToWork ? 'text-blue-100' : 'text-slate-300'}`}>Availability</p>
<h3 className="text-base font-black tracking-tight uppercase">Open to Work</h3>
</div>
<div className={`w-12 h-7 rounded-full relative transition-colors duration-300 ${isOpenToWork ? 'bg-white/20' : 'bg-slate-100'}`}>
<div className={`absolute top-1 w-5 h-5 rounded-full transition-all duration-300 shadow-md ${
isOpenToWork ? 'right-1 bg-white' : 'left-1 bg-white'
}`} />
</div>
</button>
{/* Job Offers Horizontal */}
<div className="space-y-4">
<div className="flex items-center justify-between px-1">
<h3 className="font-black text-sm uppercase tracking-tight text-slate-900">Job offers</h3>
<button className="text-[10px] font-black text-[#185FA5] uppercase tracking-widest">See all</button>
</div>
<div className="flex space-x-4 overflow-x-auto pb-4 no-scrollbar -mx-4 px-4">
{[
{ id: 1, title: 'Driver needed', salary: '$15', icon: '🚗', bg: 'bg-blue-50' },
{ id: 2, title: 'Mason needed', salary: '$25', icon: '🏗️', bg: 'bg-emerald-50' }
].map((offer) => (
<Link key={offer.id} href="/mobile/worker/job/detail" className={`${offer.bg} p-6 rounded-[32px] w-[240px] shrink-0 space-y-4 shadow-sm active:scale-95 transition-all block border border-white/50`}>
<div className="flex items-center justify-between">
<div className="w-10 h-10 rounded-full bg-white flex items-center justify-center text-xl shadow-sm">
{offer.icon}
</div>
<p className="text-sm font-black text-slate-900">{offer.salary}</p>
</div>
<div className="space-y-1">
<h4 className="font-black text-slate-900 text-xs uppercase tracking-tight">{offer.title}</h4>
<p className="text-[9px] text-slate-500 font-bold uppercase tracking-tight">High street avenue London, UK</p>
<p className="text-[9px] text-[#185FA5] font-black uppercase">From Fri, 17 Dec</p>
</div>
<span className="inline-block bg-black text-white px-4 py-1.5 rounded-full text-[8px] font-black uppercase tracking-tight">Part time</span>
</Link>
))}
</div>
</div>
{/* Employer Requests - Compact List */}
<div className="space-y-4">
<div className="px-1 flex items-center justify-between">
<h2 className="text-[12px] font-black uppercase tracking-[0.15em] text-slate-400">Employer Requests</h2>
<span className="bg-[#185FA5]/10 text-[#185FA5] px-2.5 py-1 rounded-lg text-[9px] font-black uppercase">2 Pending</span>
</div>
<div className="space-y-3">
{[
{ id: 1, employer: 'Al Mansoori Group', role: 'Project Mason', img: 'https://api.dicebear.com/7.x/initials/svg?seed=AM' },
{ id: 2, employer: 'Marina Cleaners', role: 'Deep Cleaning', img: 'https://api.dicebear.com/7.x/initials/svg?seed=MC' }
].map((req) => (
<div key={req.id} className="bg-white p-4 rounded-3xl border border-slate-50 shadow-sm flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-xl bg-slate-50 flex items-center justify-center overflow-hidden border border-slate-100 shrink-0">
<img src={req.img} alt="employer" className="w-full h-full object-cover" />
</div>
<div>
<h4 className="font-black text-slate-900 text-xs uppercase tracking-tight leading-none mb-1.5">{req.employer}</h4>
<p className="text-[9px] font-bold text-[#185FA5] uppercase tracking-widest">{req.role}</p>
</div>
</div>
<div className="flex items-center space-x-2">
<button className="w-8 h-8 bg-emerald-50 text-emerald-600 rounded-lg flex items-center justify-center active:scale-90 transition-all">
<span className="text-[10px] font-black"></span>
</button>
<button className="w-8 h-8 bg-rose-50 text-rose-400 rounded-lg flex items-center justify-center active:scale-90 transition-all">
<span className="text-[10px] font-black text-lg">×</span>
</button>
</div>
</div>
))}
</div>
</div>
{/* Recent Job Post - Compact & Professional */}
<div className="space-y-6 pb-12">
<div className="flex items-center justify-between px-1">
<h2 className="text-[12px] font-black uppercase tracking-[0.15em] text-slate-400">Recent job post</h2>
<button className="text-[10px] font-black uppercase tracking-widest text-[#185FA5]">See All</button>
</div>
{/* Category Tabs */}
<div className="flex overflow-x-auto space-x-2 no-scrollbar pb-2 -mx-4 px-4">
{['Cleaner', 'Driver', 'Mason', 'Electrician'].map((cat) => (
<button key={cat} className="bg-white px-5 py-3 rounded-2xl flex items-center space-x-2 shrink-0 border border-slate-100 active:scale-95 transition-all">
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400">{cat}</span>
</button>
))}
</div>
{/* Job Cards - Refined & Compact */}
<div className="space-y-3">
{[
{ title: 'Home Cleaning', salary: '$20', loc: 'Marina, Dubai', icon: '🧼', time: '2h ago' },
{ title: 'Mason Worker', salary: '$30', loc: 'Deira, Dubai', icon: '🏗️', time: '4h ago' },
{ title: 'Plumbing Service', salary: '$45', loc: 'Al Barsha', icon: '🔧', time: '5h ago' }
].map((job, i) => (
<Link
key={i}
href="/mobile/worker/job/detail"
className="bg-white p-5 rounded-[28px] border border-slate-50 shadow-sm flex items-center justify-between active:scale-[0.98] transition-all group"
>
<div className="flex items-center space-x-4">
<div className="w-11 h-11 bg-slate-50 rounded-2xl flex items-center justify-center text-xl group-hover:bg-blue-50 transition-colors">
{job.icon}
</div>
<div className="space-y-1">
<h4 className="font-black text-slate-900 text-sm uppercase tracking-tight leading-none">{job.title}</h4>
<div className="flex items-center space-x-2">
<p className="text-[9px] font-bold text-slate-300 uppercase tracking-widest">{job.loc}</p>
<span className="w-0.5 h-0.5 bg-slate-200 rounded-full" />
<p className="text-[9px] font-bold text-slate-300 uppercase tracking-widest">{job.time}</p>
</div>
</div>
</div>
<div className="text-right">
<p className="text-sm font-black text-[#185FA5] leading-none mb-1">{job.salary}</p>
<p className="text-[8px] font-black text-emerald-500 uppercase tracking-[0.2em] leading-none">Verified</p>
</div>
</Link>
))}
</div>
</div>
</div>
{/* Bottom Navigation */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white/90 backdrop-blur-xl border-t border-slate-100 px-6 py-4 flex items-center justify-between z-50">
<Link href="/mobile/worker/home" className="flex flex-col items-center space-y-1 text-[#185FA5]">
<Home className="w-5 h-5 fill-current" />
<span className="text-[8px] font-black uppercase tracking-tight">Home</span>
</Link>
<Link href="/mobile/worker/explore" className="flex flex-col items-center space-y-1 text-slate-300">
<Compass className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Explore</span>
</Link>
<Link href="/mobile/worker/announcements" className="flex flex-col items-center space-y-1 text-slate-300">
<Bell className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Updates</span>
</Link>
<Link href="/mobile/worker/chat" className="flex flex-col items-center space-y-1 text-slate-300">
<MessageCircle className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Chat</span>
</Link>
<Link href="/mobile/worker/profile" className="flex flex-col items-center space-y-1 text-slate-300">
<User className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Profile</span>
</Link>
</div>
</div>
);
}

View File

@ -1,147 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import {
ArrowLeft,
MoreVertical,
Briefcase,
MapPin,
Users,
ChevronRight,
Clock,
ShieldCheck,
DollarSign,
Star,
Phone,
Calendar
} from 'lucide-react';
export default function WorkerJobDetail() {
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden">
<Head title="Job Details" />
{/* Header */}
<div className="px-6 pt-12 pb-4 bg-white flex items-center justify-between border-b border-slate-50 sticky top-0 z-50">
<button onClick={() => window.history.back()} className="w-10 h-10 flex items-center justify-center text-slate-900">
<ArrowLeft className="w-5 h-5" />
</button>
<h1 className="text-sm font-black uppercase tracking-widest">Details</h1>
<button className="w-10 h-10 flex items-center justify-center text-slate-900">
<MoreVertical className="w-5 h-5" />
</button>
</div>
<div className="flex-1 overflow-y-auto pb-32">
{/* Job Icon & Title */}
<div className="flex flex-col items-center py-10 space-y-6">
<div className="w-20 h-20 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shadow-sm">
<div className="w-14 h-14 rounded-full bg-white shadow-md flex items-center justify-center">
<Briefcase className="w-8 h-8 text-slate-900" />
</div>
</div>
<div className="text-center space-y-2">
<h2 className="text-2xl font-black tracking-tight">Driver needed</h2>
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-widest flex items-center justify-center space-x-1">
<MapPin className="w-3 h-3" />
<span>High street avenue London, UK</span>
</p>
</div>
</div>
{/* Stats Grid */}
<div className="px-6 grid grid-cols-2 gap-4 mb-10">
<div className="bg-[#F9F9F9] p-5 rounded-3xl border border-slate-50 flex items-center space-x-4">
<div className="w-10 h-10 rounded-2xl bg-blue-50 text-[#185FA5] flex items-center justify-center">
<DollarSign className="w-5 h-5" />
</div>
<div>
<p className="text-[9px] text-slate-400 font-black uppercase tracking-widest leading-none mb-1">Salary</p>
<p className="text-xs font-black text-slate-900">$15</p>
</div>
</div>
<div className="bg-[#F9F9F9] p-5 rounded-3xl border border-slate-50 flex items-center space-x-4">
<div className="w-10 h-10 rounded-2xl bg-orange-50 text-orange-500 flex items-center justify-center">
<Clock className="w-5 h-5" />
</div>
<div>
<p className="text-[9px] text-slate-400 font-black uppercase tracking-widest leading-none mb-1">Job type</p>
<p className="text-xs font-black text-slate-900">Part time</p>
</div>
</div>
<div className="bg-[#F9F9F9] p-5 rounded-3xl border border-slate-50 flex items-center space-x-4">
<div className="w-10 h-10 rounded-2xl bg-emerald-50 text-emerald-500 flex items-center justify-center">
<Star className="w-5 h-5" />
</div>
<div>
<p className="text-[9px] text-slate-400 font-black uppercase tracking-widest leading-none mb-1">Skill</p>
<p className="text-xs font-black text-slate-900">Expert</p>
</div>
</div>
<div className="bg-[#F9F9F9] p-5 rounded-3xl border border-slate-50 flex items-center space-x-4">
<div className="w-10 h-10 rounded-2xl bg-purple-50 text-purple-500 flex items-center justify-center">
<ShieldCheck className="w-5 h-5" />
</div>
<div>
<p className="text-[9px] text-slate-400 font-black uppercase tracking-widest leading-none mb-1">Payment</p>
<p className="text-xs font-black text-slate-900">Verified</p>
</div>
</div>
</div>
{/* Description */}
<div className="px-6 space-y-4 mb-10">
<h3 className="font-black text-sm uppercase tracking-tight">Description</h3>
<p className="text-xs text-slate-500 leading-relaxed font-medium">
An outstanding executer that consistently delivers more than expected & start mentoring experience man to drive my vehicle safely and efficiently. <span className="text-[#185FA5] font-black uppercase tracking-widest cursor-pointer">Read More</span>
</p>
</div>
{/* Additional Details */}
<div className="px-6 space-y-6">
<h3 className="font-black text-sm uppercase tracking-tight">Additional details</h3>
<div className="space-y-4">
<div className="flex items-center justify-between py-2 border-b border-slate-50">
<div className="flex items-center space-x-3">
<Briefcase className="w-4 h-4 text-slate-300" />
<span className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Offer type</span>
</div>
<span className="text-[11px] font-black text-slate-900 uppercase">Contractual</span>
</div>
<div className="flex items-center justify-between py-2 border-b border-slate-50">
<div className="flex items-center space-x-3">
<Calendar className="w-4 h-4 text-slate-300" />
<span className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Start from</span>
</div>
<div className="text-right">
<span className="text-[11px] font-black text-slate-900 uppercase block">26 Aug, 2023</span>
<span className="text-[9px] text-slate-400 font-bold uppercase">9:00 am to 6:00 pm</span>
</div>
</div>
<div className="flex items-center justify-between py-2 border-b border-slate-50">
<div className="flex items-center space-x-3">
<Phone className="w-4 h-4 text-slate-300" />
<span className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Contact</span>
</div>
<span className="text-[11px] font-black text-slate-900">(702) 555-0122</span>
</div>
<div className="flex items-center justify-between py-2 border-b border-slate-50">
<div className="flex items-center space-x-3">
<Clock className="w-4 h-4 text-slate-300" />
<span className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Posted</span>
</div>
<span className="text-[11px] font-black text-slate-900 uppercase">2 days ago</span>
</div>
</div>
</div>
</div>
{/* Footer Apply Button */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white/90 backdrop-blur-xl border-t border-slate-100 p-6 z-50">
<button className="w-full bg-[#185FA5] text-white py-5 rounded-[28px] font-black text-[12px] uppercase tracking-[0.2em] shadow-2xl shadow-blue-500/20 active:scale-95 transition-all">
Apply Now
</button>
</div>
</div>
);
}

View File

@ -1,110 +0,0 @@
import React, { useState } from 'react';
import { useForm, Head, Link } from '@inertiajs/react';
import {
ArrowLeft,
Eye,
EyeOff,
Loader2,
Mail,
Lock,
ChevronLeft
} from 'lucide-react';
export default function WorkerLogin() {
const { data, setData, post, processing, errors } = useForm({
email: '',
password: '',
remember: false,
source: 'mobile',
});
const [showPassword, setShowPassword] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
// post('/worker/login');
};
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto">
<Head title="Worker Login" />
{/* Minimal Header */}
<div className="px-6 pt-12 pb-4 flex items-center">
<button onClick={() => window.history.back()} className="p-3 bg-slate-50 rounded-2xl text-slate-400 border border-slate-100 active:scale-95 transition-all">
<ChevronLeft className="w-6 h-6" />
</button>
</div>
<div className="flex-1 px-8 pt-8 space-y-12">
{/* Title Section */}
<div className="space-y-3">
<h1 className="text-4xl font-black tracking-tight text-slate-900 leading-none">Welcome back</h1>
<p className="text-sm font-medium text-slate-400">Please enter your details to sign in</p>
</div>
{/* Login Form */}
<form onSubmit={handleSubmit} className="space-y-8">
<div className="space-y-5">
<div className="space-y-2">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest ml-2">Email Address</label>
<div className="relative">
<Mail className="absolute left-6 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-300" />
<input
type="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
placeholder="Enter your email"
className="w-full pl-14 pr-6 py-5 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] focus:ring-4 focus:ring-blue-500/10 transition-all outline-none"
required
/>
</div>
{errors.email && <p className="text-xs text-red-500 mt-1 ml-2">{errors.email}</p>}
</div>
<div className="space-y-2">
<div className="flex items-center justify-between ml-2">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-widest">Password</label>
<Link href="#" className="text-[11px] font-bold text-[#185FA5] uppercase tracking-widest">Forgot?</label>
</div>
<div className="relative">
<Lock className="absolute left-6 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-300" />
<input
type={showPassword ? 'text' : 'password'}
value={data.password}
onChange={(e) => setData('password', e.target.value)}
placeholder="••••••••"
className="w-full pl-14 pr-14 py-5 bg-slate-50 border border-slate-100 rounded-[24px] text-sm font-medium focus:bg-white focus:border-[#185FA5] focus:ring-4 focus:ring-blue-500/10 transition-all outline-none"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-6 top-1/2 -translate-y-1/2 text-slate-300 hover:text-slate-500"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
</div>
<button
type="submit"
disabled={processing}
className="w-full py-5 bg-[#185FA5] text-white rounded-[24px] font-bold text-sm uppercase tracking-widest shadow-xl shadow-blue-500/20 flex items-center justify-center space-x-3 active:scale-[0.98] transition-all disabled:opacity-70 mt-8"
>
{processing ? <Loader2 className="w-6 h-6 animate-spin" /> : <span>Sign in</span>}
</button>
</form>
{/* Footer */}
<div className="text-center pt-4 pb-10">
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest leading-loose">
Don't have an account? <br />
<Link href="/mobile/worker/register" className="text-[#185FA5] underline underline-offset-4 decoration-4">Sign up here</Link>
</p>
</div>
</div>
</div>
);
}

View File

@ -1,171 +0,0 @@
import React from 'react';
import { Head, Link } from '@inertiajs/react';
import {
Home,
MessageCircle,
User,
Briefcase,
Bell,
ArrowLeft,
Pencil,
Star,
FileText,
Download,
MapPin,
Settings,
ChevronRight,
Compass,
Clock,
ShieldCheck
} from 'lucide-react';
export default function WorkerProfile() {
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden pb-24">
<Head title="Profile" />
{/* Header */}
<div className="px-6 pt-12 pb-4 bg-white flex items-center justify-between border-b border-slate-50 sticky top-0 z-50">
<button onClick={() => window.history.back()} className="w-10 h-10 flex items-center justify-center text-slate-900">
<ArrowLeft className="w-5 h-5" />
</button>
<h1 className="text-sm font-black uppercase tracking-widest">Profile</h1>
<Link href="/mobile/worker/profile/edit" className="w-10 h-10 flex items-center justify-center text-slate-900">
<Pencil className="w-5 h-5" />
</Link>
</div>
<div className="flex-1 overflow-y-auto">
{/* Profile Header */}
<div className="flex flex-col items-center py-8 space-y-4">
<div className="w-28 h-28 rounded-full bg-slate-100 overflow-hidden border-4 border-slate-50 shadow-sm">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Danial" alt="avatar" className="w-full h-full object-cover" />
</div>
<div className="text-center space-y-1 px-10">
<h2 className="text-2xl font-black tracking-tight">Danial Donald</h2>
<p className="text-xs text-slate-400 font-bold leading-relaxed">
4901 Thornridge Cir. Shiloh, Hawaii 81063
</p>
</div>
</div>
{/* Stats Bar */}
<div className="px-6 grid grid-cols-3 gap-3 mb-10">
<div className="bg-[#F9F9F9] p-5 rounded-3xl flex flex-col items-center justify-center space-y-1">
<p className="text-lg font-black">12</p>
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">Complete</p>
</div>
<div className="bg-[#F9F9F9] p-5 rounded-3xl flex flex-col items-center justify-center space-y-1">
<div className="flex items-center space-x-1">
<p className="text-lg font-black">4.8</p>
<Star className="w-4 h-4 text-amber-400 fill-current" />
</div>
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">Ratings</p>
</div>
<div className="bg-[#F9F9F9] p-5 rounded-3xl flex flex-col items-center justify-center space-y-1">
<p className="text-lg font-black">$ 50</p>
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">Salary</p>
</div>
</div>
{/* Content Sections */}
<div className="px-6 space-y-10 pb-10">
<div className="space-y-3">
<h3 className="font-black text-base uppercase tracking-tight">About Danial</h3>
<p className="text-sm text-slate-500 leading-relaxed font-medium">
I am Danial Donald, a passionate professional with over 10 years of experience. Specialized in high-quality service and customer satisfaction.
</p>
</div>
<div className="grid grid-cols-3 gap-6">
<div className="space-y-2">
<p className="text-[10px] text-slate-300 font-black uppercase tracking-widest leading-none">Gender:</p>
<p className="text-sm font-black text-slate-900 uppercase">Male</p>
</div>
<div className="space-y-2">
<p className="text-[10px] text-slate-300 font-black uppercase tracking-widest leading-none">Age:</p>
<p className="text-sm font-black text-slate-900 uppercase">32 years</p>
</div>
<div className="space-y-2">
<p className="text-[10px] text-slate-300 font-black uppercase tracking-widest leading-none">Experience:</p>
<p className="text-sm font-black text-slate-900 uppercase">Expert (10y)</p>
</div>
</div>
<div className="space-y-6">
<div className="space-y-2">
<p className="text-[10px] text-slate-300 font-black uppercase tracking-widest leading-none">Expertise</p>
<p className="text-sm font-black text-slate-900 uppercase tracking-tight">Plumbing, Cleaning, Electrician</p>
</div>
<div className="space-y-3">
<p className="text-[10px] text-slate-300 font-black uppercase tracking-widest leading-none">Languages</p>
<div className="flex flex-wrap gap-2.5 pt-1">
{['English', 'Arabic', 'Tagalog', 'Swahili'].map((lang) => (
<span key={lang} className="px-4 py-2 bg-slate-50 text-slate-500 rounded-full text-[10px] font-black uppercase tracking-widest border border-slate-100 shadow-sm">
{lang}
</span>
))}
</div>
</div>
</div>
<div className="space-y-4">
<h3 className="font-black text-base uppercase tracking-tight">My Documents</h3>
<div className="bg-[#F9F9F9] p-8 rounded-[40px] border border-slate-100 shadow-sm space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-blue-50 text-[#185FA5] rounded-2xl flex items-center justify-center">
<FileText className="w-6 h-6" />
</div>
<div>
<p className="text-sm font-black text-slate-900 uppercase">Passport.JPG</p>
<p className="text-[10px] text-emerald-500 font-black uppercase tracking-widest">Verified</p>
</div>
</div>
<ShieldCheck className="w-6 h-6 text-emerald-500" />
</div>
<div className="pt-6 border-t border-white grid grid-cols-2 gap-6">
<div>
<p className="text-[9px] text-slate-300 font-black uppercase tracking-widest leading-none mb-1.5">Passport No</p>
<p className="text-xs font-black text-slate-700">Z12345678</p>
</div>
<div>
<p className="text-[9px] text-slate-300 font-black uppercase tracking-widest leading-none mb-1.5">Expiry Date</p>
<p className="text-xs font-black text-slate-700">12/12/2028</p>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Bottom Navigation */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white/90 backdrop-blur-xl border-t border-slate-100 px-6 py-4 flex items-center justify-between z-50">
<Link href="/mobile/worker/home" className="flex flex-col items-center space-y-1 text-slate-300">
<Home className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Home</span>
</Link>
<Link href="/mobile/worker/explore" className="flex flex-col items-center space-y-1 text-slate-300">
<Compass className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Explore</span>
</Link>
<Link href="/mobile/worker/announcements" className="flex flex-col items-center space-y-1 text-slate-300">
<Bell className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Updates</span>
</Link>
<Link href="/mobile/worker/chat" className="flex flex-col items-center space-y-1 text-slate-300">
<MessageCircle className="w-5 h-5" />
<span className="text-[8px] font-black uppercase tracking-tight">Chat</span>
</Link>
<Link href="/mobile/worker/profile" className="flex flex-col items-center space-y-1 text-[#185FA5]">
<User className="w-5 h-5 fill-current" />
<span className="text-[8px] font-black uppercase tracking-tight">Profile</span>
</Link>
</div>
</div>
);
}

View File

@ -1,195 +0,0 @@
import React, { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import {
User,
Phone,
MapPin,
ChevronRight,
CheckCircle2,
ArrowLeft,
Camera,
Briefcase,
ShieldCheck,
Globe
} from 'lucide-react';
export default function WorkerRegister() {
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({
name: '',
phone: '',
nationality: 'Kenya',
role: 'Cleaner',
});
const nextStep = () => setStep(prev => prev + 1);
const nationalities = [
{ name: 'Kenya', flag: '🇰🇪' },
{ name: 'Philippines', flag: '🇵🇭' },
{ name: 'India', flag: '🇮🇳' },
{ name: 'Pakistan', flag: '🇵🇰' },
{ name: 'Nepal', flag: '🇳🇵' },
{ name: 'Other', flag: '🌍' }
];
const roles = [
{ name: 'Cleaner', icon: '🧼' },
{ name: 'Driver', icon: '🚗' },
{ name: 'Mason', icon: '🏗️' },
{ name: 'Electrician', icon: '⚡' },
{ name: 'Plumber', icon: '🔧' },
{ name: 'Helper', icon: '📦' }
];
return (
<div className="min-h-screen bg-white font-sans text-slate-900 flex flex-col max-w-md mx-auto shadow-2xl relative overflow-hidden">
<Head title="Join as Worker" />
{/* Top Bar */}
<div className="px-6 pt-12 pb-6 flex items-center justify-between sticky top-0 bg-white z-50">
<button onClick={() => step > 1 ? setStep(step - 1) : window.history.back()} className="w-12 h-12 rounded-2xl bg-slate-50 flex items-center justify-center text-slate-900 border border-slate-100 active:scale-95 transition-all">
<ArrowLeft className="w-6 h-6" />
</button>
<div className="flex items-center space-x-1.5">
{[1, 2, 3].map((i) => (
<div key={i} className={`h-1.5 rounded-full transition-all duration-500 ${step === i ? 'w-8 bg-[#185FA5]' : 'w-2 bg-slate-100'}`} />
))}
</div>
<div className="w-12" />
</div>
<div className="flex-1 px-8 py-4 overflow-y-auto pb-32">
{step === 1 && (
<div className="space-y-8 animate-in fade-in slide-in-from-right-6 duration-500">
<div className="space-y-2">
<h1 className="text-3xl font-black tracking-tight leading-tight">Your basic info.</h1>
<p className="text-slate-400 font-bold text-xs uppercase tracking-[0.2em]">Step 1 of 3</p>
</div>
<div className="space-y-6">
<div className="space-y-3">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Full Name</label>
<div className="relative">
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-300" />
<input
type="text"
placeholder="Enter your name"
className="w-full pl-12 pr-6 py-5 bg-slate-50 border-none rounded-3xl text-sm font-black uppercase tracking-tight focus:ring-4 focus:ring-blue-100 transition-all outline-none"
value={formData.name}
onChange={e => setFormData({...formData, name: e.target.value})}
/>
</div>
</div>
<div className="space-y-3">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Phone Number</label>
<div className="relative">
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-300" />
<input
type="text"
placeholder="+971 XX XXX XXXX"
className="w-full pl-12 pr-6 py-5 bg-slate-50 border-none rounded-3xl text-sm font-black uppercase tracking-tight focus:ring-4 focus:ring-blue-100 transition-all outline-none"
value={formData.phone}
onChange={e => setFormData({...formData, phone: e.target.value})}
/>
</div>
</div>
<div className="space-y-3">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Where are you from?</label>
<div className="grid grid-cols-3 gap-3">
{nationalities.map((nat) => (
<button
key={nat.name}
onClick={() => setFormData({...formData, nationality: nat.name})}
className={`p-4 rounded-3xl border-2 flex flex-col items-center justify-center space-y-2 transition-all active:scale-95 ${
formData.nationality === nat.name ? 'border-[#185FA5] bg-blue-50' : 'border-slate-50 bg-slate-50 text-slate-400'
}`}
>
<span className="text-2xl">{nat.flag}</span>
<span className="text-[9px] font-black uppercase tracking-tight">{nat.name}</span>
</button>
))}
</div>
</div>
</div>
</div>
)}
{step === 2 && (
<div className="space-y-8 animate-in fade-in slide-in-from-right-6 duration-500">
<div className="space-y-2">
<h1 className="text-3xl font-black tracking-tight leading-tight">What do you do?</h1>
<p className="text-slate-400 font-bold text-xs uppercase tracking-[0.2em]">Step 2 of 3</p>
</div>
<div className="grid grid-cols-2 gap-3">
{roles.map(role => (
<button
key={role.name}
onClick={() => setFormData({...formData, role: role.name})}
className={`p-6 rounded-[32px] border-2 text-center transition-all flex flex-col items-center space-y-4 active:scale-95 ${
formData.role === role.name ? 'border-[#185FA5] bg-blue-50 shadow-xl shadow-blue-500/5' : 'border-slate-50 bg-slate-50 text-slate-400'
}`}
>
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center text-3xl ${formData.role === role.name ? 'bg-white shadow-md' : 'bg-white/50'}`}>
{role.icon}
</div>
<span className={`font-black text-[11px] uppercase tracking-[0.1em] ${formData.role === role.name ? 'text-slate-900' : ''}`}>{role.name}</span>
</button>
))}
</div>
</div>
)}
{step === 3 && (
<div className="space-y-8 animate-in fade-in slide-in-from-right-6 duration-500">
<div className="space-y-2">
<h1 className="text-3xl font-black tracking-tight leading-tight">Verify Identity.</h1>
<p className="text-slate-400 font-bold text-xs uppercase tracking-[0.2em]">Final Step</p>
</div>
<div className="space-y-6">
<div className="p-10 border-4 border-dashed border-slate-100 rounded-[40px] bg-slate-50 flex flex-col items-center justify-center text-center space-y-6 active:scale-95 transition-all cursor-pointer hover:border-blue-200">
<div className="w-24 h-24 bg-white rounded-[32px] flex items-center justify-center shadow-xl">
<Camera className="w-12 h-12 text-[#185FA5]" />
</div>
<div className="space-y-2">
<h3 className="font-black text-sm uppercase tracking-tight">Upload Passport</h3>
<p className="text-[10px] font-black text-slate-300 uppercase tracking-widest">TAP TO OPEN CAMERA</p>
</div>
</div>
<div className="p-6 bg-emerald-50 rounded-3xl border border-emerald-100 flex items-start space-x-4">
<ShieldCheck className="w-6 h-6 text-emerald-600 shrink-0" />
<p className="text-[9px] font-black text-emerald-800 uppercase tracking-widest leading-relaxed">
Your data is 100% secure. We only show documents to verified employers.
</p>
</div>
</div>
</div>
)}
</div>
{/* Bottom Button */}
<div className="fixed bottom-0 left-0 right-0 max-w-md mx-auto p-8 bg-white/90 backdrop-blur-xl border-t border-slate-50 z-50">
{step < 3 ? (
<button
onClick={nextStep}
className="w-full bg-[#185FA5] text-white py-5 rounded-[28px] font-black text-[11px] uppercase tracking-[0.2em] shadow-2xl shadow-blue-500/20 active:scale-95 transition-all"
>
Next Step
</button>
) : (
<Link
href="/mobile/worker/home"
className="w-full bg-[#185FA5] text-white py-5 rounded-[28px] font-black text-[11px] uppercase tracking-[0.2em] shadow-2xl shadow-blue-500/20 active:scale-95 transition-all flex items-center justify-center"
>
Join Marketplace
</Link>
)}
</div>
</div>
);
}

View File

@ -6,6 +6,8 @@ import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { Toaster } from 'sonner';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
@ -13,10 +15,15 @@ createInertiaApp({
resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')),
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<App {...props} />);
root.render(
<>
<App {...props} />
<Toaster richColors position="top-right" closeButton />
</>
);
},
progress: {
color: '#4B5563',
color: '#185FA5',
showSpinner: true,
},
});

View File

@ -0,0 +1,128 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verify Your Email</title>
<style>
body {
font-family: 'Geist', 'Segoe UI', Helvetica, Arial, sans-serif;
background-color: #f8fafc;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
}
.container {
max-width: 600px;
margin: 40px auto;
background: #ffffff;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.03);
border: 1px solid #e2e8f0;
}
.header {
background-color: #185FA5;
padding: 40px;
text-align: center;
color: #ffffff;
}
.logo {
font-size: 28px;
font-weight: 900;
letter-spacing: -0.05em;
margin: 0;
text-transform: uppercase;
}
.content {
padding: 48px;
color: #334155;
line-height: 1.6;
}
.greeting {
font-size: 18px;
font-weight: 700;
margin-top: 0;
margin-bottom: 16px;
color: #0f172a;
}
.text {
font-size: 14px;
color: #475569;
margin-bottom: 24px;
}
.otp-container {
background-color: #f1f5f9;
border: 2px dashed #cbd5e1;
border-radius: 16px;
padding: 24px;
text-align: center;
margin: 32px 0;
}
.otp-code {
font-size: 36px;
font-weight: 800;
letter-spacing: 0.15em;
color: #185FA5;
margin: 0;
}
.expiry-warning {
font-size: 12px;
font-weight: 600;
color: #e11d48;
margin-top: 8px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.security-notice {
background-color: #f8fafc;
border-left: 4px solid #185FA5;
padding: 16px;
border-radius: 0 12px 12px 0;
font-size: 12px;
color: #64748b;
margin-bottom: 24px;
}
.footer {
background-color: #f8fafc;
padding: 24px;
text-align: center;
border-top: 1px solid #e2e8f0;
font-size: 11px;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.1em;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 class="logo">Marketplace</h1>
<div style="font-size: 12px; font-weight: 700; letter-spacing: 0.2em; opacity: 0.8; margin-top: 8px; text-transform: uppercase;">Employer Verification</div>
</div>
<div class="content">
<h2 class="greeting">Hello {{ $employerName }},</h2>
<p class="text">
Thank you for starting the registration process for <strong>{{ $companyName }}</strong> on the Marketplace portal. To verify your email address and proceed with setting up your account, please enter the 6-digit one-time passcode (OTP) below.
</p>
<div class="otp-container">
<div class="otp-code">{{ $otp }}</div>
<div class="expiry-warning">Valid for 10 minutes only</div>
</div>
<div class="security-notice">
<strong>Security Alert:</strong> This code is confidential. Our support team will never ask you for this code. Do not share this passcode with anyone.
</div>
<p class="text" style="margin-bottom: 0;">
If you did not initiate this registration, you can safely ignore this email.
</p>
</div>
<div class="footer">
Empowering Direct Household Hiring &bull; Secure Portal
</div>
</div>
</body>
</html>

View File

@ -14,67 +14,7 @@
Route::post('/admin/login', [AdminAuthController::class, 'login'])->name('admin.login.submit');
Route::post('/admin/logout', [AdminAuthController::class, 'logout'])->name('admin.logout');
// Temporary Mobile Mockup Routes for Client Review
Route::get('/mobile/welcome', function () {
return Inertia::render('Mobile/Welcome'); });
Route::get('/mobile/worker/register', function () {
return Inertia::render('Mobile/WorkerRegister'); });
Route::get('/mobile/worker/home', function () {
return Inertia::render('Mobile/WorkerHome'); });
Route::get('/mobile/worker/explore', function () {
return Inertia::render('Mobile/WorkerExplore'); });
Route::get('/mobile/worker/job/detail', function () {
return Inertia::render('Mobile/WorkerJobDetail'); });
Route::get('/mobile/worker/chat', function () {
return Inertia::render('Mobile/WorkerChatList'); });
Route::get('/mobile/worker/chat/detail', function () {
return Inertia::render('Mobile/WorkerChat'); });
Route::get('/mobile/worker/announcements', function () {
return Inertia::render('Mobile/WorkerAnnouncements'); });
Route::get('/mobile/worker/profile', function () {
return Inertia::render('Mobile/WorkerProfile'); });
Route::get('/mobile/worker/profile/edit', function () {
return Inertia::render('Mobile/WorkerEditProfile'); });
Route::get('/mobile/employer/home', function () {
return Inertia::render('Mobile/EmployerHome'); });
Route::get('/mobile/employer/workers', function () {
return Inertia::render('Mobile/EmployerWorkers'); });
Route::get('/mobile/employer/workers/{id}', function ($id) {
return Inertia::render('Mobile/WorkerDetail', ['id' => $id]); });
Route::get('/mobile/employer/shortlist', function () {
return Inertia::render('Mobile/EmployerShortlist'); });
Route::get('/mobile/employer/candidates', function () {
return Inertia::render('Mobile/EmployerCandidates'); });
Route::get('/mobile/employer/messages', function () {
return Inertia::render('Mobile/EmployerMessages'); });
Route::get('/mobile/employer/chat/{id}', function ($id) {
return Inertia::render('Mobile/EmployerChatDetail', ['id' => $id]); });
Route::get('/mobile/employer/subscription', function () {
return Inertia::render('Mobile/EmployerSubscription'); });
Route::get('/mobile/employer/profile', function () {
return Inertia::render('Mobile/EmployerProfile'); });
Route::get('/mobile/employer/profile/edit', function () {
return Inertia::render('Mobile/EmployerProfileEdit'); });
Route::get('/mobile/employer/profile/security', function () {
return Inertia::render('Mobile/EmployerSecurity'); });
Route::get('/mobile/employer/profile/notifications', function () {
return Inertia::render('Mobile/EmployerNotifications'); });
Route::get('/mobile/employer/support', function () {
return Inertia::render('Mobile/EmployerSupport'); });
Route::get('/mobile/employer/announcements', function () {
return Inertia::render('Mobile/EmployerAnnouncements'); });
Route::get('/mobile/employer/splash', function () {
return Inertia::render('Mobile/EmployerSplash'); });
Route::get('/mobile/employer/welcome', function () {
return Inertia::render('Mobile/EmployerWelcome'); });
Route::get('/mobile/employer/login', function () {
return Inertia::render('Mobile/EmployerLogin'); });
Route::get('/mobile/employer/otp', function () {
return Inertia::render('Mobile/EmployerOTP'); });
Route::get('/mobile/employer/forgot-password', function () {
return Inertia::render('Mobile/EmployerForgotPassword'); });
Route::get('/mobile/employer/register', function () {
return Inertia::render('Mobile/EmployerRegister'); });
// Admin Protected Routes
@ -114,7 +54,11 @@
Route::post('/employer/login', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'login'])->name('employer.login.submit');
Route::get('/employer/register', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'showRegister'])->name('employer.register');
Route::post('/employer/register', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'register'])->name('employer.register.submit');
Route::get('/employer/verify-email/{token}', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'verifyEmail'])->name('employer.verify');
Route::get('/employer/verify-email', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'showVerifyEmail'])->name('employer.verify-email');
Route::post('/employer/verify-email', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'verifyEmail'])->name('employer.verify-email.submit')->middleware('throttle:5,1');
Route::post('/employer/resend-otp', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'resendOtp'])->name('employer.resend-otp')->middleware('throttle:3,1');
Route::get('/employer/create-password', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'showCreatePassword'])->name('employer.create-password');
Route::post('/employer/create-password', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'createPassword'])->name('employer.create-password.submit');
Route::post('/employer/logout', [\App\Http\Controllers\Employer\EmployerAuthController::class, 'logout'])->name('employer.logout');
Route::get('/employer/pending-verification', function () {
return Inertia::render('Employer/Auth/PendingVerification');
@ -129,41 +73,42 @@
Route::get('/workers', [\App\Http\Controllers\Employer\WorkerController::class, 'index'])->name('employer.workers');
Route::get('/workers/{id}', [\App\Http\Controllers\Employer\WorkerController::class, 'show'])->name('employer.workers.show');
Route::get('/workers/{id}/hire', function ($id) {
// Mock worker for now - in reality, would fetch from DB
$worker = [
'id' => $id,
'name' => 'Maria Santos',
'nationality' => 'Filipino',
'category' => 'Domestic Helper',
'salary' => 2500,
$worker = \App\Models\Worker::with('category')->findOrFail($id);
$workerData = [
'id' => $worker->id,
'name' => $worker->name,
'nationality' => $worker->nationality,
'category' => $worker->category->name ?? 'General Worker',
'salary' => (int) $worker->expected_salary,
];
return Inertia::render('Employer/Hiring/Confirm', ['worker' => $worker]);
return Inertia::render('Employer/Hiring/Confirm', ['worker' => $workerData]);
})->name('employer.hiring.confirm');
Route::get('/workers/{id}/hire/success', function ($id) {
$worker = [
'id' => $id,
'name' => 'Maria Santos',
$worker = \App\Models\Worker::findOrFail($id);
$workerData = [
'id' => $worker->id,
'name' => $worker->name,
];
return Inertia::render('Employer/Hiring/Success', ['worker' => $worker]);
return Inertia::render('Employer/Hiring/Success', ['worker' => $workerData]);
})->name('employer.hiring.success');
// Job Management
Route::get('/jobs', function () {
return Inertia::render('Employer/Jobs/Index');
})->name('employer.jobs');
Route::get('/jobs/create', function () {
return Inertia::render('Employer/Jobs/Create');
})->name('employer.jobs.create');
Route::get('/jobs/{id}/applicants', function ($id) {
$job = ['id' => $id, 'title' => 'Experienced Mason Project', 'category' => 'Mason', 'salary' => 2800];
return Inertia::render('Employer/Jobs/Applicants', ['job' => $job]);
})->name('employer.jobs.applicants');
Route::get('/jobs', [\App\Http\Controllers\Employer\JobController::class, 'index'])->name('employer.jobs');
Route::get('/jobs/create', [\App\Http\Controllers\Employer\JobController::class, 'create'])->name('employer.jobs.create');
Route::post('/jobs/create', [\App\Http\Controllers\Employer\JobController::class, 'store'])->name('employer.jobs.store');
Route::get('/jobs/{id}/applicants', [\App\Http\Controllers\Employer\JobController::class, 'applicants'])->name('employer.jobs.applicants');
Route::get('/subscription', function () {
$sess = session('user');
$sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null);
$user = $sessId ? \App\Models\User::find($sessId) : \App\Models\User::where('role', 'employer')->first();
$sub = $user ? \Illuminate\Support\Facades\DB::table('subscriptions')->where('user_id', $user->id)->where('status', 'active')->latest('id')->first() : null;
$expiresAt = $sub && $sub->expires_at ? date('Y-m-d', strtotime($sub->expires_at)) : '2026-12-31';
$planName = $sub ? (ucfirst($sub->plan_id) . ' Pass') : 'Premium Employer Pass';
return Inertia::render('Employer/Subscription', [
'currentPlan' => 'Premium Employer Pass',
'expiresAt' => '2026-12-31',
'currentPlan' => $planName,
'expiresAt' => $expiresAt,
'plans' => [
[
'id' => 'basic',
@ -193,215 +138,20 @@
]);
})->name('employer.subscription');
Route::get('/messages', function () {
return Inertia::render('Employer/Messages/Index', [
'conversations' => [
[
'id' => 201,
'worker_name' => 'Maria Santos',
'category' => 'Childcare',
'last_message' => 'Yes ma\'am, I am available for a video interview tomorrow at 10 AM.',
'unread' => true,
'online' => true,
'sent_at' => '10 mins ago',
],
[
'id' => 202,
'worker_name' => 'Lakshmi Sharma',
'category' => 'Elderly Care',
'last_message' => 'I have 5 years of experience in Dubai taking care of elderly patients.',
'unread' => true,
'online' => false,
'sent_at' => '2 hours ago',
],
[
'id' => 203,
'worker_name' => 'Siti Aminah',
'category' => 'Housekeeping',
'last_message' => 'Thank ma\'am for shortlisting my profile. Please let me know your offer.',
'unread' => false,
'online' => true,
'sent_at' => '1 day ago',
],
]
]);
})->name('employer.messages');
Route::get('/messages', [\App\Http\Controllers\Employer\MessageController::class, 'index'])->name('employer.messages');
Route::get('/messages/{id}', [\App\Http\Controllers\Employer\MessageController::class, 'show'])->name('employer.messages.show');
Route::post('/messages/{id}/send', [\App\Http\Controllers\Employer\MessageController::class, 'send'])->name('employer.messages.send');
Route::get('/messages/{id}', function ($id) {
$conversations = [
[
'id' => 201,
'worker_name' => 'Maria Santos',
'category' => 'Childcare',
'last_message' => 'Yes ma\'am, I am available for a video interview tomorrow at 10 AM.',
'unread' => true,
'online' => true,
'sent_at' => '10 mins ago',
],
[
'id' => 202,
'worker_name' => 'Lakshmi Sharma',
'category' => 'Elderly Care',
'last_message' => 'I have 5 years of experience in Dubai taking care of elderly patients.',
'unread' => true,
'online' => false,
'sent_at' => '2 hours ago',
],
[
'id' => 203,
'worker_name' => 'Siti Aminah',
'category' => 'Housekeeping',
'last_message' => 'Thank ma\'am for shortlisting my profile. Please let me know your offer.',
'unread' => false,
'online' => true,
'sent_at' => '1 day ago',
],
];
Route::get('/shortlist', [\App\Http\Controllers\Employer\ShortlistController::class, 'index'])->name('employer.shortlist');
Route::post('/shortlist/toggle', [\App\Http\Controllers\Employer\ShortlistController::class, 'toggle'])->name('employer.shortlist.toggle');
Route::post('/shortlist/{id}/remove', [\App\Http\Controllers\Employer\ShortlistController::class, 'remove'])->name('employer.shortlist.remove');
return Inertia::render('Employer/Messages/Show', [
'conversations' => $conversations,
'conversation' => collect($conversations)->firstWhere('id', (int) $id) ?? [
'id' => (int) $id,
'worker_name' => 'Maria Santos',
'category' => 'Childcare',
'online' => true,
'salary' => '1,800 AED',
'nationality' => 'Philippines',
],
'initialMessages' => [
[
'id' => 1,
'sender' => 'employer',
'text' => 'Hello! I reviewed your profile and I am very interested in hiring you. Are you available for a phone interview?',
'time' => 'Yesterday, 4:15 PM',
],
[
'id' => 2,
'sender' => 'worker',
'text' => 'Good day ma\'am! Yes, thank you for reaching out. I am available anytime today or tomorrow.',
'time' => 'Yesterday, 4:30 PM',
],
[
'id' => 3,
'sender' => 'worker',
'text' => 'I have all my documents ready and my current visa is transferable.',
'time' => 'Yesterday, 4:31 PM',
],
]
]);
})->name('employer.messages.show');
Route::get('/candidates', [\App\Http\Controllers\Employer\CandidateController::class, 'index'])->name('employer.candidates');
Route::post('/candidates/{id}/status', [\App\Http\Controllers\Employer\CandidateController::class, 'updateStatus'])->name('employer.candidates.status');
Route::get('/shortlist', function () {
return Inertia::render('Employer/Shortlist', [
'shortlistedWorkers' => [
[
'id' => 101,
'name' => 'Maria Santos',
'nationality' => 'Philippines',
'category' => 'Childcare',
'skills' => ['Childcare', 'Cooking', 'Housekeeping'],
'availability' => 'Immediate',
'experience' => '5+ Years',
'salary' => 1800,
'verified' => true,
],
[
'id' => 103,
'name' => 'Siti Aminah',
'nationality' => 'Indonesia',
'category' => 'Housekeeping',
'skills' => ['Housekeeping', 'Ironing', 'Deep Cleaning'],
'availability' => 'Immediate',
'experience' => '3-5 Years',
'salary' => 1400,
'verified' => true,
],
[
'id' => 105,
'name' => 'Anoma Perera',
'nationality' => 'Sri Lanka',
'category' => 'Cooking',
'skills' => ['Cooking', 'Baking'],
'availability' => 'Immediate',
'experience' => '5+ Years',
'salary' => 2200,
'verified' => true,
],
]
]);
})->name('employer.shortlist');
Route::get('/announcements', [\App\Http\Controllers\Employer\AnnouncementController::class, 'index'])->name('employer.announcements');
Route::post('/announcements/create', [\App\Http\Controllers\Employer\AnnouncementController::class, 'store'])->name('employer.announcements.store');
Route::get('/candidates', function () {
return Inertia::render('Employer/SelectedCandidates', [
'selectedWorkers' => [
[
'id' => 101,
'name' => 'Maria Santos',
'category' => 'Site Supervisor',
'nationality' => 'Philippines',
'salary' => '4,500',
'status' => 'Interview Scheduled',
'experience' => '8 Years'
],
[
'id' => 102,
'name' => 'Lakshmi Sharma',
'category' => 'Electrician',
'nationality' => 'India',
'salary' => '3,200',
'status' => 'Offer Extended',
'experience' => '5 Years'
],
[
'id' => 103,
'name' => 'Siti Aminah',
'category' => 'Project Coordinator',
'nationality' => 'Indonesia',
'salary' => '5,500',
'status' => 'Hired',
'experience' => '10 Years'
],
[
'id' => 104,
'name' => 'Ahmed Khan',
'category' => 'Mason',
'nationality' => 'Pakistan',
'salary' => '2,800',
'status' => 'Reviewing',
'experience' => '4 Years'
],
[
'id' => 105,
'name' => 'John Doe',
'category' => 'Carpenter',
'nationality' => 'United Kingdom',
'salary' => '6,000',
'status' => 'Rejected',
'experience' => '12 Years'
],
]
]);
})->name('employer.candidates');
Route::get('/announcements', function () {
return Inertia::render('Employer/Announcements');
})->name('employer.announcements');
Route::get('/profile', function () {
$sess = session('user');
$user = is_array($sess) ? (object) $sess : ($sess ?? (object) [
'name' => 'John Doe',
'email' => 'employer@example.com',
'phone' => '+971 50 123 4567',
]);
return Inertia::render('Employer/Profile', [
'employerProfile' => [
'name' => $user->name ?? 'John Doe',
'email' => $user->email ?? 'employer@example.com',
'phone' => $user->phone ?? '+971 50 123 4567',
'company_name' => 'Al Mansoor Household',
'emirates_id_status' => 'approved',
]
]);
})->name('employer.profile');
Route::get('/profile', [\App\Http\Controllers\Employer\ProfileController::class, 'index'])->name('employer.profile');
Route::post('/profile/update', [\App\Http\Controllers\Employer\ProfileController::class, 'update'])->name('employer.profile.update');
});