diff --git a/resources/js/Pages/Employer/Profile.jsx b/resources/js/Pages/Employer/Profile.jsx
index a7948df..88f42be 100644
--- a/resources/js/Pages/Employer/Profile.jsx
+++ b/resources/js/Pages/Employer/Profile.jsx
@@ -1,546 +1,567 @@
import React, { useState } from 'react';
-import { Head, useForm } from '@inertiajs/react';
+import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../Layouts/EmployerLayout';
import { useTranslation } from '../../lib/LanguageContext';
import {
User,
- CheckCircle2,
- Save,
- Building2,
- Lock,
- Bell,
ShieldCheck,
Mail,
Phone,
Globe,
- CreditCard,
- ChevronRight,
- Camera,
- Home,
- Users,
MapPin,
- History,
- Sparkles
+ Home,
+ Edit3,
+ Calendar,
+ Briefcase,
+ Building,
+ FileText,
+ MessageSquare,
+ AlertCircle,
+ CheckCircle2,
+ Lock,
+ ExternalLink,
+ Compass,
+ FileSpreadsheet,
+ Eye,
+ Bell,
+ Camera,
+ Save,
+ XCircle
} from 'lucide-react';
import { toast } from 'sonner';
export default function Profile({ employerProfile }) {
const { t } = useTranslation();
- const { data: profile, setData, post, processing, errors } = useForm({
- name: employerProfile?.name || '',
- company_name: employerProfile?.company_name || '',
- email: employerProfile?.email || '',
- phone: employerProfile?.phone || '',
- language: employerProfile?.language || 'English',
- nationality: employerProfile?.nationality || '',
- family_size: employerProfile?.family_size || '',
- accommodation: employerProfile?.accommodation || '',
- district: employerProfile?.district || '',
- notifications: employerProfile?.notifications ?? true,
- emirates_id_front: null,
- emirates_id_back: null,
- current_password: '',
- new_password: '',
- new_password_confirmation: '',
- });
+ const [showConfirmPopup, setShowConfirmPopup] = useState(false);
- const [activeTab, setActiveTab] = useState('personal');
- const [saved, setSaved] = useState(false);
+ const handleEditClick = () => {
+ setShowConfirmPopup(true);
+ };
- const handleSave = (e) => {
+ const handleConfirmEdit = () => {
+ setShowConfirmPopup(false);
+ router.get('/employer/profile/edit');
+ };
+
+ const handleCancelEdit = () => {
+ setShowConfirmPopup(false);
+ };
+
+ const [showRenewModal, setShowRenewModal] = useState(false);
+ const [frontFile, setFrontFile] = useState(null);
+ const [backFile, setBackFile] = useState(null);
+ const [frontPreview, setFrontPreview] = useState(null);
+ const [backPreview, setBackPreview] = useState(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const expiryDateStr = employerProfile?.emirates_id?.expiry_date;
+ const isExpired = expiryDateStr ? new Date(expiryDateStr) <= new Date() : false;
+
+ const handleRenewalSubmit = (e) => {
e.preventDefault();
- post('/employer/profile/update', {
- preserveScroll: true,
+ if (!frontFile || !backFile) {
+ toast.error(t('upload_both_sides', 'Please upload both front and back sides of your Emirates ID.'));
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ router.post('/employer/profile/update', {
+ name: employerProfile.name,
+ email: employerProfile.email,
+ phone: employerProfile.phone,
+ emirates_id_front: frontFile,
+ emirates_id_back: backFile,
+ }, {
+ forceFormData: true,
onSuccess: () => {
- setSaved(true);
- toast.success(`🛡️ ${t('profile_updated_successfully', 'Profile updated successfully')}`, {
- description: t('profile_updated_successfully_desc', 'Household sponsor preferences and vetted credentials synchronized successfully.')
- });
- setTimeout(() => setSaved(false), 3000);
+ toast.success(t('document_renewed', 'Emirates ID renewed successfully!'));
+ setShowRenewModal(false);
+ setFrontFile(null);
+ setBackFile(null);
+ setFrontPreview(null);
+ setBackPreview(null);
+ setIsSubmitting(false);
},
onError: (errs) => {
- toast.error(t('profile_update_failed', 'Failed to update profile'), {
- description: Object.values(errs)[0] || t('check_form_fields', 'Please check the form fields.')
- });
+ toast.error(Object.values(errs)[0] || t('renewal_failed', 'Failed to renew Emirates ID.'));
+ setIsSubmitting(false);
}
});
};
- const tabs = [
- { id: 'personal', label: t('tab_personal', 'Personal'), icon: User },
- { id: 'security', label: t('tab_security', 'Security & Password'), icon: Lock },
- { id: 'billing', label: t('tab_billing', 'Billing & Receipts'), icon: CreditCard },
- { id: 'notifications', label: t('tab_notifications', 'Notifications Hub'), icon: Bell },
- ];
+ const toggleNotification = (type, currentVal) => {
+ router.post('/employer/profile/update', {
+ name: employerProfile.name,
+ email: employerProfile.email,
+ phone: employerProfile.phone,
+ email_notifications: type === 'email' ? !currentVal : employerProfile.email_notifications,
+ push_notifications: type === 'push' ? !currentVal : employerProfile.push_notifications,
+ }, {
+ preserveScroll: true,
+ onSuccess: () => {
+ toast.success(t('preferences_updated', 'Preferences updated successfully'));
+ }
+ });
+ };
+
+ const profilePhoto = employerProfile?.profile_photo_url;
return (
-
+
-
- {/* Left Side: Navigation Tabs */}
-
- {tabs.map((tab) => {
- const Icon = tab.icon;
- return (
- setActiveTab(tab.id)}
- className={`w-full flex items-center justify-between p-4 rounded-2xl text-xs font-black uppercase tracking-wider transition-all ${
- activeTab === tab.id
- ? 'bg-[#185FA5] text-white shadow-md shadow-blue-900/10'
- : 'bg-white text-slate-600 hover:bg-slate-50 border border-slate-100'
- }`}
- >
-
-
- {tab.label}
-
-
-
- );
- })}
-
+
+ {isExpired && (
+
+
+
+
+
Emirates ID Expired
+
+ Your Emirates ID card expired on {employerProfile?.emirates_id?.expiry_date} . Please upload a renewed document to maintain compliance and system access.
+
+
+
+
setShowRenewModal(true)}
+ className="bg-rose-600 hover:bg-rose-700 text-white px-5 py-3 rounded-2xl text-xs font-black uppercase tracking-wider transition-all shadow-md shadow-rose-200/50 flex items-center justify-center gap-2 whitespace-nowrap self-start md:self-center"
+ >
+ Renew Emirates ID
+
+
+ )}
- {/* Right Side: Content Area */}
-
- {/* Header Info Card */}
-
-
-
-
-
-
- {profile.name?.charAt(0)}
+ {/* Premium Banner & Cover Section */}
+
+ {/* Artistic Enterprise Gradient Header Cover */}
+
+
+ {/* Profile Header Details Row */}
+
+
+ {/* Profile Image with Glow Ring */}
+
+
+ {profilePhoto ? (
+
+ ) : (
+
+ {employerProfile?.name?.charAt(0)}
+
+ )}
-
-
-
+
-
-
{profile.name}
-
{profile.company_name}
+
+
+
{employerProfile?.name}
+
+
{employerProfile?.email}
-
-
-
- {t('emirates_id_vetted', 'Emirates ID Vetted')}
-
-
-
- {t('direct_sponsor_portal', 'Direct Sponsor Portal')}
-
+
+
+
+ {/* Edit Button */}
+
+
+ {t('edit_profile_details', 'Edit Profile')}
+
+
+
+
+ {/* Grid Layout: Left Details Column & Right Digital Card Column */}
+
+
+ {/* Left Column: General & Invoicing Info (7/12 width) */}
+
+
+ {/* Account & Contact Settings */}
+
+
+
{t('account_contact_info', 'Account & Contact Information')}
+
+ {t('manage_contact_desc', 'Contact and notification channels')}
+
+
+
+
+
+
+
+ {t('full_name', 'Full Name')}
+
+
{employerProfile?.name}
+
+
+
+
+
+ {t('email', 'Email Address')}
+
+
{employerProfile?.email}
+
+
+
+
+
+
{t('phone', 'Phone Number')}
+
+
{employerProfile?.phone}
+
+
+
+
+
+ {t('property_type', 'Property Type')}
+
+
{employerProfile?.property_type || t('not_specified', 'Not Specified')}
+
+
+
+
+
+ {t('address', 'Address')}
+
+
{employerProfile?.address || t('not_specified', 'Not Specified')}
+
+
+
+
+ {/* Notification Preferences & Integration Card */}
+
+
+
{t('notification_preferences', 'Notification Preferences')}
+
+
+ {/* Preferences Toggles */}
+
+ {/* Email Alerts Row */}
+
+
+
+
+
+
+
{t('email_alerts', 'Email Alerts')}
+
{t('email_alerts_sub', 'Invoices, updates & announcements')}
+
+
+
toggleNotification('email', employerProfile?.email_notifications)}
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${
+ employerProfile?.email_notifications ? 'bg-[#185FA5]' : 'bg-slate-200'
+ }`}
+ >
+
+
+
+
+ {/* Push Notification Alerts Row */}
+
+
+
+
+
+
+
{t('push_alerts', 'Push Notifications')}
+
{t('push_alerts_sub', 'Live updates & mobile alerts')}
+
+
+
toggleNotification('push', employerProfile?.push_notifications)}
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${
+ employerProfile?.push_notifications ? 'bg-[#185FA5]' : 'bg-slate-200'
+ }`}
+ >
+
+
- {/* Tab Specific Content */}
-
+
+ {/* Confirmation Dialog Popup Modal */}
+ {showConfirmPopup && (
+
+
+
+
+
+
+ {t('edit_profile_confirm_title', 'Edit Profile')}
+
+
+ {t('edit_profile_confirm_body', 'Are you sure you want to edit your profile details?')}
+
+
+
+
+
+
+ {t('cancel', 'Cancel')}
+
+
+ {t('confirm', 'Confirm')}
+
+
+
+
+ )}
+
+ {/* Renew Emirates ID Modal */}
+ {showRenewModal && (
+
+
+ {/* Modal Header */}
+
+
Renew Emirates ID
+
Upload both sides of your new document
+
setShowRenewModal(false)}
+ className="absolute top-6 right-6 text-rose-100 hover:text-white transition-colors"
+ >
+
+
+
+
+ {/* Modal Form */}
+
-
+ )}
);
}
diff --git a/resources/js/Pages/Employer/ProfileEdit.jsx b/resources/js/Pages/Employer/ProfileEdit.jsx
new file mode 100644
index 0000000..366f954
--- /dev/null
+++ b/resources/js/Pages/Employer/ProfileEdit.jsx
@@ -0,0 +1,241 @@
+import React, { useState } from 'react';
+import { Head, useForm, Link } from '@inertiajs/react';
+import EmployerLayout from '../../Layouts/EmployerLayout';
+import { useTranslation } from '../../lib/LanguageContext';
+import {
+ User,
+ Mail,
+ Phone,
+ MapPin,
+ Home,
+ Camera,
+ Save,
+ XCircle,
+ ArrowLeft,
+ Compass
+} from 'lucide-react';
+import { toast } from 'sonner';
+
+export default function ProfileEdit({ employerProfile }) {
+ const { t } = useTranslation();
+
+ const { data, setData, post, processing, errors } = useForm({
+ name: employerProfile?.name || '',
+ email: employerProfile?.email || '',
+ phone: employerProfile?.phone || '',
+ address: employerProfile?.address || '',
+ property_type: employerProfile?.property_type || '',
+ profile_photo: null,
+ });
+
+ const [photoPreview, setPhotoPreview] = useState(employerProfile?.profile_photo_url || null);
+
+ const handlePhotoChange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setData('profile_photo', file);
+ setPhotoPreview(URL.createObjectURL(file));
+ }
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+
+ post('/employer/profile/update', {
+ preserveScroll: true,
+ onSuccess: () => {
+ toast.success(t('profile_updated_successfully', 'Profile updated successfully!'));
+ },
+ onError: (errs) => {
+ toast.error(t('profile_update_failed', 'Failed to update profile'), {
+ description: Object.values(errs)[0] || t('check_form_fields', 'Please check the form fields.')
+ });
+ }
+ });
+ };
+
+ return (
+
+
+
+
+ {/* Back Link */}
+
+
+
+
{t('back_to_profile', 'Back to Profile')}
+
+
+
+ {/* Edit Form Card */}
+
+ {/* Header Banner Accent */}
+
+
+
+
+
+
+ );
+}
diff --git a/resources/js/Pages/Employer/Support/Index.jsx b/resources/js/Pages/Employer/Support/Index.jsx
index 209f910..96c328f 100644
--- a/resources/js/Pages/Employer/Support/Index.jsx
+++ b/resources/js/Pages/Employer/Support/Index.jsx
@@ -14,7 +14,7 @@ import {
MessageSquare
} from 'lucide-react';
-export default function Index({ tickets }) {
+export default function Index({ tickets, faqs = [] }) {
const { t } = useTranslation();
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
@@ -204,23 +204,25 @@ export default function Index({ tickets }) {
{/* FAQ section */}
-
-
-
- {t('frequently_asked_questions', 'Frequently Asked Questions')}
-
-
-
-
-
{t('faq_q1', 'How long does a ticket resolution take?')}
-
{t('faq_a1', 'For priority high tickets, our engineering support response is typically under 2 hours. General queries resolve within 24 hours.')}
-
-
-
{t('faq_q2', 'What is Tadbeer contract compliance?')}
-
{t('faq_a2', 'All worker hirings conform to MOHRE Tadbeer rules. Contracts are electronically generated and uploaded for review.')}
+ {faqs && faqs.length > 0 && (
+
+
+
+ {t('frequently_asked_questions', 'Frequently Asked Questions')}
+
+
+
+ {faqs.map((faq) => (
+
+
+ {faq.question.trim().endsWith('?') ? faq.question.trim() : faq.question.trim() + '?'}
+
+
{faq.answer}
+
+ ))}
-
+ )}
diff --git a/routes/web.php b/routes/web.php
index d17d16a..ccaef98 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -80,28 +80,32 @@
})->name('admin.subscriptions');
Route::get('/payments', function () {
- $payments = \Illuminate\Support\Facades\DB::table('payments')
- ->leftJoin('users', 'payments.user_id', '=', 'users.id')
- ->select('payments.*', 'users.name as user_name', 'users.email as user_email')
- ->orderBy('payments.created_at', 'desc')
+ $payments = \Illuminate\Support\Facades\DB::table('subscriptions')
+ ->leftJoin('users', 'subscriptions.user_id', '=', 'users.id')
+ ->select('subscriptions.*', 'users.name as user_name', 'users.email as user_email')
+ ->orderBy('subscriptions.created_at', 'desc')
->get()
- ->map(function($payment) {
+ ->map(function($sub) {
+ $planLabel = ucfirst($sub->plan_id) . ' Sponsor Pass';
+ $status = $sub->status === 'active' ? 'Completed' : (ucfirst($sub->status) ?: 'Completed');
+ $startsAt = $sub->starts_at ?? $sub->created_at;
+ $expiresAt = $sub->expires_at ?? date('Y-m-d H:i:s', strtotime($startsAt . ' +1 month'));
return [
- 'id' => 'PAY-' . str_pad($payment->id, 3, '0', STR_PAD_LEFT),
- 'employer' => $payment->user_name ?: 'System Guest / Sponsor',
- 'plan' => $payment->description ?: 'Subscription Plan',
- 'amount' => (float)$payment->amount,
- 'method' => 'Stripe Gateway',
- 'date' => date('Y-m-d', strtotime($payment->created_at)),
- 'status' => $payment->status === 'success' ? 'Completed' : (ucfirst($payment->status) ?: 'Completed'),
- 'period' => date('M Y', strtotime($payment->created_at)) . ' - ' . date('M Y', strtotime($payment->created_at . ' +1 month')),
+ 'id' => 'PAY-' . str_pad($sub->id, 3, '0', STR_PAD_LEFT),
+ 'employer' => $sub->user_name ?: 'System Guest / Sponsor',
+ 'plan' => $planLabel,
+ 'amount' => (float)$sub->amount_aed,
+ 'method' => 'PayTabs Gateway',
+ 'date' => date('Y-m-d', strtotime($startsAt)),
+ 'status' => $status,
+ 'period' => date('M Y', strtotime($startsAt)) . ' - ' . date('M Y', strtotime($expiresAt)),
];
});
- $totalRevenue = \Illuminate\Support\Facades\DB::table('payments')->where('status', 'success')->sum('amount');
- $activeSubs = \Illuminate\Support\Facades\DB::table('sponsors')->where('subscription_status', 'active')->count();
- $failedPayments = \Illuminate\Support\Facades\DB::table('payments')->where('status', 'failed')->count();
- $avgTicket = \Illuminate\Support\Facades\DB::table('payments')->where('status', 'success')->avg('amount') ?: 0;
+ $totalRevenue = \Illuminate\Support\Facades\DB::table('subscriptions')->where('status', 'active')->sum('amount_aed');
+ $activeSubs = \Illuminate\Support\Facades\DB::table('subscriptions')->where('status', 'active')->count();
+ $failedPayments = \Illuminate\Support\Facades\DB::table('subscriptions')->where('status', 'failed')->count();
+ $avgTicket = \Illuminate\Support\Facades\DB::table('subscriptions')->where('status', 'active')->avg('amount_aed') ?: 0;
$stats = [
'total_revenue' => number_format($totalRevenue, 2),
@@ -243,12 +247,16 @@
// Route::post('/disputes/{id}/resolve', [\App\Http\Controllers\Admin\AdminExtraController::class, 'resolveDispute'])->name('admin.disputes.resolve');
// Route::post('/disputes/{id}/add-note', [\App\Http\Controllers\Admin\AdminExtraController::class, 'addDisputeNote'])->name('admin.disputes.add-note');
- Route::get('/notifications', [\App\Http\Controllers\Admin\AdminExtraController::class, 'notifications'])->name('admin.notifications');
- Route::post('/notifications/broadcast', [\App\Http\Controllers\Admin\AdminExtraController::class, 'broadcastNotification'])->name('admin.notifications.broadcast');
Route::get('/analytics', [\App\Http\Controllers\Admin\AdminExtraController::class, 'analytics'])->name('admin.analytics');
Route::get('/audit-logs', [\App\Http\Controllers\Admin\AdminExtraController::class, 'auditLogs'])->name('admin.audit-logs');
+ // Admin FAQs Management
+ Route::get('/faqs', [\App\Http\Controllers\Admin\FaqController::class, 'index'])->name('admin.faqs');
+ Route::post('/faqs', [\App\Http\Controllers\Admin\FaqController::class, 'store'])->name('admin.faqs.store');
+ Route::post('/faqs/{id}', [\App\Http\Controllers\Admin\FaqController::class, 'update'])->name('admin.faqs.update');
+ Route::delete('/faqs/{id}', [\App\Http\Controllers\Admin\FaqController::class, 'destroy'])->name('admin.faqs.destroy');
+
// Admin Support Tickets
Route::get('/tickets', [\App\Http\Controllers\Admin\SupportTicketController::class, 'index'])->name('admin.tickets.index');
Route::get('/tickets/{id}', [\App\Http\Controllers\Admin\SupportTicketController::class, 'show'])->name('admin.tickets.show');
@@ -374,6 +382,7 @@
Route::post('/events/create', [\App\Http\Controllers\Employer\AnnouncementController::class, 'store'])->name('employer.events.store');
Route::get('/profile', [\App\Http\Controllers\Employer\ProfileController::class, 'index'])->name('employer.profile');
+ Route::get('/profile/edit', [\App\Http\Controllers\Employer\ProfileController::class, 'edit'])->name('employer.profile.edit');
Route::post('/profile/update', [\App\Http\Controllers\Employer\ProfileController::class, 'update'])->name('employer.profile.update');
Route::get('/payments', [\App\Http\Controllers\Employer\PaymentController::class, 'index'])->name('employer.payments');