Update src/app/search/page.tsx
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleCentered from "@/components/navbar/NavbarStyleCentered/NavbarStyleCentered";
|
||||
import FeatureCardEight from "@/components/sections/feature/FeatureCardEight";
|
||||
import ContactCenter from "@/components/sections/contact/ContactCenter";
|
||||
import FooterBase from "@/components/sections/footer/FooterBase";
|
||||
import { Sparkles, Mail } from "lucide-react";
|
||||
import { Search, ChevronDown, MapPin, DollarSign, Briefcase } from "lucide-react";
|
||||
|
||||
const navItems = [
|
||||
{ name: "Search Jobs", id: "search" },
|
||||
{ name: "Post a Job", id: "post-job" },
|
||||
{ name: "Admin", id: "admin-login" },
|
||||
{ name: "Search Jobs", id: "/search" },
|
||||
{ name: "Post a Job", id: "/post-job" },
|
||||
{ name: "Admin", id: "/admin-login" },
|
||||
{ name: "Browse", id: "browse" },
|
||||
{ name: "Contact", id: "contact" },
|
||||
];
|
||||
@@ -42,7 +41,115 @@ const footerColumns = [
|
||||
},
|
||||
];
|
||||
|
||||
const mockJobs = [
|
||||
{
|
||||
id: "1", title: "Senior Software Engineer", company: "TechCorp Amsterdam", location: "Amsterdam", province: "North Holland", salary: "€80,000 - €120,000", salaryMin: 80000,
|
||||
salaryMax: 120000,
|
||||
jobType: "Full-time", category: "Technology", description:
|
||||
"Join our innovative team as a Senior Software Engineer. We\'re looking for experienced developers with expertise in React, Node.js, and cloud technologies.", logo: "http://img.b2bpic.net/free-vector/tech-company-logo_23-2148947635.jpg"},
|
||||
{
|
||||
id: "2", title: "Marketing Manager", company: "Creative Solutions Rotterdam", location: "Rotterdam", province: "South Holland", salary: "€60,000 - €85,000", salaryMin: 60000,
|
||||
salaryMax: 85000,
|
||||
jobType: "Full-time", category: "Marketing", description:
|
||||
"Lead our marketing initiatives and drive brand growth. We seek a strategic marketer with experience in digital marketing and campaign management.", logo: "http://img.b2bpic.net/free-vector/marketing-logo_23-2148947635.jpg"},
|
||||
{
|
||||
id: "3", title: "Data Scientist", company: "Analytics Pro Utrecht", location: "Utrecht", province: "Utrecht", salary: "€70,000 - €110,000", salaryMin: 70000,
|
||||
salaryMax: 110000,
|
||||
jobType: "Full-time", category: "Data Science", description:
|
||||
"Develop advanced analytics solutions for our global clients. Expertise in Python, machine learning, and big data technologies required.", logo: "http://img.b2bpic.net/free-vector/analytics-logo_23-2148947635.jpg"},
|
||||
{
|
||||
id: "4", title: "UX/UI Designer", company: "Design Studios The Hague", location: "The Hague", province: "South Holland", salary: "€55,000 - €75,000", salaryMin: 55000,
|
||||
salaryMax: 75000,
|
||||
jobType: "Full-time", category: "Design", description:
|
||||
"Create beautiful and intuitive user experiences for our web and mobile applications. Portfolio required.", logo: "http://img.b2bpic.net/free-vector/design-logo_23-2148947635.jpg"},
|
||||
{
|
||||
id: "5", title: "Sales Executive", company: "Enterprise Solutions Groningen", location: "Groningen", province: "Groningen", salary: "€45,000 - €70,000", salaryMin: 45000,
|
||||
salaryMax: 70000,
|
||||
jobType: "Full-time", category: "Sales", description:
|
||||
"Grow our sales pipeline and build strong client relationships. Commission and bonus structure available.", logo: "http://img.b2bpic.net/free-vector/sales-logo_23-2148947635.jpg"},
|
||||
{
|
||||
id: "6", title: "HR Specialist", company: "People First Leiden", location: "Leiden", province: "South Holland", salary: "€50,000 - €65,000", salaryMin: 50000,
|
||||
salaryMax: 65000,
|
||||
jobType: "Part-time", category: "Human Resources", description:
|
||||
"Support our HR team in recruitment, onboarding, and employee development initiatives.", logo: "http://img.b2bpic.net/free-vector/hr-logo_23-2148947635.jpg"},
|
||||
{
|
||||
id: "7", title: "DevOps Engineer", company: "Cloud Innovations Eindhoven", location: "Eindhoven", province: "North Brabant", salary: "€75,000 - €105,000", salaryMin: 75000,
|
||||
salaryMax: 105000,
|
||||
jobType: "Full-time", category: "Technology", description:
|
||||
"Manage and optimize our cloud infrastructure. Experience with Docker, Kubernetes, and CI/CD pipelines required.", logo: "http://img.b2bpic.net/free-vector/devops-logo_23-2148947635.jpg"},
|
||||
{
|
||||
id: "8", title: "Product Manager", company: "Innovation Lab Delft", location: "Delft", province: "South Holland", salary: "€70,000 - €95,000", salaryMin: 70000,
|
||||
salaryMax: 95000,
|
||||
jobType: "Full-time", category: "Product", description:
|
||||
"Lead product strategy and roadmap development for our SaaS platform. Experience with agile methodologies required.", logo: "http://img.b2bpic.net/free-vector/product-logo_23-2148947635.jpg"},
|
||||
];
|
||||
|
||||
const provinces = [
|
||||
"All Provinces", "North Holland", "South Holland", "Utrecht", "Groningen", "North Brabant", "Limburg", "Friesland", "Drenthe", "Flevoland", "Overijssel", "Gelderland"];
|
||||
|
||||
const categories = [
|
||||
"All Categories", "Technology", "Marketing", "Sales", "Design", "Data Science", "Human Resources", "Product"];
|
||||
|
||||
const jobTypes = ["All Types", "Full-time", "Part-time", "Contract", "Freelance"];
|
||||
|
||||
export default function SearchPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedProvince, setSelectedProvince] = useState("All Provinces");
|
||||
const [selectedCategory, setSelectedCategory] = useState("All Categories");
|
||||
const [selectedJobType, setSelectedJobType] = useState("All Types");
|
||||
const [sortBy, setSortBy] = useState("relevant");
|
||||
const [minSalary, setMinSalary] = useState(0);
|
||||
const [maxSalary, setMaxSalary] = useState(150000);
|
||||
|
||||
const filteredJobs = useMemo(() => {
|
||||
let filtered = mockJobs.filter((job) => {
|
||||
const matchesSearch =
|
||||
job.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
job.company.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
job.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
const matchesProvince =
|
||||
selectedProvince === "All Provinces" ||
|
||||
job.province === selectedProvince;
|
||||
|
||||
const matchesCategory =
|
||||
selectedCategory === "All Categories" ||
|
||||
job.category === selectedCategory;
|
||||
|
||||
const matchesJobType =
|
||||
selectedJobType === "All Types" || job.jobType === selectedJobType;
|
||||
|
||||
const matchesSalary =
|
||||
job.salaryMin >= minSalary && job.salaryMax <= maxSalary;
|
||||
|
||||
return (
|
||||
matchesSearch &&
|
||||
matchesProvince &&
|
||||
matchesCategory &&
|
||||
matchesJobType &&
|
||||
matchesSalary
|
||||
);
|
||||
});
|
||||
|
||||
if (sortBy === "salary-high") {
|
||||
filtered.sort((a, b) => b.salaryMax - a.salaryMax);
|
||||
} else if (sortBy === "salary-low") {
|
||||
filtered.sort((a, b) => a.salaryMin - b.salaryMin);
|
||||
} else if (sortBy === "recent") {
|
||||
filtered.reverse();
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [
|
||||
searchQuery,
|
||||
selectedProvince,
|
||||
selectedCategory,
|
||||
selectedJobType,
|
||||
minSalary,
|
||||
maxSalary,
|
||||
sortBy,
|
||||
]);
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="text-stagger"
|
||||
@@ -65,54 +172,188 @@ export default function SearchPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="features" data-section="features">
|
||||
<FeatureCardEight
|
||||
title="Search & Browse Jobs Across the Netherlands"
|
||||
description="Explore our comprehensive job search experience with advanced filters, diverse listings, and tailored opportunities for every career stage."
|
||||
tag="Advanced Search"
|
||||
tagIcon={Sparkles}
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
features={[
|
||||
{
|
||||
id: 1,
|
||||
title: "Filter by Province", description:
|
||||
"Search jobs from all 12 Dutch provinces including Amsterdam, Rotterdam, Utrecht, and beyond. Find opportunities near you or explore remote positions nationwide.", imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/homepage-concept-with-search-bar_23-2150040187.jpg?_wi=4", imageAlt: "Province filter interface"},
|
||||
{
|
||||
id: 2,
|
||||
title: "Refine by Category", description:
|
||||
"Browse across multiple industries: Technology, Healthcare, Finance, Engineering, Marketing, Sales, and more. Find roles that match your expertise and interests.", imageSrc:
|
||||
"http://img.b2bpic.net/free-vector/professional-bookkeeping-postcard-template_23-2149341358.jpg?_wi=2", imageAlt: "Job category options"},
|
||||
{
|
||||
id: 3,
|
||||
title: "Salary & Experience Level", description:
|
||||
"Filter by salary range, job type (full-time, part-time, contract), and experience level (entry-level, mid-career, senior) to find the perfect match for your career goals.", imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/corporate-workers-brainstorming-together_23-2148804568.jpg?_wi=3", imageAlt: "Salary and level filters"},
|
||||
]}
|
||||
buttons={[
|
||||
{
|
||||
text: "Start Your Search", href: "/search"},
|
||||
]}
|
||||
buttonAnimation="slide-up"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-screen bg-gradient-to-b from-background to-background-accent pt-20 pb-20">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
{/* Search Header */}
|
||||
<div className="mb-12">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-4">
|
||||
Find Your Dream Job
|
||||
</h1>
|
||||
<p className="text-lg text-foreground/70 mb-8">
|
||||
Discover thousands of opportunities across the Netherlands
|
||||
</p>
|
||||
|
||||
<div id="contact" data-section="contact">
|
||||
<ContactCenter
|
||||
tag="Save Your Search"
|
||||
title="Get Personalized Job Recommendations"
|
||||
description="Create an account and set up job alerts. We'll notify you about new positions matching your criteria so you never miss an opportunity."
|
||||
tagIcon={Mail}
|
||||
tagAnimation="slide-up"
|
||||
background={{
|
||||
variant: "animated-grid"}}
|
||||
useInvertedBackground={false}
|
||||
inputPlaceholder="Enter your email to get started"
|
||||
buttonText="Create Alert"
|
||||
termsText="Your preferences are private and secure. Manage your alerts anytime."
|
||||
/>
|
||||
{/* Main Search Bar */}
|
||||
<div className="relative mb-8">
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-foreground/50" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by job title, company, or keywords..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 rounded-full bg-card border border-accent/20 text-foreground placeholder-foreground/50 focus:outline-none focus:ring-2 focus:ring-primary-cta"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Filter Bar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
|
||||
{/* Province Filter */}
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedProvince}
|
||||
onChange={(e) => setSelectedProvince(e.target.value)}
|
||||
className="w-full px-4 py-3 rounded-lg bg-card border border-accent/20 text-foreground cursor-pointer appearance-none pr-10 focus:outline-none focus:ring-2 focus:ring-primary-cta"
|
||||
>
|
||||
{provinces.map((province) => (
|
||||
<option key={province} value={province}>
|
||||
{province}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<MapPin className="absolute right-3 top-1/2 transform -translate-y-1/2 text-foreground/50 pointer-events-none" />
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedCategory}
|
||||
onChange={(e) => setSelectedCategory(e.target.value)}
|
||||
className="w-full px-4 py-3 rounded-lg bg-card border border-accent/20 text-foreground cursor-pointer appearance-none pr-10 focus:outline-none focus:ring-2 focus:ring-primary-cta"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<option key={category} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Briefcase className="absolute right-3 top-1/2 transform -translate-y-1/2 text-foreground/50 pointer-events-none" />
|
||||
</div>
|
||||
|
||||
{/* Job Type Filter */}
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedJobType}
|
||||
onChange={(e) => setSelectedJobType(e.target.value)}
|
||||
className="w-full px-4 py-3 rounded-lg bg-card border border-accent/20 text-foreground cursor-pointer appearance-none pr-10 focus:outline-none focus:ring-2 focus:ring-primary-cta"
|
||||
>
|
||||
{jobTypes.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-foreground/50 pointer-events-none" />
|
||||
</div>
|
||||
|
||||
{/* Salary Range */}
|
||||
<div className="flex items-center gap-2">
|
||||
<DollarSign className="text-foreground/50" />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="150000"
|
||||
step="10000"
|
||||
value={maxSalary}
|
||||
onChange={(e) => setMaxSalary(parseInt(e.target.value))}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Sort By */}
|
||||
<div className="relative">
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
className="w-full px-4 py-3 rounded-lg bg-card border border-accent/20 text-foreground cursor-pointer appearance-none pr-10 focus:outline-none focus:ring-2 focus:ring-primary-cta"
|
||||
>
|
||||
<option value="relevant">Most Relevant</option>
|
||||
<option value="recent">Most Recent</option>
|
||||
<option value="salary-high">Highest Salary</option>
|
||||
<option value="salary-low">Lowest Salary</option>
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-foreground/50 pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results Count */}
|
||||
<p className="text-sm text-foreground/60">
|
||||
Showing {filteredJobs.length} of {mockJobs.length} jobs
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Job Listings */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{filteredJobs.length > 0 ? (
|
||||
filteredJobs.map((job) => (
|
||||
<div
|
||||
key={job.id}
|
||||
className="p-6 rounded-xl bg-card border border-accent/20 hover:border-accent/50 transition-all duration-300 hover:shadow-lg cursor-pointer"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-start gap-4 flex-1">
|
||||
<div className="w-16 h-16 rounded-lg bg-gradient-to-br from-primary-cta to-secondary-cta flex items-center justify-center text-white font-bold text-xl flex-shrink-0">
|
||||
{job.company.charAt(0)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-foreground mb-1">
|
||||
{job.title}
|
||||
</h3>
|
||||
<p className="text-sm text-foreground/70 mb-2">
|
||||
{job.company}
|
||||
</p>
|
||||
<p className="text-sm text-foreground/60">
|
||||
{job.location}, {job.province}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-lg font-bold text-primary-cta mb-2">
|
||||
{job.salary}
|
||||
</p>
|
||||
<span className="inline-block px-3 py-1 rounded-full text-xs font-semibold bg-primary-cta/10 text-primary-cta">
|
||||
{job.jobType}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-foreground/70 mb-4 line-clamp-2">
|
||||
{job.description}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
<span className="inline-block px-3 py-1 rounded-lg text-xs bg-background text-foreground/70">
|
||||
{job.category}
|
||||
</span>
|
||||
</div>
|
||||
<button className="px-4 py-2 rounded-full bg-primary-cta text-white font-semibold hover:opacity-90 transition-opacity">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-lg text-foreground/60 mb-4">
|
||||
No jobs found matching your criteria.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchQuery("");
|
||||
setSelectedProvince("All Provinces");
|
||||
setSelectedCategory("All Categories");
|
||||
setSelectedJobType("All Types");
|
||||
setMinSalary(0);
|
||||
setMaxSalary(150000);
|
||||
}}
|
||||
className="px-6 py-2 rounded-full bg-primary-cta text-white font-semibold hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Clear Filters
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
@@ -124,4 +365,4 @@ export default function SearchPage() {
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user