306 lines
17 KiB
PHP
306 lines
17 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', $job->title . ' at ' . $job->company . ' - JobifyGarden')
|
|
@section('description', Str::limit(strip_tags($job->description), 155))
|
|
@section('canonical', route('jobs.show', $job))
|
|
|
|
@push('structured_data')
|
|
<script type="application/ld+json">
|
|
{!! json_encode(app(\App\Services\SeoService::class)->generateJobStructuredData($job), JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) !!}
|
|
</script>
|
|
|
|
<script type="application/ld+json">
|
|
@php
|
|
$breadcrumbsArray = [
|
|
['name' => 'Home', 'url' => route('home')],
|
|
['name' => 'Jobs', 'url' => route('jobs.index')],
|
|
];
|
|
|
|
if ($job->category) {
|
|
$breadcrumbsArray[] = ['name' => $job->category->name, 'url' => route('jobs.category', $job->category->slug)];
|
|
}
|
|
|
|
$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) !!}
|
|
</script>
|
|
@endpush
|
|
|
|
@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="mb-8">
|
|
<h3 class="text-xl font-semibold text-gray-900 mb-4">Job Description</h3>
|
|
<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>
|
|
|
|
<!-- 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
|
|
|
|
<!-- Tags -->
|
|
@if($job->tags->count() > 0)
|
|
<div class="mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-900 mb-2">Tags</h3>
|
|
<div class="flex flex-wrap gap-2">
|
|
@foreach($job->tags as $tag)
|
|
<span class="inline-flex items-center px-3 py-1 bg-emerald-50 text-emerald-700 rounded-full text-sm border border-emerald-200">
|
|
{{ $tag->name }}
|
|
</span>
|
|
@endforeach
|
|
</div>
|
|
</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)) }}"
|
|
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)) }}"
|
|
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" 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>
|
|
</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
|