phase 6
This commit is contained in:
@@ -124,18 +124,22 @@ class JobRepository
|
|||||||
*/
|
*/
|
||||||
public function getFeaturedJobs(int $limit = 10): Collection
|
public function getFeaturedJobs(int $limit = 10): Collection
|
||||||
{
|
{
|
||||||
return Job::with(['source', 'category', 'jobLocation'])
|
// First, build a subquery with window function
|
||||||
->active()
|
$subQuery = \DB::table('jobs')
|
||||||
->select('jobs.*')
|
->selectRaw('jobs.*, ROW_NUMBER() OVER (PARTITION BY job_source_id ORDER BY posted_at DESC) as rn')
|
||||||
->selectRaw('ROW_NUMBER() OVER (PARTITION BY job_source_id ORDER BY posted_at DESC) as rn')
|
->whereNull('deleted_at')
|
||||||
->fromSub(function ($query) {
|
->where(function ($query) {
|
||||||
$query->from('jobs')
|
$query->whereNull('expires_at')
|
||||||
->whereNull('deleted_at')
|
|
||||||
->whereNull('expires_at')
|
|
||||||
->orWhere('expires_at', '>', now());
|
->orWhere('expires_at', '>', now());
|
||||||
}, 'jobs')
|
});
|
||||||
->havingRaw('rn <= 2') // Max 2 jobs per source
|
|
||||||
->latest('posted_at')
|
// Then query from that subquery filtering by rn
|
||||||
|
// Use withoutGlobalScopes to prevent SoftDeletes from adding WHERE clause
|
||||||
|
return Job::withoutGlobalScopes()
|
||||||
|
->with(['source', 'category', 'jobLocation'])
|
||||||
|
->fromSub($subQuery, 'ranked_jobs')
|
||||||
|
->whereRaw('ranked_jobs.rn <= 2')
|
||||||
|
->orderBy('posted_at', 'desc')
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1981
File diff suppressed because it is too large
Load Diff
@@ -13,5 +13,8 @@
|
|||||||
"laravel-vite-plugin": "^3.0.0",
|
"laravel-vite-plugin": "^3.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"vite": "^8.0.0"
|
"vite": "^8.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"alpinejs": "^3.15.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,8 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
import Alpine from 'alpinejs';
|
||||||
|
|
||||||
|
// Make Alpine available globally
|
||||||
|
window.Alpine = Alpine;
|
||||||
|
|
||||||
|
// Start Alpine
|
||||||
|
Alpine.start();
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
@props(['job'])
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow duration-200">
|
||||||
|
<div class="flex justify-between items-start mb-3">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-1">
|
||||||
|
<a href="{{ route('jobs.show', $job->id) }}" class="hover:text-emerald-600">
|
||||||
|
{{ $job->title }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-700 font-medium">{{ $job->company }}</p>
|
||||||
|
</div>
|
||||||
|
@if($job->remote_allowed)
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-emerald-100 text-emerald-800">
|
||||||
|
Remote
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-3 text-sm text-gray-600 mb-4">
|
||||||
|
@if($job->location)
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ $job->location }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($job->job_type)
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $job->job_type)) }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($job->experience_level)
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $job->experience_level)) }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($job->salary_min || $job->salary_max)
|
||||||
|
<div class="mb-4">
|
||||||
|
<span class="text-sm font-semibold text-emerald-600">
|
||||||
|
@if($job->salary_min && $job->salary_max)
|
||||||
|
{{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_min) }} - {{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_max) }}
|
||||||
|
@elseif($job->salary_min)
|
||||||
|
{{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_min) }}+
|
||||||
|
@elseif($job->salary_max)
|
||||||
|
Up to {{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_max) }}
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($job->description)
|
||||||
|
<p class="text-gray-600 text-sm mb-4 line-clamp-2">
|
||||||
|
{{ Str::limit(strip_tags($job->description), 150) }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center pt-4 border-t border-gray-100">
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
Posted {{ $job->posted_at ? $job->posted_at->diffForHumans() : $job->created_at->diffForHumans() }}
|
||||||
|
</span>
|
||||||
|
<a href="{{ route('jobs.show', $job->id) }}"
|
||||||
|
class="inline-flex items-center px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-medium rounded-md transition-colors duration-200">
|
||||||
|
View Details
|
||||||
|
<svg class="ml-2 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'JobifyGarden - Find Your Dream Job')
|
||||||
|
@section('description', 'Discover thousands of job opportunities from multiple sources. Browse remote jobs, full-time positions, and more.')
|
||||||
|
|
||||||
|
@section('main-class', '')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<div class="bg-gradient-to-r from-emerald-600 to-emerald-500">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 sm:py-24">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6">
|
||||||
|
Find Your Dream Job
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl text-emerald-50 mb-8 max-w-2xl mx-auto">
|
||||||
|
Search through thousands of job listings from multiple sources. Your next career opportunity is just a search away.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Search Bar -->
|
||||||
|
<div class="max-w-3xl mx-auto">
|
||||||
|
<form action="{{ route('jobs.index') }}" method="GET" class="flex flex-col sm:flex-row gap-2">
|
||||||
|
<div class="flex-1">
|
||||||
|
<input type="text"
|
||||||
|
name="keywords"
|
||||||
|
placeholder="Job title, keywords, or company"
|
||||||
|
class="w-full px-4 py-3 rounded-lg border-0 focus:ring-2 focus:ring-emerald-600 text-gray-900 placeholder-gray-500">
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<input type="text"
|
||||||
|
name="location"
|
||||||
|
placeholder="Location"
|
||||||
|
class="w-full px-4 py-3 rounded-lg border-0 focus:ring-2 focus:ring-emerald-600 text-gray-900 placeholder-gray-500">
|
||||||
|
</div>
|
||||||
|
<button type="submit"
|
||||||
|
class="px-8 py-3 bg-emerald-800 hover:bg-emerald-900 text-white font-semibold rounded-lg transition-colors duration-200">
|
||||||
|
Search Jobs
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Section -->
|
||||||
|
<div class="bg-white border-b border-gray-200">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-6 text-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-bold text-emerald-600">{{ number_format($stats['active_jobs'] ?? 0) }}</div>
|
||||||
|
<div class="text-sm text-gray-600 mt-1">Active Jobs</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-bold text-emerald-600">{{ number_format($stats['remote_jobs'] ?? 0) }}</div>
|
||||||
|
<div class="text-sm text-gray-600 mt-1">Remote Jobs</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-bold text-emerald-600">{{ number_format($stats['recently_added'] ?? 0) }}</div>
|
||||||
|
<div class="text-sm text-gray-600 mt-1">Added This Week</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-bold text-emerald-600">{{ $categories->count() }}</div>
|
||||||
|
<div class="text-sm text-gray-600 mt-1">Categories</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Featured Jobs Section -->
|
||||||
|
<div class="bg-gray-50 py-12">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center mb-8">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900">Featured Jobs</h2>
|
||||||
|
<a href="{{ route('jobs.index') }}" class="text-emerald-600 hover:text-emerald-700 font-medium">
|
||||||
|
View all jobs →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($featuredJobs->isNotEmpty())
|
||||||
|
<div class="grid gap-6 md:grid-cols-2">
|
||||||
|
@foreach($featuredJobs as $job)
|
||||||
|
<x-job-card :job="$job" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="text-center py-12 bg-white rounded-lg border border-gray-200">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No jobs available</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">Check back later for new opportunities.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Jobs Section -->
|
||||||
|
<div class="bg-white py-12">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 mb-8">Recently Posted</h2>
|
||||||
|
|
||||||
|
@if($recentJobs->isNotEmpty())
|
||||||
|
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
@foreach($recentJobs as $job)
|
||||||
|
<x-job-card :job="$job" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="text-center py-12 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
|
<p class="text-gray-500">No recent jobs posted.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Categories Section -->
|
||||||
|
<div class="bg-gray-50 py-12">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 mb-8">Browse by Category</h2>
|
||||||
|
|
||||||
|
@if($categories->isNotEmpty())
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
||||||
|
@foreach($categories as $category)
|
||||||
|
<a href="{{ route('jobs.category', $category->slug) }}"
|
||||||
|
class="bg-white p-4 rounded-lg border border-gray-200 hover:border-emerald-500 hover:shadow-md transition-all duration-200 text-center group">
|
||||||
|
<div class="text-2xl mb-2">{{ $category->icon ?? '📁' }}</div>
|
||||||
|
<div class="text-sm font-medium text-gray-900 group-hover:text-emerald-600">{{ $category->name }}</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">{{ $category->jobs_count }} jobs</div>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-gray-500 text-center">No categories available yet.</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
|
<div class="bg-emerald-600">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
<div class="text-center">
|
||||||
|
<h2 class="text-3xl font-bold text-white mb-4">Ready to Find Your Next Opportunity?</h2>
|
||||||
|
<p class="text-emerald-50 mb-8 max-w-2xl mx-auto">
|
||||||
|
Join thousands of job seekers who have found their dream careers through JobifyGarden.
|
||||||
|
</p>
|
||||||
|
<a href="{{ route('jobs.index') }}"
|
||||||
|
class="inline-flex items-center px-8 py-3 bg-white text-emerald-600 font-semibold rounded-lg hover:bg-emerald-50 transition-colors duration-200">
|
||||||
|
Browse All Jobs
|
||||||
|
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', $category->name . ' Jobs - JobifyGarden')
|
||||||
|
@section('description', 'Browse ' . $category->name . ' job opportunities. ' . ($category->description ?? ''))
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="py-8">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<span class="text-4xl mr-3">{{ $category->icon ?? '📁' }}</span>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900">{{ $category->name }} Jobs</h1>
|
||||||
|
@if($category->description)
|
||||||
|
<p class="text-gray-600 mt-1">{{ $category->description }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600">{{ $jobs->total() }} jobs found in this category</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||||
|
<!-- Filters Sidebar -->
|
||||||
|
<aside class="lg:col-span-1">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 sticky top-4">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">Filters</h2>
|
||||||
|
|
||||||
|
<form action="{{ route('jobs.category', $category->slug) }}" method="GET" class="space-y-6">
|
||||||
|
<!-- Keywords -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Keywords</label>
|
||||||
|
<input type="text"
|
||||||
|
name="keywords"
|
||||||
|
value="{{ $filters['keywords'] ?? '' }}"
|
||||||
|
placeholder="Job title, skills..."
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Location</label>
|
||||||
|
<input type="text"
|
||||||
|
name="location"
|
||||||
|
value="{{ $filters['location'] ?? '' }}"
|
||||||
|
placeholder="City, state, or country"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Type -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Job Type</label>
|
||||||
|
<select name="job_type"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Types</option>
|
||||||
|
@foreach($filterOptions['job_types'] ?? [] as $type)
|
||||||
|
<option value="{{ $type }}" {{ ($filters['job_type'] ?? '') == $type ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $type)) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Experience Level -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Experience Level</label>
|
||||||
|
<select name="experience_level"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Levels</option>
|
||||||
|
@foreach($filterOptions['experience_levels'] ?? [] as $level)
|
||||||
|
<option value="{{ $level }}" {{ ($filters['experience_level'] ?? '') == $level ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $level)) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Remote -->
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="remote"
|
||||||
|
value="1"
|
||||||
|
{{ ($filters['remote'] ?? false) ? 'checked' : '' }}
|
||||||
|
class="rounded border-gray-300 text-emerald-600 focus:ring-emerald-500">
|
||||||
|
<span class="ml-2 text-sm text-gray-700">Remote jobs only</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<button type="submit"
|
||||||
|
class="w-full px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white font-medium rounded-md transition-colors duration-200">
|
||||||
|
Apply Filters
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Jobs List -->
|
||||||
|
<div class="lg:col-span-3">
|
||||||
|
@if($jobs->count() > 0)
|
||||||
|
<div class="space-y-4">
|
||||||
|
@foreach($jobs as $job)
|
||||||
|
<x-job-card :job="$job" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="mt-8">
|
||||||
|
{{ $jobs->appends($filters)->links() }}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="bg-white rounded-lg border border-gray-200 p-12 text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No jobs found</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">Try adjusting your search or filter criteria.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Browse Jobs - JobifyGarden')
|
||||||
|
@section('description', 'Browse thousands of job opportunities. Filter by location, job type, experience level, and more.')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="py-8">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">Browse Jobs</h1>
|
||||||
|
<p class="text-gray-600">{{ $jobs->total() }} jobs found</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||||
|
<!-- Filters Sidebar -->
|
||||||
|
<aside class="lg:col-span-1">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 sticky top-4"
|
||||||
|
x-data="{
|
||||||
|
showFilters: true,
|
||||||
|
jobType: '{{ $filters['job_type'] ?? '' }}',
|
||||||
|
experienceLevel: '{{ $filters['experience_level'] ?? '' }}',
|
||||||
|
remote: {{ ($filters['remote'] ?? false) ? 'true' : 'false' }}
|
||||||
|
}">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900">Filters</h2>
|
||||||
|
@if(!empty(array_filter($filters)))
|
||||||
|
<a href="{{ route('jobs.index') }}" class="text-sm text-emerald-600 hover:text-emerald-700">Clear all</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ route('jobs.index') }}" method="GET" class="space-y-6">
|
||||||
|
<!-- Keywords -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Keywords</label>
|
||||||
|
<input type="text"
|
||||||
|
name="keywords"
|
||||||
|
value="{{ $filters['keywords'] ?? '' }}"
|
||||||
|
placeholder="Job title, skills..."
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Location</label>
|
||||||
|
<input type="text"
|
||||||
|
name="location"
|
||||||
|
value="{{ $filters['location'] ?? '' }}"
|
||||||
|
placeholder="City, state, or country"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Category</label>
|
||||||
|
<select name="category_id"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Categories</option>
|
||||||
|
@foreach($categories as $category)
|
||||||
|
<option value="{{ $category->id }}" {{ ($filters['category_id'] ?? '') == $category->id ? 'selected' : '' }}>
|
||||||
|
{{ $category->name }} ({{ $category->jobs_count }})
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Type -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Job Type</label>
|
||||||
|
<select name="job_type"
|
||||||
|
x-model="jobType"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Types</option>
|
||||||
|
@foreach($filterOptions['job_types'] ?? [] as $type)
|
||||||
|
<option value="{{ $type }}">{{ ucfirst(str_replace('_', ' ', $type)) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Experience Level -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Experience Level</label>
|
||||||
|
<select name="experience_level"
|
||||||
|
x-model="experienceLevel"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Levels</option>
|
||||||
|
@foreach($filterOptions['experience_levels'] ?? [] as $level)
|
||||||
|
<option value="{{ $level }}">{{ ucfirst(str_replace('_', ' ', $level)) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Remote -->
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="remote"
|
||||||
|
value="1"
|
||||||
|
x-model="remote"
|
||||||
|
{{ ($filters['remote'] ?? false) ? 'checked' : '' }}
|
||||||
|
class="rounded border-gray-300 text-emerald-600 focus:ring-emerald-500">
|
||||||
|
<span class="ml-2 text-sm text-gray-700">Remote jobs only</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Salary Range -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Minimum Salary</label>
|
||||||
|
<input type="number"
|
||||||
|
name="salary_min"
|
||||||
|
value="{{ $filters['salary_min'] ?? '' }}"
|
||||||
|
placeholder="e.g., 50000"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Posted Within -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Posted Within</label>
|
||||||
|
<select name="posted_within"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">Any time</option>
|
||||||
|
<option value="1" {{ ($filters['posted_within'] ?? '') == '1' ? 'selected' : '' }}>Last 24 hours</option>
|
||||||
|
<option value="7" {{ ($filters['posted_within'] ?? '') == '7' ? 'selected' : '' }}>Last 7 days</option>
|
||||||
|
<option value="30" {{ ($filters['posted_within'] ?? '') == '30' ? 'selected' : '' }}>Last 30 days</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<button type="submit"
|
||||||
|
class="w-full px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white font-medium rounded-md transition-colors duration-200">
|
||||||
|
Apply Filters
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Jobs List -->
|
||||||
|
<div class="lg:col-span-3">
|
||||||
|
<!-- Sort Options -->
|
||||||
|
<div class="mb-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
Showing {{ $jobs->firstItem() ?? 0 }} - {{ $jobs->lastItem() ?? 0 }} of {{ $jobs->total() }} jobs
|
||||||
|
</div>
|
||||||
|
<form action="{{ route('jobs.index') }}" method="GET" class="flex items-center gap-2">
|
||||||
|
<!-- Preserve existing filters -->
|
||||||
|
@foreach($filters as $key => $value)
|
||||||
|
@if($key !== 'sort' && $value)
|
||||||
|
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<label class="text-sm text-gray-700">Sort by:</label>
|
||||||
|
<select name="sort"
|
||||||
|
onchange="this.form.submit()"
|
||||||
|
class="px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="posted_at_desc" {{ ($filters['sort'] ?? 'posted_at_desc') === 'posted_at_desc' ? 'selected' : '' }}>Newest First</option>
|
||||||
|
<option value="posted_at_asc" {{ ($filters['sort'] ?? '') === 'posted_at_asc' ? 'selected' : '' }}>Oldest First</option>
|
||||||
|
<option value="salary_desc" {{ ($filters['sort'] ?? '') === 'salary_desc' ? 'selected' : '' }}>Highest Salary</option>
|
||||||
|
<option value="salary_asc" {{ ($filters['sort'] ?? '') === 'salary_asc' ? 'selected' : '' }}>Lowest Salary</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Listings -->
|
||||||
|
@if($jobs->count() > 0)
|
||||||
|
<div class="space-y-4">
|
||||||
|
@foreach($jobs as $job)
|
||||||
|
<x-job-card :job="$job" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="mt-8">
|
||||||
|
{{ $jobs->appends($filters)->links() }}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="bg-white rounded-lg border border-gray-200 p-12 text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No jobs found</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">Try adjusting your search or filter criteria.</p>
|
||||||
|
<div class="mt-6">
|
||||||
|
<a href="{{ route('jobs.index') }}"
|
||||||
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-emerald-700 bg-emerald-100 hover:bg-emerald-200">
|
||||||
|
Clear all filters
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Remote Jobs - JobifyGarden')
|
||||||
|
@section('description', 'Discover remote job opportunities from companies worldwide. Work from anywhere.')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="py-8">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<span class="text-4xl mr-3">🌍</span>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900">Remote Jobs</h1>
|
||||||
|
<p class="text-gray-600 mt-1">Work from anywhere in the world</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600">{{ $jobs->total() }} remote jobs available</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||||
|
<!-- Filters Sidebar -->
|
||||||
|
<aside class="lg:col-span-1">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 sticky top-4">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">Filters</h2>
|
||||||
|
|
||||||
|
<form action="{{ route('jobs.remote') }}" method="GET" class="space-y-6">
|
||||||
|
<!-- Keywords -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Keywords</label>
|
||||||
|
<input type="text"
|
||||||
|
name="keywords"
|
||||||
|
value="{{ $filters['keywords'] ?? '' }}"
|
||||||
|
placeholder="Job title, skills..."
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Category</label>
|
||||||
|
<select name="category_id"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Categories</option>
|
||||||
|
@foreach($categories as $category)
|
||||||
|
<option value="{{ $category->id }}" {{ ($filters['category_id'] ?? '') == $category->id ? 'selected' : '' }}>
|
||||||
|
{{ $category->name }} ({{ $category->jobs_count }})
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Type -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Job Type</label>
|
||||||
|
<select name="job_type"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Types</option>
|
||||||
|
@foreach($filterOptions['job_types'] ?? [] as $type)
|
||||||
|
<option value="{{ $type }}" {{ ($filters['job_type'] ?? '') == $type ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $type)) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Experience Level -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Experience Level</label>
|
||||||
|
<select name="experience_level"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
<option value="">All Levels</option>
|
||||||
|
@foreach($filterOptions['experience_levels'] ?? [] as $level)
|
||||||
|
<option value="{{ $level }}" {{ ($filters['experience_level'] ?? '') == $level ? 'selected' : '' }}>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $level)) }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Minimum Salary -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Minimum Salary</label>
|
||||||
|
<input type="number"
|
||||||
|
name="salary_min"
|
||||||
|
value="{{ $filters['salary_min'] ?? '' }}"
|
||||||
|
placeholder="e.g., 50000"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-emerald-500 focus:border-emerald-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<button type="submit"
|
||||||
|
class="w-full px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white font-medium rounded-md transition-colors duration-200">
|
||||||
|
Apply Filters
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Jobs List -->
|
||||||
|
<div class="lg:col-span-3">
|
||||||
|
@if($jobs->count() > 0)
|
||||||
|
<div class="space-y-4">
|
||||||
|
@foreach($jobs as $job)
|
||||||
|
<x-job-card :job="$job" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="mt-8">
|
||||||
|
{{ $jobs->appends($filters)->links() }}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="bg-white rounded-lg border border-gray-200 p-12 text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No remote jobs found</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">Check back later for new remote opportunities.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', $job->title . ' at ' . $job->company . ' - JobifyGarden')
|
||||||
|
@section('description', Str::limit(strip_tags($job->description), 155))
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<!-- Back Button -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<a href="javascript:history.back()" class="inline-flex items-center text-emerald-600 hover:text-emerald-700">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||||
|
</svg>
|
||||||
|
Back to results
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
|
||||||
|
<!-- Job Header -->
|
||||||
|
<div class="mb-6 pb-6 border-b border-gray-200">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">{{ $job->title }}</h1>
|
||||||
|
<h2 class="text-xl text-gray-700 mb-3">{{ $job->company }}</h2>
|
||||||
|
</div>
|
||||||
|
@if($job->remote_allowed)
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-emerald-100 text-emerald-800">
|
||||||
|
Remote
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-4 text-sm text-gray-600">
|
||||||
|
@if($job->location)
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ $job->location }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($job->job_type)
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $job->job_type)) }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($job->experience_level)
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $job->experience_level)) }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
Posted {{ $job->posted_at ? $job->posted_at->diffForHumans() : $job->created_at->diffForHumans() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($job->salary_min || $job->salary_max)
|
||||||
|
<div class="mt-4">
|
||||||
|
<span class="text-lg font-semibold text-emerald-600">
|
||||||
|
@if($job->salary_min && $job->salary_max)
|
||||||
|
{{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_min) }} - {{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_max) }}
|
||||||
|
@elseif($job->salary_min)
|
||||||
|
{{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_min) }}+
|
||||||
|
@elseif($job->salary_max)
|
||||||
|
Up to {{ $job->salary_currency ?? '$' }}{{ number_format($job->salary_max) }}
|
||||||
|
@endif
|
||||||
|
<span class="text-sm text-gray-600">per year</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Description -->
|
||||||
|
<div class="prose prose-emerald max-w-none mb-8">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-4">Job Description</h3>
|
||||||
|
<div class="text-gray-700 leading-relaxed">
|
||||||
|
{!! nl2br(e($job->description)) !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category -->
|
||||||
|
@if($job->category)
|
||||||
|
<div class="mb-6">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-900 mb-2">Category</h3>
|
||||||
|
<a href="{{ route('jobs.category', $job->category->slug) }}"
|
||||||
|
class="inline-flex items-center px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-full text-sm text-gray-700">
|
||||||
|
{{ $job->category->icon ?? '📁' }} {{ $job->category->name }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Job Source -->
|
||||||
|
@if($job->source)
|
||||||
|
<div class="mb-6 text-sm text-gray-600">
|
||||||
|
<p>Job posted via <span class="font-medium">{{ $job->source->name }}</span></p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="lg:col-span-1">
|
||||||
|
<div class="sticky top-4 space-y-6">
|
||||||
|
<!-- Apply Button -->
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<a href="{{ $job->apply_url ?? $job->source_url }}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="block w-full px-6 py-3 bg-emerald-600 hover:bg-emerald-700 text-white text-center font-semibold rounded-lg transition-colors duration-200">
|
||||||
|
Apply Now
|
||||||
|
<svg class="inline-block w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<p class="text-xs text-gray-500 text-center mt-3">You will be redirected to the company's application page</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Share -->
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-900 mb-3">Share this job</h3>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a href="https://twitter.com/intent/tweet?text={{ urlencode($job->title . ' at ' . $job->company) }}&url={{ urlencode(route('jobs.show', $job->id)) }}"
|
||||||
|
target="_blank"
|
||||||
|
class="flex-1 px-3 py-2 bg-blue-500 hover:bg-blue-600 text-white text-center rounded text-sm">
|
||||||
|
Twitter
|
||||||
|
</a>
|
||||||
|
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ urlencode(route('jobs.show', $job->id)) }}"
|
||||||
|
target="_blank"
|
||||||
|
class="flex-1 px-3 py-2 bg-blue-700 hover:bg-blue-800 text-white text-center rounded text-sm">
|
||||||
|
LinkedIn
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Report -->
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
<button class="text-sm text-red-600 hover:text-red-700">
|
||||||
|
Report this job
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Similar Jobs -->
|
||||||
|
@if($similarJobs->count() > 0)
|
||||||
|
<div class="mt-12">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-6">Similar Jobs</h2>
|
||||||
|
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
@foreach($similarJobs as $similarJob)
|
||||||
|
<x-job-card :job="$similarJob" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
|
<title>@yield('title', 'JobifyGarden - Find Your Dream Job')</title>
|
||||||
|
<meta name="description" content="@yield('description', 'Discover thousands of job opportunities from multiple sources. Find remote jobs, full-time positions, and more.')">
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||||
|
<link href="https://fonts.bunny.net/css?family=inter:400,500,600,700&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
|
|
||||||
|
@stack('head')
|
||||||
|
</head>
|
||||||
|
<body class="h-full antialiased bg-gray-50">
|
||||||
|
<div class="min-h-full">
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="bg-white shadow-sm border-b border-gray-200" x-data="{ mobileMenuOpen: false }">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<!-- Logo and main navigation -->
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0 flex items-center">
|
||||||
|
<a href="{{ route('home') }}" class="text-2xl font-bold text-emerald-600 hover:text-emerald-700">
|
||||||
|
JobifyGarden
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:ml-8 sm:flex sm:space-x-8">
|
||||||
|
<a href="{{ route('jobs.index') }}"
|
||||||
|
class="inline-flex items-center px-1 pt-1 text-sm font-medium {{ request()->routeIs('jobs.*') ? 'border-b-2 border-emerald-500 text-gray-900' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
|
||||||
|
Browse Jobs
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('jobs.remote') }}"
|
||||||
|
class="inline-flex items-center px-1 pt-1 text-sm font-medium {{ request()->routeIs('jobs.remote') ? 'border-b-2 border-emerald-500 text-gray-900' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
|
||||||
|
Remote Jobs
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile menu button -->
|
||||||
|
<div class="flex items-center sm:hidden">
|
||||||
|
<button @click="mobileMenuOpen = !mobileMenuOpen"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-emerald-500">
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<svg class="h-6 w-6" :class="{ 'hidden': mobileMenuOpen, 'block': !mobileMenuOpen }" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
</svg>
|
||||||
|
<svg class="h-6 w-6" :class="{ 'block': mobileMenuOpen, 'hidden': !mobileMenuOpen }" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile menu -->
|
||||||
|
<div x-show="mobileMenuOpen"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
class="sm:hidden"
|
||||||
|
style="display: none;">
|
||||||
|
<div class="pt-2 pb-3 space-y-1">
|
||||||
|
<a href="{{ route('jobs.index') }}"
|
||||||
|
class="block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('jobs.*') ? 'border-emerald-500 text-emerald-700 bg-emerald-50' : 'border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700' }}">
|
||||||
|
Browse Jobs
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('jobs.remote') }}"
|
||||||
|
class="block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('jobs.remote') ? 'border-emerald-500 text-emerald-700 bg-emerald-50' : 'border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700' }}">
|
||||||
|
Remote Jobs
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page header (optional) -->
|
||||||
|
@hasSection('header')
|
||||||
|
<header class="bg-white shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
||||||
|
@yield('header')
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<main class="@yield('main-class', 'py-8')">
|
||||||
|
@yield('content')
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-white border-t border-gray-200 mt-auto">
|
||||||
|
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||||
|
<!-- About -->
|
||||||
|
<div class="col-span-1 md:col-span-2">
|
||||||
|
<h3 class="text-lg font-bold text-emerald-600 mb-4">JobifyGarden</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-4">
|
||||||
|
Your gateway to thousands of job opportunities. We aggregate job listings from multiple sources to help you find your dream career.
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-500 text-xs">
|
||||||
|
© {{ date('Y') }} JobifyGarden. All rights reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Links -->
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">Quick Links</h4>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
<li><a href="{{ route('jobs.index') }}" class="text-gray-600 hover:text-emerald-600 text-sm">Browse Jobs</a></li>
|
||||||
|
<li><a href="{{ route('jobs.remote') }}" class="text-gray-600 hover:text-emerald-600 text-sm">Remote Jobs</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Resources -->
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">Resources</h4>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
<li><a href="#" class="text-gray-600 hover:text-emerald-600 text-sm">About Us</a></li>
|
||||||
|
<li><a href="#" class="text-gray-600 hover:text-emerald-600 text-sm">Contact</a></li>
|
||||||
|
<li><a href="#" class="text-gray-600 hover:text-emerald-600 text-sm">Privacy Policy</a></li>
|
||||||
|
<li><a href="#" class="text-gray-600 hover:text-emerald-600 text-sm">Terms of Service</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stack('scripts')
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user