Compare commits
No commits in common. "4a77c7689ce65a99123fe34755fa45973e432c8b" and "0dc116afc8a24a81fe14157e0428d21d5b93b782" have entirely different histories.
4a77c7689c
...
0dc116afc8
@ -67,7 +67,6 @@ export default function EmployerLayout({ children, title, fullPage = false }) {
|
||||
{ name: 'Find Workers', translationKey: 'find_workers', href: '/employer/workers', icon: Search },
|
||||
{ name: 'Shortlist', translationKey: 'shortlist', href: '/employer/shortlist', icon: Bookmark },
|
||||
{ name: 'Hired Workers', translationKey: 'candidates', href: '/employer/candidates', icon: UserCheck },
|
||||
{ name: 'My Jobs', translationKey: 'my_jobs', href: '/employer/jobs', icon: Briefcase },
|
||||
{ name: 'Messages', translationKey: 'messages', href: '/employer/messages', icon: MessageSquare, badge: unread_messages_count },
|
||||
{ name: 'Charity Events', translationKey: 'charity_events', href: '/employer/events', icon: Heart },
|
||||
{ name: 'Subscription', translationKey: 'subscription', href: '/employer/subscription', icon: CreditCard },
|
||||
|
||||
@ -36,9 +36,9 @@ import {
|
||||
|
||||
export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
const [plans, setPlans] = useState(initialPlans || [
|
||||
{ id: 'basic', name: 'Basic Search', price: 99, duration: 'Monthly', features: ['Browse 500+ workers', '10 Shortlists'], status: 'Active', maxContactPeople: 10, enableJobApply: false },
|
||||
{ id: 'premium', name: 'Premium Pass', price: 199, duration: 'Monthly', features: ['Unlimited shortlisting', 'Direct messaging'], status: 'Active', maxContactPeople: 50, enableJobApply: true },
|
||||
{ id: 'vip', name: 'VIP Concierge', price: 499, duration: 'Monthly', features: ['Dedicated Manager', '30-day replacement'], status: 'Active', maxContactPeople: 100, enableJobApply: true },
|
||||
{ id: 'basic', name: 'Basic Search', price: 99, duration: 'Monthly', features: ['Browse 500+ workers', '10 Shortlists'], status: 'Active' },
|
||||
{ id: 'premium', name: 'Premium Pass', price: 199, duration: 'Monthly', features: ['Unlimited shortlisting', 'Direct messaging'], status: 'Active' },
|
||||
{ id: 'vip', name: 'VIP Concierge', price: 499, duration: 'Monthly', features: ['Dedicated Manager', '30-day replacement'], status: 'Active' },
|
||||
]);
|
||||
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
@ -50,8 +50,6 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
const [price, setPrice] = useState('');
|
||||
const [duration, setDuration] = useState('Monthly');
|
||||
const [features, setFeatures] = useState('');
|
||||
const [maxContactPeople, setMaxContactPeople] = useState('');
|
||||
const [enableJobApply, setEnableJobApply] = useState(false);
|
||||
|
||||
const handleCreateClick = () => {
|
||||
setEditingPlan(null);
|
||||
@ -59,8 +57,6 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
setPrice('');
|
||||
setDuration('Monthly');
|
||||
setFeatures('');
|
||||
setMaxContactPeople('');
|
||||
setEnableJobApply(false);
|
||||
setIsDialogOpen(true);
|
||||
};
|
||||
|
||||
@ -70,8 +66,6 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
setPrice(plan.price);
|
||||
setDuration(plan.duration);
|
||||
setFeatures(plan.features.join('\n'));
|
||||
setMaxContactPeople(plan.maxContactPeople !== undefined ? plan.maxContactPeople : '');
|
||||
setEnableJobApply(!!plan.enableJobApply);
|
||||
setIsDialogOpen(true);
|
||||
};
|
||||
|
||||
@ -99,9 +93,7 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
name,
|
||||
price: Number(price),
|
||||
duration,
|
||||
features: featuresArray,
|
||||
maxContactPeople: maxContactPeople !== '' ? Number(maxContactPeople) : '',
|
||||
enableJobApply
|
||||
features: featuresArray
|
||||
} : p));
|
||||
} else {
|
||||
const newId = name.toLowerCase().replace(/\s+/g, '-');
|
||||
@ -111,9 +103,7 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
price: Number(price),
|
||||
duration,
|
||||
features: featuresArray,
|
||||
status: 'Active',
|
||||
maxContactPeople: maxContactPeople !== '' ? Number(maxContactPeople) : '',
|
||||
enableJobApply
|
||||
status: 'Active'
|
||||
}]);
|
||||
}
|
||||
setIsDialogOpen(false);
|
||||
@ -159,8 +149,6 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12">Plan Name</TableHead>
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12">Pricing (AED)</TableHead>
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12">Duration</TableHead>
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12">Max Contacts</TableHead>
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12">Job Apply</TableHead>
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12">Key Features</TableHead>
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12">Status</TableHead>
|
||||
<TableHead className="font-semibold text-slate-600 text-xs h-12 text-right pr-6">Actions</TableHead>
|
||||
@ -169,7 +157,7 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
<TableBody>
|
||||
{filteredPlans.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="text-center py-8 text-slate-400 text-sm">
|
||||
<TableCell colSpan={6} className="text-center py-8 text-slate-400 text-sm">
|
||||
No plans found
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -192,14 +180,6 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
</TableCell>
|
||||
<TableCell className="font-bold text-slate-900">{plan.price}</TableCell>
|
||||
<TableCell className="text-sm text-slate-500 font-medium">{plan.duration}</TableCell>
|
||||
<TableCell className="text-sm text-slate-700 font-bold">{plan.maxContactPeople || 'Unlimited'}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-wider ${
|
||||
plan.enableJobApply ? 'bg-emerald-50 text-emerald-700' : 'bg-rose-50 text-rose-600'
|
||||
}`}>
|
||||
{plan.enableJobApply ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{plan.features.map((f, i) => (
|
||||
@ -277,7 +257,6 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Duration</label>
|
||||
<select
|
||||
@ -290,32 +269,6 @@ export default function SubscriptionsIndex({ plans: initialPlans }) {
|
||||
<option value="Yearly">Yearly</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Max Contact People</label>
|
||||
<input
|
||||
type="number"
|
||||
value={maxContactPeople}
|
||||
onChange={(e) => setMaxContactPeople(e.target.value)}
|
||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-100 rounded-xl text-sm font-bold focus:ring-4 focus:ring-teal-500/10 outline-none"
|
||||
placeholder="e.g. 10 (blank for unlimited)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3 bg-slate-50 p-3.5 rounded-xl border border-slate-100/50">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="enableJobApply"
|
||||
checked={enableJobApply}
|
||||
onChange={(e) => setEnableJobApply(e.target.checked)}
|
||||
className="w-4.5 h-4.5 text-teal-600 border-slate-300 rounded focus:ring-teal-500"
|
||||
/>
|
||||
<label htmlFor="enableJobApply" className="text-xs font-bold text-slate-700 cursor-pointer select-none">
|
||||
Enable Job Apply Module
|
||||
<span className="block text-[10px] font-medium text-slate-400 mt-0.5">Allows employers subscribed to this plan to receive job applications.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Features (one per line)</label>
|
||||
<textarea
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
"find_workers": "Find Workers",
|
||||
"shortlist": "Shortlist",
|
||||
"candidates": "Hired Workers",
|
||||
"my_jobs": "My Jobs",
|
||||
"messages": "Messages",
|
||||
"charity_events": "Charity Events",
|
||||
"subscription": "Subscription",
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
"find_workers": "कामगार खोजें",
|
||||
"shortlist": "शॉर्टलिस्ट",
|
||||
"candidates": "नियुक्त कर्मचारी",
|
||||
"my_jobs": "मेरी नौकरियाँ",
|
||||
"messages": "संदेश",
|
||||
"charity_events": "दान कार्यक्रम",
|
||||
"subscription": "सदस्यता",
|
||||
|
||||
@ -1,158 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\JobPost;
|
||||
use App\Models\JobApplication;
|
||||
use App\Models\Worker;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EmployerJobTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected $employer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->employer = User::create([
|
||||
'name' => 'Jane Sponsor',
|
||||
'email' => 'sponsor@example.com',
|
||||
'password' => bcrypt('password'),
|
||||
'role' => 'employer',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test displaying the employer jobs list.
|
||||
*/
|
||||
public function test_employer_can_view_jobs_list()
|
||||
{
|
||||
$job = JobPost::create([
|
||||
'employer_id' => $this->employer->id,
|
||||
'title' => 'Experienced Mason',
|
||||
'workers_needed' => 5,
|
||||
'job_type' => 'Full Time',
|
||||
'location' => 'Dubai Marina',
|
||||
'salary' => 2800,
|
||||
'start_date' => '2026-07-01',
|
||||
'description' => 'Looking for experienced masons for a residential project.',
|
||||
'requirements' => '5+ years experience, basic English.',
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->employer)
|
||||
->withSession(['user' => $this->employer])
|
||||
->get('/employer/jobs');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Experienced Mason');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test displaying the create job form.
|
||||
*/
|
||||
public function test_employer_can_view_create_job_form()
|
||||
{
|
||||
$response = $this->actingAs($this->employer)
|
||||
->withSession(['user' => $this->employer])
|
||||
->get('/employer/jobs/create');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test storing a new job posting.
|
||||
*/
|
||||
public function test_employer_can_post_job()
|
||||
{
|
||||
$response = $this->actingAs($this->employer)
|
||||
->withSession(['user' => $this->employer])
|
||||
->post('/employer/jobs/create', [
|
||||
'title' => 'Villa Cleaner',
|
||||
'workers_needed' => 2,
|
||||
'job_type' => 'Part Time',
|
||||
'location' => 'Al Barsha',
|
||||
'salary' => 1800,
|
||||
'start_date' => '2026-07-10',
|
||||
'description' => 'Need part-time villa cleaners for daily housekeeping tasks.',
|
||||
'requirements' => 'Housekeeping experience is a plus.',
|
||||
]);
|
||||
|
||||
$response->assertRedirect('/employer/jobs');
|
||||
$response->assertSessionHas('success');
|
||||
|
||||
$this->assertDatabaseHas('job_posts', [
|
||||
'employer_id' => $this->employer->id,
|
||||
'title' => 'Villa Cleaner',
|
||||
'workers_needed' => 2,
|
||||
'job_type' => 'Part Time',
|
||||
'location' => 'Al Barsha',
|
||||
'salary' => 1800,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validation during job posting.
|
||||
*/
|
||||
public function test_job_posting_requires_fields()
|
||||
{
|
||||
$response = $this->actingAs($this->employer)
|
||||
->withSession(['user' => $this->employer])
|
||||
->post('/employer/jobs/create', [
|
||||
'title' => '',
|
||||
'workers_needed' => 0,
|
||||
'salary' => -100,
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors(['title', 'workers_needed', 'location', 'salary', 'job_type', 'start_date', 'description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test viewing job applicants.
|
||||
*/
|
||||
public function test_employer_can_view_applicants()
|
||||
{
|
||||
$job = JobPost::create([
|
||||
'employer_id' => $this->employer->id,
|
||||
'title' => 'Mason for Project',
|
||||
'workers_needed' => 3,
|
||||
'job_type' => 'Contract',
|
||||
'location' => 'Sharjah',
|
||||
'salary' => 3000,
|
||||
'start_date' => '2026-07-15',
|
||||
'description' => 'Contract mason project.',
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$worker = Worker::create([
|
||||
'name' => 'John Worker',
|
||||
'email' => 'worker@example.com',
|
||||
'phone' => '+971500000001',
|
||||
'nationality' => 'Indian',
|
||||
'age' => 30,
|
||||
'salary' => 2500,
|
||||
'availability' => 'Available',
|
||||
'experience' => '4 Years',
|
||||
'religion' => 'Christian',
|
||||
'bio' => 'Vetted domestic worker with great experience.',
|
||||
]);
|
||||
|
||||
$application = JobApplication::create([
|
||||
'job_id' => $job->id,
|
||||
'worker_id' => $worker->id,
|
||||
'status' => 'applied',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->employer)
|
||||
->withSession(['user' => $this->employer])
|
||||
->get("/employer/jobs/{$job->id}/applicants");
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('John Worker');
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user