fixes
This commit is contained in:
@@ -2,6 +2,34 @@
|
||||
|
||||
A comprehensive job aggregator platform built with Laravel 13 that fetches, stores, and displays job listings from multiple free job APIs.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <repository-url> && cd jobifygarden
|
||||
|
||||
# Install dependencies
|
||||
composer install
|
||||
|
||||
# Start Docker environment (includes MySQL, Redis)
|
||||
./vendor/bin/sail up -d
|
||||
|
||||
# Setup application
|
||||
./vendor/bin/sail artisan key:generate
|
||||
./vendor/bin/sail artisan migrate
|
||||
./vendor/bin/sail artisan db:seed
|
||||
|
||||
# Install & build frontend assets
|
||||
./vendor/bin/sail npm install && ./vendor/bin/sail npm run dev
|
||||
|
||||
# Start queue worker (in a separate terminal)
|
||||
./vendor/bin/sail artisan queue:work
|
||||
|
||||
# Visit http://localhost
|
||||
```
|
||||
|
||||
**Important:** After setup, configure the cron job (see [Cron Jobs](#cron-jobs-required) section) to enable automatic job fetching.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **PHP**: 8.3.x or higher
|
||||
@@ -40,7 +68,81 @@ A comprehensive job aggregator platform built with Laravel 13 that fetches, stor
|
||||
|
||||
## Installation
|
||||
|
||||
### Development Setup
|
||||
### Development Setup (Laravel Sail - Recommended)
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd jobifygarden
|
||||
```
|
||||
|
||||
2. **Install PHP dependencies**
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
3. **Copy environment file**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
4. **Configure environment variables**
|
||||
Edit `.env` and update the following:
|
||||
```env
|
||||
APP_NAME=JobifyGarden
|
||||
APP_ENV=local
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
# Database (Sail uses these defaults)
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=jobifygarden
|
||||
DB_USERNAME=sail
|
||||
DB_PASSWORD=password
|
||||
|
||||
# Cache & Sessions (Redis)
|
||||
CACHE_STORE=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
# Redis (Sail default)
|
||||
REDIS_HOST=redis
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
```
|
||||
|
||||
5. **Start Docker containers**
|
||||
```bash
|
||||
./vendor/bin/sail up -d
|
||||
```
|
||||
|
||||
6. **Generate application key**
|
||||
```bash
|
||||
./vendor/bin/sail artisan key:generate
|
||||
```
|
||||
|
||||
7. **Run database migrations**
|
||||
```bash
|
||||
./vendor/bin/sail artisan migrate
|
||||
```
|
||||
|
||||
8. **Seed initial data (optional)**
|
||||
```bash
|
||||
./vendor/bin/sail artisan db:seed
|
||||
```
|
||||
|
||||
9. **Install frontend dependencies and build assets**
|
||||
```bash
|
||||
./vendor/bin/sail npm install
|
||||
./vendor/bin/sail npm run dev
|
||||
```
|
||||
|
||||
10. **Visit the application**
|
||||
Open `http://localhost` in your browser.
|
||||
|
||||
### Production Setup (Traditional Server)
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
@@ -50,8 +152,9 @@ cd jobifygarden
|
||||
|
||||
2. **Install dependencies**
|
||||
```bash
|
||||
composer install
|
||||
composer install --optimize-autoloader --no-dev
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
3. **Configure environment**
|
||||
@@ -60,72 +163,141 @@ cp .env.example .env
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
4. **Update `.env` with your database credentials**
|
||||
4. **Update `.env` for production**
|
||||
```env
|
||||
APP_NAME=JobifyGarden
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://yourdomain.com
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=jobifygarden
|
||||
DB_USERNAME=your_username
|
||||
DB_PASSWORD=your_password
|
||||
DB_PASSWORD=your_secure_password
|
||||
|
||||
CACHE_STORE=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=your_redis_password
|
||||
REDIS_PORT=6379
|
||||
```
|
||||
|
||||
5. **Run migrations**
|
||||
```bash
|
||||
php artisan migrate
|
||||
php artisan migrate --force
|
||||
```
|
||||
|
||||
6. **Seed the database (optional)**
|
||||
6. **Optimize for production**
|
||||
```bash
|
||||
php artisan db:seed
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan optimize
|
||||
```
|
||||
|
||||
7. **Build frontend assets**
|
||||
7. **Set proper permissions**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
8. **Start the development server**
|
||||
```bash
|
||||
php artisan serve
|
||||
```
|
||||
|
||||
Visit `http://localhost:8000` to view the application.
|
||||
|
||||
### Docker Setup (Laravel Sail)
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
composer install
|
||||
|
||||
# Start Docker containers
|
||||
./vendor/bin/sail up -d
|
||||
|
||||
# Run migrations
|
||||
./vendor/bin/sail artisan migrate
|
||||
|
||||
# Build assets
|
||||
./vendor/bin/sail npm install
|
||||
./vendor/bin/sail npm run dev
|
||||
chown -R www-data:www-data storage bootstrap/cache
|
||||
chmod -R 775 storage bootstrap/cache
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed production deployment instructions.
|
||||
|
||||
## Required Services
|
||||
|
||||
### Queue Worker (Required)
|
||||
|
||||
The application uses queues for background job processing. You must run a queue worker:
|
||||
|
||||
**Using Laravel Sail (Development):**
|
||||
```bash
|
||||
./vendor/bin/sail artisan queue:work --sleep=3 --tries=3 --timeout=90
|
||||
```
|
||||
|
||||
**Using Supervisor (Production):**
|
||||
Create `/etc/supervisor/conf.d/jobifygarden-worker.conf`:
|
||||
```ini
|
||||
[program:jobifygarden-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /path/to/jobifygarden/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
numprocs=2
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/path/to/jobifygarden/storage/logs/worker.log
|
||||
stopwaitsecs=3600
|
||||
```
|
||||
|
||||
Then reload supervisor:
|
||||
```bash
|
||||
sudo supervisorctl reread
|
||||
sudo supervisorctl update
|
||||
sudo supervisorctl start jobifygarden-worker:*
|
||||
```
|
||||
|
||||
### Cron Jobs (Required)
|
||||
|
||||
Laravel's task scheduler must run every minute. Add this to your crontab:
|
||||
|
||||
**Edit crontab:**
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
**Add this line:**
|
||||
```bash
|
||||
* * * * * cd /path/to/jobifygarden && php artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
**For Laravel Sail (Development):**
|
||||
```bash
|
||||
* * * * * cd /path/to/jobifygarden && ./vendor/bin/sail artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
### Scheduled Tasks
|
||||
|
||||
The following tasks are automatically scheduled and will run via the cron job above:
|
||||
|
||||
| Task | Schedule | Description |
|
||||
|------|----------|-------------|
|
||||
| `jobs:fetch` | Every 2 hours | Fetch new jobs from all API sources |
|
||||
| `jobs:clean` | Daily at 3:00 AM | Remove expired and old jobs |
|
||||
| `jobs:deduplicate` | Daily at 4:00 AM | Remove duplicate job listings |
|
||||
| `cache:prune-stale-tags` | Hourly | Clean up stale cache tags |
|
||||
|
||||
**Note:** Job fetching intervals can be customized in `app/Console/Kernel.php`
|
||||
|
||||
## Available Commands
|
||||
|
||||
### Job Management
|
||||
```bash
|
||||
# Fetch jobs from all sources
|
||||
# Fetch jobs from all sources (automatically scheduled)
|
||||
php artisan jobs:fetch
|
||||
|
||||
# Clean expired jobs
|
||||
# Fetch from a specific source
|
||||
php artisan jobs:fetch --source=indeed
|
||||
|
||||
# Clean expired jobs (automatically scheduled)
|
||||
php artisan jobs:clean
|
||||
|
||||
# Remove duplicate jobs
|
||||
# Clean jobs older than specific days
|
||||
php artisan jobs:clean --days=60
|
||||
|
||||
# Remove duplicate jobs (automatically scheduled)
|
||||
php artisan jobs:deduplicate
|
||||
|
||||
# Force dedupe even with different sources
|
||||
php artisan jobs:deduplicate --force
|
||||
|
||||
# Enrich job data (categories, experience levels)
|
||||
php artisan jobs:enrich
|
||||
|
||||
@@ -133,24 +305,55 @@ php artisan jobs:enrich
|
||||
php artisan jobs:test-apis
|
||||
```
|
||||
|
||||
### Database Optimization
|
||||
### Database Management
|
||||
```bash
|
||||
# Optimize database tables and rebuild indexes
|
||||
# Run migrations
|
||||
php artisan migrate
|
||||
|
||||
# Rollback last migration
|
||||
php artisan migrate:rollback
|
||||
|
||||
# Fresh migration with seeding
|
||||
php artisan migrate:fresh --seed
|
||||
|
||||
# Optimize database tables
|
||||
php artisan db:optimize
|
||||
```
|
||||
|
||||
### Cache Management
|
||||
```bash
|
||||
# Clear all caches
|
||||
php artisan optimize:clear
|
||||
|
||||
# Clear specific caches
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
php artisan view:clear
|
||||
|
||||
# Build caches for production
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan optimize
|
||||
```
|
||||
|
||||
### Queue Management
|
||||
```bash
|
||||
# Start queue worker
|
||||
php artisan queue:work
|
||||
|
||||
# Process jobs with options
|
||||
php artisan queue:work --queue=high,default --sleep=3 --tries=3
|
||||
|
||||
# List failed jobs
|
||||
php artisan queue:failed
|
||||
|
||||
# Retry all failed jobs
|
||||
php artisan queue:retry all
|
||||
|
||||
# Retry specific failed job
|
||||
php artisan queue:retry {id}
|
||||
|
||||
# Clear all failed jobs
|
||||
php artisan queue:flush
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
@@ -194,8 +397,9 @@ GET /api/jobs/autocomplete/companies # Company autocomplete
|
||||
|
||||
### Backend
|
||||
- **Laravel 13**: PHP framework
|
||||
- **PHP 8.3+**: Programming language
|
||||
- **MySQL 8**: Primary database
|
||||
- **Redis**: Caching and queues
|
||||
- **Redis**: Caching, sessions, and queues
|
||||
- **Guzzle**: HTTP client for API requests
|
||||
|
||||
### Frontend
|
||||
@@ -204,6 +408,114 @@ GET /api/jobs/autocomplete/companies # Company autocomplete
|
||||
- **TailwindCSS**: Utility-first CSS framework
|
||||
- **Vite**: Frontend build tool
|
||||
|
||||
### DevOps
|
||||
- **Laravel Sail**: Docker development environment
|
||||
- **Supervisor**: Process manager for queue workers
|
||||
- **Nginx/Apache**: Web server
|
||||
- **Certbot**: SSL certificate management
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**1. Queue jobs not processing**
|
||||
```bash
|
||||
# Check if queue worker is running
|
||||
ps aux | grep "queue:work"
|
||||
|
||||
# Restart queue worker (Supervisor)
|
||||
sudo supervisorctl restart jobifygarden-worker:*
|
||||
|
||||
# Or start manually for debugging
|
||||
php artisan queue:work --verbose
|
||||
```
|
||||
|
||||
**2. Jobs not being fetched automatically**
|
||||
```bash
|
||||
# Verify cron is set up
|
||||
crontab -l
|
||||
|
||||
# Test schedule manually
|
||||
php artisan schedule:run
|
||||
|
||||
# Check schedule list
|
||||
php artisan schedule:list
|
||||
```
|
||||
|
||||
**3. Redis connection errors**
|
||||
```bash
|
||||
# Check Redis is running
|
||||
redis-cli ping
|
||||
|
||||
# For Sail
|
||||
./vendor/bin/sail redis redis-cli ping
|
||||
|
||||
# Restart Redis (Sail)
|
||||
./vendor/bin/sail down
|
||||
./vendor/bin/sail up -d
|
||||
```
|
||||
|
||||
**4. Permission errors**
|
||||
```bash
|
||||
# Fix storage permissions
|
||||
sudo chown -R www-data:www-data storage bootstrap/cache
|
||||
sudo chmod -R 775 storage bootstrap/cache
|
||||
|
||||
# For development (use your user)
|
||||
sudo chown -R $USER:$USER storage bootstrap/cache
|
||||
chmod -R 775 storage bootstrap/cache
|
||||
```
|
||||
|
||||
**5. Slugs not generating for new jobs**
|
||||
```bash
|
||||
# Regenerate slugs for existing jobs
|
||||
php artisan tinker
|
||||
# Then run:
|
||||
# \App\Models\Job::whereNull('slug')->chunk(100, function($jobs) {
|
||||
# foreach($jobs as $job) {
|
||||
# $job->slug = \App\Models\Job::generateUniqueSlug($job->title, $job->company);
|
||||
# $job->save();
|
||||
# }
|
||||
# });
|
||||
```
|
||||
|
||||
**6. Cache issues**
|
||||
```bash
|
||||
# Clear all caches
|
||||
php artisan optimize:clear
|
||||
|
||||
# Rebuild for production
|
||||
php artisan optimize
|
||||
```
|
||||
|
||||
### Performance Tips
|
||||
|
||||
1. **Enable OPcache in production** (add to `php.ini`):
|
||||
```ini
|
||||
opcache.enable=1
|
||||
opcache.memory_consumption=256
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=20000
|
||||
opcache.validate_timestamps=0
|
||||
```
|
||||
|
||||
2. **Optimize database queries:**
|
||||
```bash
|
||||
php artisan db:optimize
|
||||
```
|
||||
|
||||
3. **Use queue workers with multiple processes:**
|
||||
```bash
|
||||
# Update supervisor config to use more workers
|
||||
numprocs=4
|
||||
```
|
||||
|
||||
4. **Monitor slow queries:**
|
||||
```bash
|
||||
# Enable query logging in .env
|
||||
DB_LOG_QUERIES=true
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
|
||||
@@ -25,6 +25,19 @@ class HomeController extends Controller
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('home', compact('featuredJobs', 'recentJobs', 'stats', 'categories'));
|
||||
// Get filter options for search form
|
||||
$jobTypes = \App\Models\Job::select('job_type')
|
||||
->whereNotNull('job_type')
|
||||
->distinct()
|
||||
->pluck('job_type')
|
||||
->toArray();
|
||||
|
||||
$experienceLevels = \App\Models\Job::select('experience_level')
|
||||
->whereNotNull('experience_level')
|
||||
->distinct()
|
||||
->pluck('experience_level')
|
||||
->toArray();
|
||||
|
||||
return view('home', compact('featuredJobs', 'recentJobs', 'stats', 'categories', 'jobTypes', 'experienceLevels'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ class JobController extends Controller
|
||||
/**
|
||||
* Display the specified job
|
||||
*/
|
||||
public function show(int $id)
|
||||
public function show(Job $job)
|
||||
{
|
||||
$job = Job::with(['source', 'category', 'jobLocation'])
|
||||
->findOrFail($id);
|
||||
// Load relationships
|
||||
$job->load(['source', 'category', 'jobLocation']);
|
||||
|
||||
// Track job view event
|
||||
AnalyticsEvent::logEvent(AnalyticsEvent::EVENT_JOB_VIEW, $job->id);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PageController extends Controller
|
||||
{
|
||||
public function about(): View
|
||||
{
|
||||
return view('pages.about');
|
||||
}
|
||||
|
||||
public function contact(): View
|
||||
{
|
||||
return view('pages.contact');
|
||||
}
|
||||
|
||||
public function privacy(): View
|
||||
{
|
||||
return view('pages.privacy');
|
||||
}
|
||||
|
||||
public function terms(): View
|
||||
{
|
||||
return view('pages.terms');
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,32 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Job extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($job) {
|
||||
if (empty($job->slug)) {
|
||||
$job->slug = static::generateUniqueSlug($job->title, $job->company);
|
||||
}
|
||||
});
|
||||
|
||||
static::updating(function ($job) {
|
||||
if ($job->isDirty('title') && empty($job->slug)) {
|
||||
$job->slug = static::generateUniqueSlug($job->title, $job->company);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
'external_id',
|
||||
'slug',
|
||||
'title',
|
||||
'company',
|
||||
'description',
|
||||
@@ -187,4 +206,40 @@ class Job extends Model
|
||||
{
|
||||
return $query->where('company', 'LIKE', "%{$company}%");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique slug for the job.
|
||||
*/
|
||||
public static function generateUniqueSlug(string $title, ?string $company = null): string
|
||||
{
|
||||
// Create base slug from title
|
||||
$baseSlug = Str::slug($title);
|
||||
|
||||
// Add company to slug if provided for better uniqueness
|
||||
if ($company) {
|
||||
$baseSlug .= '-' . Str::slug(Str::limit($company, 30, ''));
|
||||
}
|
||||
|
||||
// Limit slug length to avoid database issues
|
||||
$baseSlug = Str::limit($baseSlug, 200, '');
|
||||
|
||||
$slug = $baseSlug;
|
||||
$counter = 1;
|
||||
|
||||
// Check if slug exists and append counter if needed
|
||||
while (static::withTrashed()->where('slug', $slug)->exists()) {
|
||||
$slug = $baseSlug . '-' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
*/
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,16 @@ class AdzunaTransformer implements JobTransformer
|
||||
|
||||
private function sanitizeDescription(string $description): string
|
||||
{
|
||||
$clean = strip_tags($description);
|
||||
// Adzuna returns truncated descriptions with ellipsis (...)
|
||||
// Keep the description as-is but strip excessive HTML while preserving basic formatting
|
||||
$clean = strip_tags($description, '<p><br><ul><ol><li><strong><b><em><i><h1><h2><h3><h4>');
|
||||
$clean = html_entity_decode($clean, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$clean = preg_replace('/\s+/', ' ', $clean);
|
||||
|
||||
// Convert multiple spaces to single space but preserve newlines
|
||||
$clean = preg_replace('/[^\S\n]+/', ' ', $clean);
|
||||
|
||||
// Note: Adzuna API only provides truncated descriptions in search results.
|
||||
// Full descriptions would require individual job detail API calls.
|
||||
return trim($clean);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,9 +70,32 @@ class RemotiveTransformer implements JobTransformer
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to extract numbers from salary string
|
||||
if (preg_match_all('/[\d,]+/', $salary, $matches)) {
|
||||
$numbers = array_map(fn($n) => (float) str_replace(',', '', $n), $matches[0]);
|
||||
// Normalize the salary string
|
||||
$salary = strtolower($salary);
|
||||
|
||||
// Check if it's hourly rate - if so, convert to annual (assuming 40hr/week * 52 weeks = 2080 hours/year)
|
||||
$isHourly = str_contains($salary, '/hour') || str_contains($salary, 'per hour') || str_contains($salary, '/hr');
|
||||
|
||||
// Try to extract numbers with optional k/K suffix
|
||||
// Pattern: number followed by optional comma-separated digits, then optional 'k'
|
||||
if (preg_match_all('/\$?\s*([\d,]+)\s*([kK])?/', $salary, $matches, PREG_SET_ORDER)) {
|
||||
$numbers = [];
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$number = (float) str_replace(',', '', $match[1]);
|
||||
|
||||
// If there's a 'k' suffix, multiply by 1000
|
||||
if (!empty($match[2]) && strtolower($match[2]) === 'k') {
|
||||
$number *= 1000;
|
||||
}
|
||||
|
||||
// If it's hourly, convert to annual
|
||||
if ($isHourly) {
|
||||
$number *= 2080; // 40 hours/week * 52 weeks
|
||||
}
|
||||
|
||||
$numbers[] = $number;
|
||||
}
|
||||
|
||||
if (count($numbers) >= 2) {
|
||||
return $type === 'min' ? min($numbers) : max($numbers);
|
||||
|
||||
@@ -15,7 +15,7 @@ class SeoService
|
||||
{
|
||||
$title = $job->title . ' at ' . $job->company . ' - JobifyGarden';
|
||||
$description = Str::limit(strip_tags($job->description), 155);
|
||||
$url = route('jobs.show', $job->id);
|
||||
$url = route('jobs.show', $job);
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
|
||||
@@ -43,12 +43,12 @@ class SitemapService
|
||||
})
|
||||
->orderBy('posted_at', 'desc')
|
||||
->limit(10000) // Limit to 10k most recent jobs
|
||||
->get(['id', 'posted_at', 'updated_at']);
|
||||
->get(['id', 'slug', 'posted_at', 'updated_at']);
|
||||
|
||||
foreach ($jobs as $job) {
|
||||
$this->addUrl(
|
||||
$xml,
|
||||
route('jobs.show', $job->id),
|
||||
route('jobs.show', $job),
|
||||
$job->updated_at ?? $job->posted_at,
|
||||
'weekly',
|
||||
'0.7'
|
||||
@@ -150,12 +150,12 @@ class SitemapService
|
||||
})
|
||||
->orderBy('posted_at', 'desc')
|
||||
->limit(50000) // Sitemap limit
|
||||
->get(['id', 'posted_at', 'updated_at']);
|
||||
->get(['id', 'slug', 'posted_at', 'updated_at']);
|
||||
|
||||
foreach ($jobs as $job) {
|
||||
$this->addUrl(
|
||||
$xml,
|
||||
route('jobs.show', $job->id),
|
||||
route('jobs.show', $job),
|
||||
$job->updated_at ?? $job->posted_at,
|
||||
'weekly',
|
||||
'0.7'
|
||||
|
||||
@@ -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::table('jobs', function (Blueprint $table) {
|
||||
$table->string('slug')->unique()->nullable()->after('id');
|
||||
$table->index('slug');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('jobs', function (Blueprint $table) {
|
||||
$table->dropIndex(['slug']);
|
||||
$table->dropColumn('slug');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
<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">
|
||||
<a href="{{ route('jobs.show', $job) }}" class="hover:text-emerald-600">
|
||||
{{ $job->title }}
|
||||
</a>
|
||||
</h3>
|
||||
@@ -71,7 +71,7 @@
|
||||
<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) }}"
|
||||
<a href="{{ route('jobs.show', $job) }}"
|
||||
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">
|
||||
|
||||
+100
-12
@@ -37,35 +37,123 @@
|
||||
<!-- 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">
|
||||
<div class="text-center mb-10">
|
||||
<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">
|
||||
<p class="text-xl text-emerald-50 max-w-2xl mx-auto">
|
||||
Search through thousands of job listings from multiple sources. Your next career opportunity is just a search away.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<!-- Advanced Search Form -->
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<form action="{{ route('jobs.index') }}" method="GET" class="bg-white rounded-xl shadow-2xl p-6 space-y-4">
|
||||
<!-- Main Search Field -->
|
||||
<div>
|
||||
<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">
|
||||
placeholder="Job title, keywords, or company..."
|
||||
class="w-full px-4 py-4 text-lg rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-900 placeholder-gray-500">
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
|
||||
<!-- Filter Grid -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<!-- Category -->
|
||||
<div>
|
||||
<select name="category_id"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 bg-white">
|
||||
<option value="">All Categories</option>
|
||||
@foreach($categories as $category)
|
||||
<option value="{{ $category->id }}">{{ $category->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div>
|
||||
<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">
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 placeholder-gray-500">
|
||||
</div>
|
||||
|
||||
<!-- Job Type -->
|
||||
<div>
|
||||
<select name="job_type"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 bg-white">
|
||||
<option value="">All Job Types</option>
|
||||
@foreach($jobTypes as $type)
|
||||
<option value="{{ $type }}">{{ ucfirst(str_replace('_', ' ', $type)) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Experience Level -->
|
||||
<div>
|
||||
<select name="experience_level"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 bg-white">
|
||||
<option value="">All Experience Levels</option>
|
||||
@foreach($experienceLevels as $level)
|
||||
<option value="{{ $level }}">{{ ucfirst($level) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Filters Row -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<!-- Remote -->
|
||||
<div>
|
||||
<select name="remote"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 bg-white">
|
||||
<option value="">Remote/On-site</option>
|
||||
<option value="1">Remote Only</option>
|
||||
<option value="0">On-site Only</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Posted Within -->
|
||||
<div>
|
||||
<select name="posted_within"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 bg-white">
|
||||
<option value="">Any Time</option>
|
||||
<option value="1">Last 24 hours</option>
|
||||
<option value="7">Last 7 days</option>
|
||||
<option value="30">Last 30 days</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Salary Min -->
|
||||
<div>
|
||||
<input type="number"
|
||||
name="salary_min"
|
||||
placeholder="Min Salary"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 placeholder-gray-500">
|
||||
</div>
|
||||
|
||||
<!-- Salary Max -->
|
||||
<div>
|
||||
<input type="number"
|
||||
name="salary_max"
|
||||
placeholder="Max Salary"
|
||||
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-gray-700 placeholder-gray-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Button -->
|
||||
<div class="flex justify-center pt-2">
|
||||
<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">
|
||||
class="px-12 py-4 bg-emerald-600 hover:bg-emerald-700 text-white text-lg font-semibold rounded-lg transition-colors duration-200 shadow-lg hover:shadow-xl">
|
||||
<span 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 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
Search Jobs
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
"{{ '@' }}context": "https://schema.org",
|
||||
"{{ '@' }}type": "CollectionPage",
|
||||
"name": "{{ $category->name }} Jobs",
|
||||
"description": "{{ 'Browse ' . $jobs->total() . ' ' . $category->name . ' job opportunities' }}",
|
||||
"url": "{{ route('jobs.category', $category->slug) }}"
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
"{{ '@' }}context": "https://schema.org",
|
||||
"{{ '@' }}type": "CollectionPage",
|
||||
"name": "Browse Jobs",
|
||||
"description": "Browse thousands of job opportunities from multiple sources",
|
||||
"url": "{{ route('jobs.index') }}"
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
"{{ '@' }}context": "https://schema.org",
|
||||
"{{ '@' }}type": "CollectionPage",
|
||||
"name": "Remote Jobs",
|
||||
"description": "Work from anywhere remote job opportunities",
|
||||
"url": "{{ route('jobs.remote') }}"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@section('title', $job->title . ' at ' . $job->company . ' - JobifyGarden')
|
||||
@section('description', Str::limit(strip_tags($job->description), 155))
|
||||
@section('canonical', route('jobs.show', $job->id))
|
||||
@section('canonical', route('jobs.show', $job))
|
||||
|
||||
@push('structured_data')
|
||||
<script type="application/ld+json">
|
||||
@@ -20,7 +20,7 @@
|
||||
$breadcrumbsArray[] = ['name' => $job->category->name, 'url' => route('jobs.category', $job->category->slug)];
|
||||
}
|
||||
|
||||
$breadcrumbsArray[] = ['name' => $job->title, 'url' => route('jobs.show', $job->id)];
|
||||
$breadcrumbsArray[] = ['name' => $job->title, 'url' => route('jobs.show', $job)];
|
||||
|
||||
@endphp
|
||||
{!! json_encode(app(\App\Services\SeoService::class)->generateBreadcrumbStructuredData($breadcrumbsArray), JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) !!}
|
||||
@@ -111,10 +111,24 @@
|
||||
</div>
|
||||
|
||||
<!-- Job Description -->
|
||||
<div class="prose prose-emerald max-w-none mb-8">
|
||||
<div class="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 class="prose prose-emerald prose-lg max-w-none text-gray-700">
|
||||
{!! Str::of($job->description)
|
||||
->pipe(function($desc) {
|
||||
// Allow safe HTML tags
|
||||
return strip_tags($desc, '<p><br><ul><ol><li><strong><b><em><i><h1><h2><h3><h4><h5><h6><a><div><span>');
|
||||
})
|
||||
->pipe(function($desc) {
|
||||
// Convert remaining newlines to paragraphs if no HTML structure
|
||||
if (!str_contains($desc, '<p>') && !str_contains($desc, '<div>')) {
|
||||
$paragraphs = array_filter(explode("\n\n", $desc));
|
||||
return '<p>' . implode('</p><p>', array_map(fn($p) => nl2br(trim($p)), $paragraphs)) . '</p>';
|
||||
}
|
||||
return $desc;
|
||||
})
|
||||
->toString()
|
||||
!!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,12 +173,12 @@
|
||||
<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)) }}"
|
||||
<a href="https://twitter.com/intent/tweet?text={{ urlencode($job->title . ' at ' . $job->company) }}&url={{ urlencode(route('jobs.show', $job)) }}"
|
||||
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)) }}"
|
||||
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ urlencode(route('jobs.show', $job)) }}"
|
||||
target="_blank"
|
||||
class="flex-1 px-3 py-2 bg-blue-700 hover:bg-blue-800 text-white text-center rounded text-sm">
|
||||
LinkedIn
|
||||
@@ -173,10 +187,90 @@
|
||||
</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">
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6" x-data="{ showReportModal: false }">
|
||||
<button @click="showReportModal = true" class="text-sm text-red-600 hover:text-red-700">
|
||||
Report this job
|
||||
</button>
|
||||
|
||||
<!-- Report Modal -->
|
||||
<div x-show="showReportModal"
|
||||
x-cloak
|
||||
@click.away="showReportModal = false"
|
||||
@keydown.escape.window="showReportModal = false"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<!-- Overlay -->
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50"></div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Report this job</h3>
|
||||
<button @click="showReportModal = false" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Please select a reason for reporting this job posting:
|
||||
</p>
|
||||
|
||||
<form action="#" method="POST" class="space-y-4">
|
||||
@csrf
|
||||
<input type="hidden" name="job_id" value="{{ $job->id }}">
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="reason" value="expired" class="mr-2" required>
|
||||
<span class="text-sm">Job posting has expired</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="reason" value="scam" class="mr-2" required>
|
||||
<span class="text-sm">Suspected scam or fraud</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="reason" value="duplicate" class="mr-2" required>
|
||||
<span class="text-sm">Duplicate posting</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="reason" value="inappropriate" class="mr-2" required>
|
||||
<span class="text-sm">Inappropriate content</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="reason" value="other" class="mr-2" required>
|
||||
<span class="text-sm">Other</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="details" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
Additional details (optional)
|
||||
</label>
|
||||
<textarea id="details" name="details" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
|
||||
placeholder="Provide more information..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button type="button"
|
||||
@click="showReportModal = false"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg">
|
||||
Submit Report
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-gray-500 text-center">
|
||||
Thank you for helping us maintain quality job listings.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
|
||||
<!-- Alpine.js x-cloak CSS -->
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
|
||||
@stack('head')
|
||||
</head>
|
||||
<body class="h-full antialiased bg-gray-50">
|
||||
@@ -145,10 +150,10 @@
|
||||
<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>
|
||||
<li><a href="{{ route('about') }}" class="text-gray-600 hover:text-emerald-600 text-sm">About Us</a></li>
|
||||
<li><a href="{{ route('contact') }}" class="text-gray-600 hover:text-emerald-600 text-sm">Contact</a></li>
|
||||
<li><a href="{{ route('privacy') }}" class="text-gray-600 hover:text-emerald-600 text-sm">Privacy Policy</a></li>
|
||||
<li><a href="{{ route('terms') }}" class="text-gray-600 hover:text-emerald-600 text-sm">Terms of Service</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'About Us - JobifyGarden')
|
||||
@section('description', 'Learn about JobifyGarden, your trusted job aggregator platform connecting job seekers with opportunities from multiple sources.')
|
||||
@section('canonical', route('about'))
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-gray-900 mb-4">About JobifyGarden</h1>
|
||||
<p class="text-xl text-gray-600">Your trusted job aggregator platform</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8 space-y-8">
|
||||
<!-- Mission Section -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Our Mission</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
JobifyGarden was created with a simple mission: to make job searching easier and more efficient.
|
||||
We aggregate job listings from multiple trusted sources, bringing thousands of opportunities to
|
||||
one convenient platform. Whether you're looking for remote work, full-time positions, or freelance
|
||||
opportunities, we're here to help you find your next career move.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- What We Do Section -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">What We Do</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
JobifyGarden aggregates job listings from various free job APIs and job boards, providing you with:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4">
|
||||
<li>Access to thousands of job listings from multiple sources</li>
|
||||
<li>Advanced search and filtering capabilities</li>
|
||||
<li>Real-time job updates from trusted platforms</li>
|
||||
<li>Remote and on-site job opportunities across various industries</li>
|
||||
<li>Easy-to-use interface for efficient job hunting</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Why Choose Us Section -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Why Choose JobifyGarden</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="bg-emerald-50 p-6 rounded-lg">
|
||||
<h3 class="font-semibold text-gray-900 mb-2">Comprehensive Coverage</h3>
|
||||
<p class="text-gray-700 text-sm">We aggregate jobs from multiple sources, so you don't have to visit multiple websites.</p>
|
||||
</div>
|
||||
<div class="bg-emerald-50 p-6 rounded-lg">
|
||||
<h3 class="font-semibold text-gray-900 mb-2">Always Up-to-Date</h3>
|
||||
<p class="text-gray-700 text-sm">Our automated system regularly fetches new job listings to keep our database current.</p>
|
||||
</div>
|
||||
<div class="bg-emerald-50 p-6 rounded-lg">
|
||||
<h3 class="font-semibold text-gray-900 mb-2">Smart Search</h3>
|
||||
<p class="text-gray-700 text-sm">Advanced filtering options help you find exactly what you're looking for quickly.</p>
|
||||
</div>
|
||||
<div class="bg-emerald-50 p-6 rounded-lg">
|
||||
<h3 class="font-semibold text-gray-900 mb-2">Free to Use</h3>
|
||||
<p class="text-gray-700 text-sm">All features are completely free. No hidden fees or premium subscriptions required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact CTA -->
|
||||
<section class="bg-gray-50 p-6 rounded-lg text-center">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-3">Have Questions?</h2>
|
||||
<p class="text-gray-700 mb-4">We'd love to hear from you.</p>
|
||||
<a href="{{ route('contact') }}" class="inline-block px-6 py-3 bg-emerald-600 hover:bg-emerald-700 text-white font-semibold rounded-lg transition-colors duration-200">
|
||||
Contact Us
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,122 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Contact Us - JobifyGarden')
|
||||
@section('description', 'Get in touch with JobifyGarden. We\'re here to help with any questions or feedback you may have.')
|
||||
@section('canonical', route('contact'))
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-gray-900 mb-4">Contact Us</h1>
|
||||
<p class="text-xl text-gray-600">We'd love to hear from you</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
<!-- Contact Information -->
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">Get In Touch</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Email -->
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Email</h3>
|
||||
<p class="text-gray-600">hello@jobifygarden.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Support -->
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Support</h3>
|
||||
<p class="text-gray-600">support@jobifygarden.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Business Hours -->
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="w-6 h-6 text-emerald-600" 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>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Response Time</h3>
|
||||
<p class="text-gray-600">We typically respond within 24-48 hours</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FAQ Link -->
|
||||
<div class="mt-8 pt-8 border-t border-gray-200">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Frequently Asked Questions</h3>
|
||||
<p class="text-gray-600 text-sm mb-4">
|
||||
Before reaching out, you might find answers to common questions in our FAQ section.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Form -->
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">Send Us a Message</h2>
|
||||
|
||||
<form class="space-y-6">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">Name</label>
|
||||
<input type="text" id="name" name="name"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
|
||||
placeholder="Your name">
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
||||
<input type="email" id="email" name="email"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
|
||||
placeholder="your.email@example.com">
|
||||
</div>
|
||||
|
||||
<!-- Subject -->
|
||||
<div>
|
||||
<label for="subject" class="block text-sm font-medium text-gray-700 mb-2">Subject</label>
|
||||
<input type="text" id="subject" name="subject"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
|
||||
placeholder="How can we help?">
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<div>
|
||||
<label for="message" class="block text-sm font-medium text-gray-700 mb-2">Message</label>
|
||||
<textarea id="message" name="message" rows="5"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
|
||||
placeholder="Tell us more about your question or feedback..."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div>
|
||||
<button type="submit"
|
||||
class="w-full px-6 py-3 bg-emerald-600 hover:bg-emerald-700 text-white font-semibold rounded-lg transition-colors duration-200">
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-gray-500 text-center">
|
||||
Note: This is a demo form. For actual inquiries, please email us directly at hello@jobifygarden.com
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,120 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Privacy Policy - JobifyGarden')
|
||||
@section('description', 'Read JobifyGarden\'s privacy policy to understand how we collect, use, and protect your information.')
|
||||
@section('canonical', route('privacy'))
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-gray-900 mb-4">Privacy Policy</h1>
|
||||
<p class="text-gray-600">Last updated: {{ now()->format('F d, Y') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8 prose prose-emerald max-w-none">
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Introduction</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
JobifyGarden ("we", "our", or "us") is committed to protecting your privacy. This Privacy Policy
|
||||
explains how we collect, use, disclose, and safeguard your information when you visit our website.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Information We Collect</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-6">Usage Data</h3>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
We may collect information that your browser sends whenever you visit our website. This may include:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4 mb-4">
|
||||
<li>Your computer's Internet Protocol (IP) address</li>
|
||||
<li>Browser type and version</li>
|
||||
<li>Pages you visit on our site</li>
|
||||
<li>Time and date of your visit</li>
|
||||
<li>Time spent on pages</li>
|
||||
<li>Other diagnostic data</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-6">Cookies and Tracking Technologies</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We use cookies and similar tracking technologies to track activity on our website and store certain
|
||||
information. You can instruct your browser to refuse all cookies or to indicate when a cookie is
|
||||
being sent.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">How We Use Your Information</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
We may use the information we collect for various purposes, including:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4">
|
||||
<li>To provide and maintain our service</li>
|
||||
<li>To improve and personalize user experience</li>
|
||||
<li>To analyze how our service is used</li>
|
||||
<li>To monitor the usage of our service</li>
|
||||
<li>To detect, prevent and address technical issues</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Third-Party Services</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
JobifyGarden aggregates job listings from various third-party sources. When you click on a job
|
||||
listing to apply, you will be redirected to the original job posting on a third-party website.
|
||||
We are not responsible for the privacy practices of these third-party websites.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Data Security</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
The security of your data is important to us, but remember that no method of transmission over the
|
||||
Internet or method of electronic storage is 100% secure. While we strive to use commercially
|
||||
acceptable means to protect your information, we cannot guarantee its absolute security.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Links to Other Sites</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Our service contains links to other sites (job postings from various sources). If you click on a
|
||||
third-party link, you will be directed to that site. We strongly advise you to review the Privacy
|
||||
Policy of every site you visit. We have no control over and assume no responsibility for the content,
|
||||
privacy policies, or practices of any third-party sites or services.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Children's Privacy</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Our service is not intended for use by children under the age of 18. We do not knowingly collect
|
||||
personally identifiable information from children under 18. If you are a parent or guardian and you
|
||||
are aware that your child has provided us with personal data, please contact us.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Changes to This Privacy Policy</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the
|
||||
new Privacy Policy on this page and updating the "Last updated" date at the top of this Privacy Policy.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Contact Us</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
If you have any questions about this Privacy Policy, please contact us:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4">
|
||||
<li>By email: privacy@jobifygarden.com</li>
|
||||
<li>By visiting our <a href="{{ route('contact') }}" class="text-emerald-600 hover:text-emerald-700">contact page</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,154 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Terms of Service - JobifyGarden')
|
||||
@section('description', 'Read JobifyGarden\'s terms of service to understand the rules and regulations for using our platform.')
|
||||
@section('canonical', route('terms'))
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-gray-900 mb-4">Terms of Service</h1>
|
||||
<p class="text-gray-600">Last updated: {{ now()->format('F d, Y') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8 prose prose-emerald max-w-none">
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Agreement to Terms</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
By accessing or using JobifyGarden, you agree to be bound by these Terms of Service and all
|
||||
applicable laws and regulations. If you do not agree with any of these terms, you are prohibited
|
||||
from using or accessing this site.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Use License</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
Permission is granted to temporarily access the materials (information or software) on JobifyGarden
|
||||
for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer
|
||||
of title, and under this license you may not:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4">
|
||||
<li>Modify or copy the materials</li>
|
||||
<li>Use the materials for any commercial purpose or for any public display</li>
|
||||
<li>Attempt to reverse engineer any software contained on JobifyGarden</li>
|
||||
<li>Remove any copyright or other proprietary notations from the materials</li>
|
||||
<li>Transfer the materials to another person or "mirror" the materials on any other server</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Disclaimer</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
JobifyGarden is a job aggregation platform that collects and displays job listings from various
|
||||
third-party sources. We make no warranties or representations about:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4 mb-4">
|
||||
<li>The accuracy, completeness, or reliability of job listings</li>
|
||||
<li>The availability of job positions</li>
|
||||
<li>The legitimacy of employers or job postings</li>
|
||||
<li>The outcome of any job application</li>
|
||||
</ul>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
The materials on JobifyGarden are provided on an 'as is' basis. JobifyGarden makes no warranties,
|
||||
expressed or implied, and hereby disclaims and negates all other warranties including, without
|
||||
limitation, implied warranties or conditions of merchantability, fitness for a particular purpose,
|
||||
or non-infringement of intellectual property or other violation of rights.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Third-Party Content</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
All job listings displayed on JobifyGarden are sourced from third-party job boards and APIs.
|
||||
JobifyGarden does not create, verify, or endorse these job listings. When you apply for a job,
|
||||
you will be redirected to the original posting on a third-party website. We are not responsible
|
||||
for the content, accuracy, or practices of these third-party sites.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">User Conduct</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
You agree not to use JobifyGarden to:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4">
|
||||
<li>Violate any applicable laws or regulations</li>
|
||||
<li>Engage in any automated data collection (scraping, bots, etc.) without permission</li>
|
||||
<li>Transmit any harmful code, viruses, or malicious software</li>
|
||||
<li>Impersonate another person or entity</li>
|
||||
<li>Interfere with or disrupt the service or servers</li>
|
||||
<li>Attempt to gain unauthorized access to any part of the service</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Intellectual Property</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
The JobifyGarden name, logo, and all related names, logos, product and service names, designs, and
|
||||
slogans are trademarks of JobifyGarden or its affiliates. You must not use such marks without our
|
||||
prior written permission. All other names, logos, product and service names, designs, and slogans
|
||||
on this website are the trademarks of their respective owners.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Limitation of Liability</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
In no event shall JobifyGarden or its suppliers be liable for any damages (including, without
|
||||
limitation, damages for loss of data or profit, or due to business interruption) arising out of
|
||||
the use or inability to use JobifyGarden, even if JobifyGarden or a JobifyGarden authorized
|
||||
representative has been notified orally or in writing of the possibility of such damage.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Indemnification</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
You agree to indemnify, defend, and hold harmless JobifyGarden and its officers, directors, employees,
|
||||
and agents from and against any claims, liabilities, damages, losses, and expenses, including reasonable
|
||||
attorneys' fees, arising out of or in any way connected with your access to or use of the service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Modifications to Service</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
JobifyGarden reserves the right to modify or discontinue, temporarily or permanently, the service
|
||||
(or any part thereof) with or without notice. You agree that JobifyGarden shall not be liable to
|
||||
you or to any third party for any modification, suspension, or discontinuance of the service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Governing Law</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
These terms and conditions are governed by and construed in accordance with the laws of the
|
||||
United States, and you irrevocably submit to the exclusive jurisdiction of the courts in that location.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Changes to Terms</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a
|
||||
revision is material, we will provide at least 30 days' notice prior to any new terms taking effect.
|
||||
What constitutes a material change will be determined at our sole discretion.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Contact Information</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
If you have any questions about these Terms, please contact us:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 ml-4">
|
||||
<li>By email: legal@jobifygarden.com</li>
|
||||
<li>By visiting our <a href="{{ route('contact') }}" class="text-emerald-600 hover:text-emerald-700">contact page</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
+8
-1
@@ -3,18 +3,25 @@
|
||||
use App\Http\Controllers\Admin\DashboardController;
|
||||
use App\Http\Controllers\Web\HomeController;
|
||||
use App\Http\Controllers\Web\JobController;
|
||||
use App\Http\Controllers\Web\PageController;
|
||||
use App\Http\Controllers\Web\SitemapController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// Homepage
|
||||
Route::get('/', [HomeController::class, 'index'])->name('home');
|
||||
|
||||
// Static pages
|
||||
Route::get('/about', [PageController::class, 'about'])->name('about');
|
||||
Route::get('/contact', [PageController::class, 'contact'])->name('contact');
|
||||
Route::get('/privacy', [PageController::class, 'privacy'])->name('privacy');
|
||||
Route::get('/terms', [PageController::class, 'terms'])->name('terms');
|
||||
|
||||
// Job routes
|
||||
Route::prefix('jobs')->group(function () {
|
||||
Route::get('/', [JobController::class, 'index'])->name('jobs.index');
|
||||
Route::get('/remote', [JobController::class, 'remote'])->name('jobs.remote');
|
||||
Route::get('/category/{slug}', [JobController::class, 'category'])->name('jobs.category');
|
||||
Route::get('/{id}', [JobController::class, 'show'])->name('jobs.show');
|
||||
Route::get('/{job:slug}', [JobController::class, 'show'])->name('jobs.show');
|
||||
});
|
||||
|
||||
// Sitemap routes
|
||||
|
||||
Reference in New Issue
Block a user