Merge version_2 into main
Merge version_2 into main
This commit was merged in pull request #4.
This commit is contained in:
246
src/app/admin/page.tsx
Normal file
246
src/app/admin/page.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleCentered from "@/components/navbar/NavbarStyleCentered/NavbarStyleCentered";
|
||||
import MetricCardTen from "@/components/sections/metrics/MetricCardTen";
|
||||
import FeatureBento from "@/components/sections/feature/FeatureBento";
|
||||
import FooterBase from "@/components/sections/footer/FooterBase";
|
||||
import {
|
||||
Users,
|
||||
Briefcase,
|
||||
BarChart3,
|
||||
Shield,
|
||||
Activity,
|
||||
TrendingUp,
|
||||
AlertCircle,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
|
||||
const navItems = [
|
||||
{ name: "Dashboard", id: "/admin" },
|
||||
{ name: "Jobs", id: "/admin#jobs" },
|
||||
{ name: "Users", id: "/admin#users" },
|
||||
{ name: "Moderation", id: "/admin#moderation" },
|
||||
{ name: "Analytics", id: "/admin#analytics" },
|
||||
];
|
||||
|
||||
const footerColumns = [
|
||||
{
|
||||
title: "Admin", items: [
|
||||
{ label: "Dashboard", href: "/admin" },
|
||||
{ label: "Settings", href: "/admin#settings" },
|
||||
{ label: "Reports", href: "/admin#reports" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Quick Links", items: [
|
||||
{ label: "Help", href: "#" },
|
||||
{ label: "Support", href: "#" },
|
||||
{ label: "Documentation", href: "#" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function AdminPage() {
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="text-stagger"
|
||||
defaultTextAnimation="reveal-blur"
|
||||
borderRadius="pill"
|
||||
contentWidth="smallMedium"
|
||||
sizing="mediumLargeSizeLargeTitles"
|
||||
background="circleGradient"
|
||||
cardStyle="gradient-radial"
|
||||
primaryButtonStyle="double-inset"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="bold"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleCentered
|
||||
brandName="Jobee Admin"
|
||||
navItems={navItems}
|
||||
button={{
|
||||
text: "Logout", onClick: () => console.log("logout"),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="jobs" data-section="jobs" className="pt-20">
|
||||
<MetricCardTen
|
||||
title="Job Management"
|
||||
description="Monitor and manage all job postings across the platform"
|
||||
tag="Active Jobs"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
animationType="slide-up"
|
||||
metrics={[
|
||||
{
|
||||
id: "1", title: "Senior Software Engineer, Backend", subtitle: "Amsterdam, Netherlands · Full-time · Remote eligible", category: "Engineering", value: "Posted 2 days ago", buttons: [
|
||||
{ text: "View", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "2", title: "Product Manager, Enterprise", subtitle: "Rotterdam, Netherlands · Full-time", category: "Product", value: "Posted 5 days ago", buttons: [
|
||||
{ text: "View", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "3", title: "UX Designer, B2B", subtitle: "Utrecht, Netherlands · Full-time", category: "Design", value: "Posted 1 week ago", buttons: [
|
||||
{ text: "View", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "4", title: "Data Scientist, ML Platform", subtitle: "Remote · Full-time", category: "Data", value: "Posted 10 days ago", buttons: [
|
||||
{ text: "View", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "5", title: "DevOps Engineer, Infrastructure", subtitle: "Amsterdam, Netherlands · Full-time · Remote", category: "Infrastructure", value: "Posted 3 weeks ago", buttons: [
|
||||
{ text: "View", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "6", title: "Marketing Manager, Growth", subtitle: "The Hague, Netherlands · Full-time", category: "Marketing", value: "Posted 1 month ago", buttons: [
|
||||
{ text: "View", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="users" data-section="users">
|
||||
<MetricCardTen
|
||||
title="User Management"
|
||||
description="Overview of registered users, job seekers, and employers"
|
||||
tag="Active Users"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={true}
|
||||
animationType="slide-up"
|
||||
metrics={[
|
||||
{
|
||||
id: "1", title: "Sarah van der Berg", subtitle: "Amsterdam, Netherlands · Software Developer", category: "Job Seeker", value: "Active", buttons: [
|
||||
{ text: "View Profile", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "2", title: "Acme Corporation", subtitle: "Rotterdam, Netherlands · Tech Company", category: "Employer", value: "Verified", buttons: [
|
||||
{ text: "View", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "3", title: "Jan Pieterzoon", subtitle: "Utrecht, Netherlands · HR Manager", category: "Employer", value: "Active", buttons: [
|
||||
{ text: "View Profile", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "4", title: "Emma Dijkstra", subtitle: "The Hague, Netherlands · Marketing Specialist", category: "Job Seeker", value: "Active", buttons: [
|
||||
{ text: "View Profile", href: "#" },
|
||||
{ text: "Manage", href: "#" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="analytics" data-section="analytics">
|
||||
<FeatureBento
|
||||
title="Platform Analytics & Insights"
|
||||
description="Monitor key metrics and platform performance across all regions"
|
||||
tag="Real-time Data"
|
||||
tagIcon={Activity}
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
animationType="slide-up"
|
||||
carouselMode="buttons"
|
||||
features={[
|
||||
{
|
||||
id: 1,
|
||||
title: "Job Applications Trend", description: "Track weekly application volume and trends", bentoComponent: "line-chart"},
|
||||
{
|
||||
id: 2,
|
||||
title: "Revenue by Province", description: "Revenue distribution across Dutch provinces", bentoComponent: "animated-bar-chart"},
|
||||
{
|
||||
id: 3,
|
||||
title: "User Growth Metrics", description: "Active users and engagement statistics", bentoComponent: "icon-info-cards", items: [
|
||||
{ icon: Users, label: "Total Users", value: "24,500+" },
|
||||
{ icon: Briefcase, label: "Job Postings", value: "3,240" },
|
||||
{ icon: TrendingUp, label: "Monthly Growth", value: "12.5%" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Geographic Distribution", description: "Job seeker and employer locations worldwide", bentoComponent: "map"},
|
||||
{
|
||||
id: 5,
|
||||
title: "Popular Job Categories", description: "Most sought-after job positions and skills", bentoComponent: "marquee", centerIcon: Briefcase,
|
||||
variant: "text", texts: [
|
||||
"Software Engineering", "Product Management", "Data Science", "UX Design", "Marketing", "Sales"],
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "System Status Overview", description: "Platform health and service monitoring", bentoComponent: "3d-stack-cards", items: [
|
||||
{
|
||||
icon: Activity,
|
||||
title: "API Health", subtitle: "99.9% Uptime", detail: "All systems operational"},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: "Database", subtitle: "256 GB", detail: "45% capacity used"},
|
||||
{
|
||||
icon: Shield,
|
||||
title: "Security", subtitle: "Secure", detail: "No active threats"},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="moderation" data-section="moderation">
|
||||
<MetricCardTen
|
||||
title="Content Moderation"
|
||||
description="Review and manage flagged content, reports, and user complaints"
|
||||
tag="Flagged Content"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={true}
|
||||
animationType="slide-up"
|
||||
metrics={[
|
||||
{
|
||||
id: "1", title: "Job Post: Suspicious Salary Offer", subtitle: "Posted by Employer XYZ · Flag reason: Unusually high salary", category: "Review", value: "Pending", buttons: [
|
||||
{ text: "Review", href: "#" },
|
||||
{ text: "Action", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "2", title: "User Report: Inappropriate Profile", subtitle: "Report from user about profile content violation", category: "Report", value: "2 days ago", buttons: [
|
||||
{ text: "Investigate", href: "#" },
|
||||
{ text: "Resolve", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "3", title: "Automated Alert: Spam Job Posting", subtitle: "System detected duplicate job posts from same employer", category: "Alert", value: "Flagged", buttons: [
|
||||
{ text: "Review", href: "#" },
|
||||
{ text: "Remove", href: "#" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
logoText="Jobee Admin"
|
||||
copyrightText="© 2025 Jobee Admin Portal"
|
||||
columns={footerColumns}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
299
src/app/applications/page.tsx
Normal file
299
src/app/applications/page.tsx
Normal file
@@ -0,0 +1,299 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleCentered from "@/components/navbar/NavbarStyleCentered/NavbarStyleCentered";
|
||||
import FeatureCardEight from "@/components/sections/feature/FeatureCardEight";
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import ContactCenter from "@/components/sections/contact/ContactCenter";
|
||||
import FooterBase from "@/components/sections/footer/FooterBase";
|
||||
import { CheckCircle, Clock, AlertCircle, Mail } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const navItems = [
|
||||
{ name: "Search Jobs", id: "search" },
|
||||
{ name: "Post a Job", id: "post-job" },
|
||||
{ name: "Admin", id: "admin-login" },
|
||||
{ name: "Browse", id: "browse" },
|
||||
{ name: "Applications", id: "/applications" },
|
||||
{ name: "Contact", id: "contact" },
|
||||
];
|
||||
|
||||
const footerColumns = [
|
||||
{
|
||||
title: "Product", items: [
|
||||
{ label: "Search Jobs", href: "/search" },
|
||||
{ label: "Post a Job", href: "/post-job" },
|
||||
{ label: "Browse by Province", href: "#provinces" },
|
||||
{ label: "For Employers", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company", items: [
|
||||
{ label: "About Jobee", href: "#about" },
|
||||
{ label: "Careers", href: "#" },
|
||||
{ label: "Contact Us", href: "#contact" },
|
||||
{ label: "Blog", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Resources", items: [
|
||||
{ label: "Privacy Policy", href: "#" },
|
||||
{ label: "Terms of Service", href: "#" },
|
||||
{ label: "FAQ", href: "#" },
|
||||
{ label: "Support", href: "#" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface Application {
|
||||
id: string;
|
||||
jobTitle: string;
|
||||
company: string;
|
||||
status: "pending" | "accepted" | "rejected" | "interviewing";
|
||||
appliedDate: string;
|
||||
lastUpdated: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function ApplicationsPage() {
|
||||
const [applications] = useState<Application[]>([
|
||||
{
|
||||
id: "1", jobTitle: "Senior Frontend Developer", company: "Tech Innovations Amsterdam", status: "interviewing", appliedDate: "2025-01-10", lastUpdated: "2025-01-15", description:
|
||||
"You've been selected for the interview round. Check your email for scheduling details and prepare for a technical assessment."},
|
||||
{
|
||||
id: "2", jobTitle: "UX/UI Designer", company: "Creative Studio Rotterdam", status: "accepted", appliedDate: "2025-01-08", lastUpdated: "2025-01-16", description:
|
||||
"Congratulations! Your application has been accepted. Please review the job offer details and respond within 5 business days."},
|
||||
{
|
||||
id: "3", jobTitle: "Data Analyst", company: "Analytics Corp Utrecht", status: "pending", appliedDate: "2025-01-12", lastUpdated: "2025-01-12", description:
|
||||
"Your application is being reviewed by the hiring team. You'll receive an update within 7-10 business days."},
|
||||
{
|
||||
id: "4", jobTitle: "Marketing Manager", company: "Brand Solutions Hague", status: "rejected", appliedDate: "2025-01-05", lastUpdated: "2025-01-14", description:
|
||||
"Thank you for your application. We've decided to move forward with other candidates but encourage you to apply for future opportunities."},
|
||||
{
|
||||
id: "5", jobTitle: "Full Stack Developer", company: "Web Tech Solutions", status: "interviewing", appliedDate: "2025-01-11", lastUpdated: "2025-01-16", description:
|
||||
"First round interview scheduled for January 20, 2025 at 14:00 CET. Confirmation link sent to your email."},
|
||||
{
|
||||
id: "6", jobTitle: "Content Writer", company: "Digital Media Groningen", status: "pending", appliedDate: "2025-01-13", lastUpdated: "2025-01-13", description:
|
||||
"Your portfolio and writing samples are being evaluated. We'll contact you soon with next steps."},
|
||||
]);
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "accepted":
|
||||
return CheckCircle;
|
||||
case "interviewing":
|
||||
return Clock;
|
||||
case "rejected":
|
||||
return AlertCircle;
|
||||
default:
|
||||
return Clock;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string): string => {
|
||||
switch (status) {
|
||||
case "accepted":
|
||||
return "success";
|
||||
case "interviewing":
|
||||
return "info";
|
||||
case "rejected":
|
||||
return "error";
|
||||
default:
|
||||
return "warning";
|
||||
}
|
||||
};
|
||||
|
||||
const applicationCards = applications.map((app) => (
|
||||
<div
|
||||
key={app.id}
|
||||
className="p-6 rounded-lg border border-border bg-card hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-foreground mb-1">
|
||||
{app.jobTitle}
|
||||
</h3>
|
||||
<p className="text-sm text-foreground/70 mb-3">{app.company}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{getStatusIcon(app.status) &&
|
||||
(() => {
|
||||
const Icon = getStatusIcon(app.status);
|
||||
return (
|
||||
<Icon
|
||||
className="w-5 h-5"
|
||||
style={{
|
||||
color:
|
||||
app.status === "accepted"
|
||||
? "#10b981"
|
||||
: app.status === "rejected"
|
||||
? "#ef4444"
|
||||
: app.status === "interviewing"
|
||||
? "#3b82f6"
|
||||
: "#f59e0b"}}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium capitalize ${
|
||||
app.status === "accepted"
|
||||
? "bg-green-100 text-green-800"
|
||||
: app.status === "rejected"
|
||||
? "bg-red-100 text-red-800"
|
||||
: app.status === "interviewing"
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: "bg-amber-100 text-amber-800"
|
||||
}`}
|
||||
>
|
||||
{app.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-foreground/60 mb-4">{app.description}</p>
|
||||
<div className="flex justify-between text-xs text-foreground/50 pt-4 border-t border-border/30">
|
||||
<span>Applied: {new Date(app.appliedDate).toLocaleDateString()}</span>
|
||||
<span>Updated: {new Date(app.lastUpdated).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="text-stagger"
|
||||
defaultTextAnimation="reveal-blur"
|
||||
borderRadius="pill"
|
||||
contentWidth="smallMedium"
|
||||
sizing="mediumLargeSizeLargeTitles"
|
||||
background="circleGradient"
|
||||
cardStyle="gradient-radial"
|
||||
primaryButtonStyle="double-inset"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="bold"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleCentered
|
||||
brandName="Jobee"
|
||||
navItems={navItems}
|
||||
button={{
|
||||
text: "Post a Job", href: "/post-job"}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="hero" data-section="hero" className="py-20">
|
||||
<FeatureCardEight
|
||||
title="Your Job Applications"
|
||||
description="Track the status of all your job applications, monitor interview schedules, and manage your career journey in one place."
|
||||
tag="Application Tracker"
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
features={[
|
||||
{
|
||||
id: 1,
|
||||
title: "Real-Time Status Updates", description:
|
||||
"Get instant notifications when employers review or respond to your applications. Never miss an important update.", imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/corporate-workers-brainstorming-together_23-2148804568.jpg?_wi=1", imageAlt: "Real-time notifications"},
|
||||
{
|
||||
id: 2,
|
||||
title: "Interview Management", description:
|
||||
"Keep track of interview dates, times, and preparation materials. Get reminders so you never miss an interview opportunity.", imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/personal-information-form-identity-concept_53876-137622.jpg?_wi=1", imageAlt: "Interview scheduler"},
|
||||
{
|
||||
id: 3,
|
||||
title: "Application Analytics", description:
|
||||
"View detailed insights about your application success rate, response times, and which job types get the most attention.", imageSrc:
|
||||
"http://img.b2bpic.net/free-vector/professional-recruitment-plan-diversity-general-infographic-template_23-2148947635.jpg?_wi=1", imageAlt: "Analytics dashboard"},
|
||||
{
|
||||
id: 4,
|
||||
title: "Personalized Recommendations", description:
|
||||
"Based on your applications and preferences, get tailored job suggestions that match your skills and career goals.", imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/homepage-concept-with-search-bar_23-2150040187.jpg?_wi=1", imageAlt: "Personalized recommendations"},
|
||||
]}
|
||||
buttons={[
|
||||
{
|
||||
text: "Browse More Jobs", href: "/search"},
|
||||
]}
|
||||
buttonAnimation="slide-up"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="applications" data-section="applications" className="py-20">
|
||||
<CardStack
|
||||
title="Your Active Applications"
|
||||
description="Manage and track all your job applications in one convenient dashboard. Click on any application to view details and take action."
|
||||
tag="Application Management"
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
animationType="slide-up"
|
||||
gridVariant="uniform-all-items-equal"
|
||||
mode="buttons"
|
||||
carouselThreshold={5}
|
||||
>
|
||||
{applicationCards}
|
||||
</CardStack>
|
||||
</div>
|
||||
|
||||
<div id="stats" data-section="stats" className="py-20">
|
||||
<FeatureCardEight
|
||||
title="Application Statistics"
|
||||
description="Monitor your application metrics and track your job search progress over time."
|
||||
tag="Your Progress"
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={true}
|
||||
features={[
|
||||
{
|
||||
id: 1,
|
||||
title: "Total Applications", description: `You've submitted ${applications.length} applications across various positions and companies in the Netherlands.`,
|
||||
imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/homepage-concept-with-search-bar_23-2150040187.jpg?_wi=1", imageAlt: "Total applications"},
|
||||
{
|
||||
id: 2,
|
||||
title: "Response Rate", description: `${Math.round((applications.filter((a) => a.status !== "pending").length / applications.length) * 100)}% of your applications have received responses from employers.`,
|
||||
imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/corporate-workers-brainstorming-together_23-2148804568.jpg?_wi=1", imageAlt: "Response rate"},
|
||||
{
|
||||
id: 3,
|
||||
title: "Interviews Scheduled", description: `You have ${applications.filter((a) => a.status === "interviewing").length} interviews currently in the process or scheduled. Prepare and shine!",`,
|
||||
imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/personal-information-form-identity-concept_53876-137622.jpg?_wi=1", imageAlt: "Interviews"},
|
||||
{
|
||||
id: 4,
|
||||
title: "Success Stories", description: `Congratulations! You have ${applications.filter((a) => a.status === "accepted").length} job offers. Review and respond to secure your next opportunity!`,
|
||||
imageSrc:
|
||||
"http://img.b2bpic.net/free-vector/professional-recruitment-plan-diversity-general-infographic-template_23-2148947635.jpg?_wi=1", imageAlt: "Accepted offers"},
|
||||
]}
|
||||
buttons={[
|
||||
{
|
||||
text: "Continue Searching", href: "/search"},
|
||||
]}
|
||||
buttonAnimation="slide-up"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="contact" data-section="contact">
|
||||
<ContactCenter
|
||||
tag="Need Help?"
|
||||
title="Stay in Touch with Your Job Search"
|
||||
description="Get personalized job recommendations and career advice delivered to your inbox. Subscribe to stay updated on new opportunities."
|
||||
tagIcon={Mail}
|
||||
tagAnimation="slide-up"
|
||||
background={{
|
||||
variant: "animated-grid"}}
|
||||
useInvertedBackground={false}
|
||||
inputPlaceholder="Enter your email address"
|
||||
buttonText="Subscribe"
|
||||
termsText="We'll send you job recommendations and updates relevant to your applications. Unsubscribe anytime."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
logoText="Jobee"
|
||||
copyrightText="© 2025 Jobee | Dutch Job Listing Platform"
|
||||
columns={footerColumns}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
1431
src/app/layout.tsx
1431
src/app/layout.tsx
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ import { Briefcase, Sparkles, Mail, Quote } from "lucide-react";
|
||||
const navItems = [
|
||||
{ name: "Search Jobs", id: "search" },
|
||||
{ name: "Post a Job", id: "post-job" },
|
||||
{ name: "Admin", id: "admin-login" },
|
||||
{ name: "Admin", id: "/admin" },
|
||||
{ name: "Browse", id: "browse" },
|
||||
{ name: "Contact", id: "contact" },
|
||||
];
|
||||
@@ -211,4 +211,4 @@ export default function HomePage() {
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import { useState } 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 Link from "next/link";
|
||||
import { Briefcase, Mail, MapPin, Sparkles } from "lucide-react";
|
||||
import Input from "@/components/form/Input";
|
||||
import { Upload, CheckCircle, X } from "lucide-react";
|
||||
|
||||
const navItems = [
|
||||
{ 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" },
|
||||
];
|
||||
|
||||
const footerColumns = [
|
||||
{
|
||||
title: "Product", items: [
|
||||
{ label: "Search Jobs", href: "/search" },
|
||||
{ label: "Post a Job", href: "/post-job" },
|
||||
{ label: "Browse by Province", href: "#provinces" },
|
||||
{ label: "For Employers", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company", items: [
|
||||
{ label: "About Jobee", href: "#about" },
|
||||
{ label: "Careers", href: "#" },
|
||||
{ label: "Contact Us", href: "#contact" },
|
||||
{ label: "Blog", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Resources", items: [
|
||||
{ label: "Privacy Policy", href: "#" },
|
||||
{ label: "Terms of Service", href: "#" },
|
||||
{ label: "FAQ", href: "#" },
|
||||
{ label: "Support", href: "#" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface FormData {
|
||||
jobTitle: string;
|
||||
companyName: string;
|
||||
location: string;
|
||||
jobType: string;
|
||||
salary: string;
|
||||
description: string;
|
||||
requirements: string;
|
||||
benefits: string;
|
||||
contactEmail: string;
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export default function PostJobPage() {
|
||||
const navItems = [
|
||||
{ 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" },
|
||||
];
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
jobTitle: "", companyName: "", location: "", jobType: "Full-time", salary: "", description: "", requirements: "", benefits: "", contactEmail: ""});
|
||||
|
||||
const footerColumns = [
|
||||
{
|
||||
title: "Product", items: [
|
||||
{ label: "Search Jobs", href: "/search" },
|
||||
{ label: "Post a Job", href: "/post-job" },
|
||||
{ label: "Browse by Province", href: "#provinces" },
|
||||
{ label: "For Employers", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company", items: [
|
||||
{ label: "About Jobee", href: "#about" },
|
||||
{ label: "Careers", href: "#" },
|
||||
{ label: "Contact Us", href: "#contact" },
|
||||
{ label: "Blog", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Resources", items: [
|
||||
{ label: "Privacy Policy", href: "#" },
|
||||
{ label: "Terms of Service", href: "#" },
|
||||
{ label: "FAQ", href: "#" },
|
||||
{ label: "Support", href: "#" },
|
||||
],
|
||||
},
|
||||
];
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: FormErrors = {};
|
||||
|
||||
if (!formData.jobTitle.trim()) {
|
||||
newErrors.jobTitle = "Job title is required";
|
||||
}
|
||||
if (!formData.companyName.trim()) {
|
||||
newErrors.companyName = "Company name is required";
|
||||
}
|
||||
if (!formData.location.trim()) {
|
||||
newErrors.location = "Location is required";
|
||||
}
|
||||
if (!formData.salary.trim()) {
|
||||
newErrors.salary = "Salary range is required";
|
||||
}
|
||||
if (!formData.description.trim() || formData.description.trim().length < 20) {
|
||||
newErrors.description = "Job description must be at least 20 characters";
|
||||
}
|
||||
if (!formData.requirements.trim() || formData.requirements.trim().length < 20) {
|
||||
newErrors.requirements = "Requirements must be at least 20 characters";
|
||||
}
|
||||
if (!formData.contactEmail.trim()) {
|
||||
newErrors.contactEmail = "Contact email is required";
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.contactEmail)) {
|
||||
newErrors.contactEmail = "Please enter a valid email address";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleInputChange = (field: keyof FormData, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
if (errors[field]) {
|
||||
setErrors((prev) => {
|
||||
const newErrors = { ...prev };
|
||||
delete newErrors[field];
|
||||
return newErrors;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!validateForm()) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
setSubmitted(true);
|
||||
setFormData({
|
||||
jobTitle: "", companyName: "", location: "", jobType: "Full-time", salary: "", description: "", requirements: "", benefits: "", contactEmail: ""});
|
||||
setTimeout(() => setSubmitted(false), 5000);
|
||||
} catch (error) {
|
||||
console.error("Submission error:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
@@ -59,65 +141,230 @@ export default function PostJobPage() {
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleCentered
|
||||
navItems={navItems}
|
||||
button={{ text: "Post a Job", href: "/post-job" }}
|
||||
brandName="Jobee"
|
||||
navItems={navItems}
|
||||
button={{
|
||||
text: "Post a Job", href: "/post-job"}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="features" data-section="features">
|
||||
<FeatureCardEight
|
||||
features={[
|
||||
{
|
||||
id: 1,
|
||||
title: "Create a Job Posting", description:
|
||||
"Fill in job details, requirements, and salary information. Our intuitive form guides you through every step to create a compelling job listing.", imageSrc:
|
||||
"http://img.b2bpic.net/free-vector/professional-recruitment-plan-diversity-general-infographic-template_23-2148947635.jpg?_wi=4", imageAlt: "Job posting creation interface"},
|
||||
{
|
||||
id: 2,
|
||||
title: "Reach Qualified Candidates", description:
|
||||
"Your job posting is automatically distributed across our network of active job seekers across all 12 Dutch provinces.", imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/homepage-concept-with-search-bar_23-2150040187.jpg?_wi=5", imageAlt: "Candidate reach visualization"},
|
||||
{
|
||||
id: 3,
|
||||
title: "Review Applications", description:
|
||||
"Manage all incoming applications in one centralized dashboard. Review resumes, cover letters, and candidate profiles easily.", imageSrc:
|
||||
"http://img.b2bpic.net/free-photo/personal-information-form-identity-concept_53876-137622.jpg?_wi=4", imageAlt: "Application review dashboard"},
|
||||
]}
|
||||
title="Post a Job in Three Easy Steps"
|
||||
description="Reach thousands of qualified job seekers across the Netherlands and build your dream team."
|
||||
tag="Simple Process"
|
||||
tagIcon={Sparkles}
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
buttons={[{ text: "Start Posting", href: "/post-job" }]}
|
||||
buttonAnimation="slide-up"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-screen bg-background py-16 md:py-24">
|
||||
<div className="mx-auto w-full max-w-2xl px-4 md:px-8">
|
||||
{/* Header */}
|
||||
<div className="mb-12 text-center">
|
||||
<h1 className="mb-4 text-4xl md:text-5xl font-bold text-foreground">
|
||||
Post a Job
|
||||
</h1>
|
||||
<p className="text-lg text-foreground/75">
|
||||
Reach thousands of qualified job seekers across the Netherlands
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="contact" data-section="contact">
|
||||
<ContactCenter
|
||||
tag="Newsletter"
|
||||
title="Get Notified About Top Talent"
|
||||
description="Subscribe to receive alerts when qualified candidates match your job requirements. Stay ahead of the competition and hire the best talent."
|
||||
tagIcon={Mail}
|
||||
tagAnimation="slide-up"
|
||||
background={{ variant: "animated-grid" }}
|
||||
useInvertedBackground={false}
|
||||
inputPlaceholder="Enter your company email"
|
||||
buttonText="Subscribe"
|
||||
termsText="We respect your privacy. Unsubscribe anytime from our notifications."
|
||||
/>
|
||||
{/* Success Message */}
|
||||
{submitted && (
|
||||
<div className="mb-8 flex items-center gap-3 rounded-lg bg-green-50 p-4 text-green-700 border border-green-200">
|
||||
<CheckCircle size={20} />
|
||||
<div>
|
||||
<p className="font-semibold">Job posted successfully!</p>
|
||||
<p className="text-sm">Your job listing will be live shortly.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSubmitted(false)}
|
||||
className="ml-auto p-1 hover:bg-green-100 rounded"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Form */}
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="rounded-xl bg-card p-8 md:p-12 border border-accent/20 shadow-lg"
|
||||
>
|
||||
{/* Job Title */}
|
||||
<div className="mb-6">
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Job Title *
|
||||
</label>
|
||||
<Input
|
||||
value={formData.jobTitle}
|
||||
onChange={(value) => handleInputChange("jobTitle", value)}
|
||||
placeholder="e.g., Senior Software Engineer"
|
||||
required
|
||||
/>
|
||||
{errors.jobTitle && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.jobTitle}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Company Name */}
|
||||
<div className="mb-6">
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Company Name *
|
||||
</label>
|
||||
<Input
|
||||
value={formData.companyName}
|
||||
onChange={(value) => handleInputChange("companyName", value)}
|
||||
placeholder="Your company name"
|
||||
required
|
||||
/>
|
||||
{errors.companyName && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.companyName}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Location */}
|
||||
<div className="mb-6">
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Location *
|
||||
</label>
|
||||
<Input
|
||||
value={formData.location}
|
||||
onChange={(value) => handleInputChange("location", value)}
|
||||
placeholder="e.g., Amsterdam, Netherlands"
|
||||
required
|
||||
/>
|
||||
{errors.location && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.location}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Job Type and Salary */}
|
||||
<div className="mb-6 grid gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Job Type *
|
||||
</label>
|
||||
<select
|
||||
value={formData.jobType}
|
||||
onChange={(e) => handleInputChange("jobType", e.target.value)}
|
||||
className="w-full rounded-lg bg-secondary-button px-4 py-2.5 text-foreground placeholder-foreground/75 border border-accent/20 focus:outline-none focus:ring-2 focus:ring-primary-cta"
|
||||
>
|
||||
<option>Full-time</option>
|
||||
<option>Part-time</option>
|
||||
<option>Contract</option>
|
||||
<option>Temporary</option>
|
||||
<option>Internship</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Salary Range *
|
||||
</label>
|
||||
<Input
|
||||
value={formData.salary}
|
||||
onChange={(value) => handleInputChange("salary", value)}
|
||||
placeholder="e.g., €50,000 - €80,000"
|
||||
required
|
||||
/>
|
||||
{errors.salary && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.salary}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Job Description */}
|
||||
<div className="mb-6">
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Job Description *
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => handleInputChange("description", e.target.value)}
|
||||
placeholder="Describe the role, responsibilities, and key details..."
|
||||
rows={6}
|
||||
className="w-full rounded-lg bg-secondary-button px-4 py-2.5 text-foreground placeholder-foreground/75 border border-accent/20 focus:outline-none focus:ring-2 focus:ring-primary-cta resize-none"
|
||||
required
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Requirements */}
|
||||
<div className="mb-6">
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Requirements *
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.requirements}
|
||||
onChange={(e) => handleInputChange("requirements", e.target.value)}
|
||||
placeholder="List the key requirements and qualifications (e.g., skills, experience, education)..."
|
||||
rows={5}
|
||||
className="w-full rounded-lg bg-secondary-button px-4 py-2.5 text-foreground placeholder-foreground/75 border border-accent/20 focus:outline-none focus:ring-2 focus:ring-primary-cta resize-none"
|
||||
required
|
||||
/>
|
||||
{errors.requirements && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.requirements}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="mb-6">
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Benefits (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.benefits}
|
||||
onChange={(e) => handleInputChange("benefits", e.target.value)}
|
||||
placeholder="Highlight benefits such as remote work, flexible hours, health insurance, training, etc."
|
||||
rows={4}
|
||||
className="w-full rounded-lg bg-secondary-button px-4 py-2.5 text-foreground placeholder-foreground/75 border border-accent/20 focus:outline-none focus:ring-2 focus:ring-primary-cta resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Contact Email */}
|
||||
<div className="mb-8">
|
||||
<label className="block mb-2 text-sm font-semibold text-foreground">
|
||||
Contact Email *
|
||||
</label>
|
||||
<Input
|
||||
value={formData.contactEmail}
|
||||
onChange={(value) => handleInputChange("contactEmail", value)}
|
||||
type="email"
|
||||
placeholder="your-email@company.com"
|
||||
required
|
||||
/>
|
||||
{errors.contactEmail && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.contactEmail}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full rounded-lg bg-primary-cta px-6 py-3 font-semibold text-white hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed transition-opacity flex items-center justify-center gap-2"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent"></div>
|
||||
Posting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Upload size={18} />
|
||||
Post Job
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Form Info */}
|
||||
<p className="mt-4 text-center text-sm text-foreground/60">
|
||||
* Required fields. Your job will be reviewed before going live.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
columns={footerColumns}
|
||||
logoText="Jobee"
|
||||
copyrightText="© 2025 Jobee | Dutch Job Listing Platform"
|
||||
columns={footerColumns}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"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, MapPin, DollarSign, Briefcase, ChevronLeft, ChevronRight, X } from "lucide-react";
|
||||
|
||||
const navItems = [
|
||||
{ name: "Search Jobs", id: "search" },
|
||||
{ name: "Search Jobs", id: "/search" },
|
||||
{ name: "Post a Job", id: "post-job" },
|
||||
{ name: "Admin", id: "admin-login" },
|
||||
{ name: "Browse", id: "browse" },
|
||||
@@ -42,7 +41,94 @@ const footerColumns = [
|
||||
},
|
||||
];
|
||||
|
||||
interface Job {
|
||||
id: string;
|
||||
title: string;
|
||||
company: string;
|
||||
location: string;
|
||||
province: string;
|
||||
salary: string;
|
||||
type: string;
|
||||
category: string;
|
||||
description: string;
|
||||
posted: string;
|
||||
}
|
||||
|
||||
const mockJobs: Job[] = [
|
||||
{
|
||||
id: "1", title: "Senior Software Engineer", company: "TechFlow Solutions", location: "Amsterdam", province: "North Holland", salary: "€80,000 - €120,000", type: "Full-time", category: "Technology", description: "We are looking for an experienced software engineer to join our growing team.", posted: "2 days ago"},
|
||||
{
|
||||
id: "2", title: "UX/UI Designer", company: "Creative Studio", location: "Rotterdam", province: "South Holland", salary: "€50,000 - €75,000", type: "Full-time", category: "Design", description: "Join our design team to create beautiful and intuitive user experiences.", posted: "1 day ago"},
|
||||
{
|
||||
id: "3", title: "Marketing Manager", company: "Global Marketing Inc", location: "Utrecht", province: "Utrecht", salary: "€60,000 - €85,000", type: "Full-time", category: "Marketing", description: "Lead our marketing initiatives and strategy for European markets.", posted: "3 days ago"},
|
||||
{
|
||||
id: "4", title: "Data Analyst", company: "DataViz Corp", location: "Amsterdam", province: "North Holland", salary: "€55,000 - €80,000", type: "Full-time", category: "Technology", description: "Analyze complex datasets and create actionable insights for stakeholders.", posted: "1 week ago"},
|
||||
{
|
||||
id: "5", title: "Sales Representative", company: "SalesForce Pro", location: "Groningen", province: "Groningen", salary: "€45,000 - €65,000", type: "Full-time", category: "Sales", description: "Build relationships with clients and close deals in the B2B tech sector.", posted: "4 days ago"},
|
||||
{
|
||||
id: "6", title: "Product Manager", company: "Innovation Labs", location: "Amsterdam", province: "North Holland", salary: "€75,000 - €110,000", type: "Full-time", category: "Technology", description: "Define product strategy and lead cross-functional teams.", posted: "5 days ago"},
|
||||
{
|
||||
id: "7", title: "HR Specialist", company: "People First", location: "The Hague", province: "South Holland", salary: "€40,000 - €60,000", type: "Part-time", category: "Human Resources", description: "Support recruitment, onboarding, and employee development.", posted: "6 days ago"},
|
||||
{
|
||||
id: "8", title: "Backend Developer", company: "Cloud Systems", location: "Amsterdam", province: "North Holland", salary: "€70,000 - €100,000", type: "Full-time", category: "Technology", description: "Develop scalable backend systems and APIs using modern technologies.", posted: "2 weeks ago"},
|
||||
{
|
||||
id: "9", title: "Content Writer", company: "Content Creators", location: "Utrecht", province: "Utrecht", salary: "€35,000 - €50,000", type: "Full-time", category: "Marketing", description: "Create engaging content for blogs, social media, and marketing campaigns.", posted: "3 days ago"},
|
||||
{
|
||||
id: "10", title: "DevOps Engineer", company: "Infrastructure Pro", location: "Amsterdam", province: "North Holland", salary: "€65,000 - €95,000", type: "Full-time", category: "Technology", description: "Maintain and optimize cloud infrastructure and CI/CD pipelines.", posted: "1 week ago"},
|
||||
{
|
||||
id: "11", title: "Financial Analyst", company: "Finance Solutions", location: "Amsterdam", province: "North Holland", salary: "€55,000 - €80,000", type: "Full-time", category: "Finance", description: "Analyze financial data and provide insights for investment decisions.", posted: "4 days ago"},
|
||||
{
|
||||
id: "12", title: "Customer Support Manager", company: "Support Hub", location: "Leiden", province: "South Holland", salary: "€45,000 - €65,000", type: "Full-time", category: "Customer Service", description: "Lead and manage customer support team to ensure excellent service quality.", posted: "5 days ago"},
|
||||
];
|
||||
|
||||
const provinces = [
|
||||
"North Holland", "South Holland", "Utrecht", "Groningen", "The Hague", "Friesland", "Drenthe", "Flevoland", "Overijssel", "Gelderland", "Limburg", "North Brabant"];
|
||||
|
||||
const categories = ["Technology", "Design", "Marketing", "Sales", "Human Resources", "Finance", "Customer Service"];
|
||||
const jobTypes = ["Full-time", "Part-time", "Contract", "Freelance"];
|
||||
|
||||
export default function SearchPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedProvinces, setSelectedProvinces] = useState<string[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||
const [selectedTypes, setSelectedTypes] = useState<string[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 6;
|
||||
|
||||
const filteredJobs = useMemo(() => {
|
||||
return mockJobs.filter((job) => {
|
||||
const matchesQuery =
|
||||
searchQuery === "" ||
|
||||
job.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
job.company.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
job.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
const matchesProvince =
|
||||
selectedProvinces.length === 0 || selectedProvinces.includes(job.province);
|
||||
|
||||
const matchesCategory =
|
||||
selectedCategories.length === 0 || selectedCategories.includes(job.category);
|
||||
|
||||
const matchesType = selectedTypes.length === 0 || selectedTypes.includes(job.type);
|
||||
|
||||
return matchesQuery && matchesProvince && matchesCategory && matchesType;
|
||||
});
|
||||
}, [searchQuery, selectedProvinces, selectedCategories, selectedTypes]);
|
||||
|
||||
const totalPages = Math.ceil(filteredJobs.length / itemsPerPage);
|
||||
const paginatedJobs = filteredJobs.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
);
|
||||
|
||||
const toggleFilter = (value: string, setter: Function, state: string[]) => {
|
||||
if (state.includes(value)) {
|
||||
setter(state.filter((item) => item !== value));
|
||||
} else {
|
||||
setter([...state, value]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="text-stagger"
|
||||
@@ -65,54 +151,241 @@ 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-br from-slate-50 to-slate-100 dark:from-slate-950 dark:to-slate-900 pt-20 pb-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Search Header */}
|
||||
<div className="mb-12">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-slate-900 dark:text-white mb-4">
|
||||
Find Your Dream Job
|
||||
</h1>
|
||||
<p className="text-lg text-slate-600 dark:text-slate-300 mb-8">
|
||||
Browse thousands of opportunities across all Dutch provinces
|
||||
</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."
|
||||
/>
|
||||
{/* Search Bar */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-slate-400 w-5 h-5" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by job title, company, or keyword..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
setSearchQuery(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="w-full pl-12 pr-4 py-3 rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
{/* Filters Sidebar */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="sticky top-24 space-y-6 bg-white dark:bg-slate-800 p-6 rounded-lg border border-slate-200 dark:border-slate-700">
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-900 dark:text-white mb-3 text-sm uppercase tracking-wide">
|
||||
Provinces
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-64 overflow-y-auto">
|
||||
{provinces.map((province) => (
|
||||
<label key={province} className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedProvinces.includes(province)}
|
||||
onChange={() =>
|
||||
toggleFilter(province, setSelectedProvinces, selectedProvinces)
|
||||
}
|
||||
className="w-4 h-4 rounded border-slate-300 text-blue-600 focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-slate-700 dark:text-slate-300">
|
||||
{province}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-200 dark:border-slate-700 pt-4">
|
||||
<h3 className="font-semibold text-slate-900 dark:text-white mb-3 text-sm uppercase tracking-wide">
|
||||
Category
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{categories.map((category) => (
|
||||
<label key={category} className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedCategories.includes(category)}
|
||||
onChange={() =>
|
||||
toggleFilter(category, setSelectedCategories, selectedCategories)
|
||||
}
|
||||
className="w-4 h-4 rounded border-slate-300 text-blue-600 focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-slate-700 dark:text-slate-300">
|
||||
{category}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-200 dark:border-slate-700 pt-4">
|
||||
<h3 className="font-semibold text-slate-900 dark:text-white mb-3 text-sm uppercase tracking-wide">
|
||||
Job Type
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{jobTypes.map((type) => (
|
||||
<label key={type} className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedTypes.includes(type)}
|
||||
onChange={() => toggleFilter(type, setSelectedTypes, selectedTypes)}
|
||||
className="w-4 h-4 rounded border-slate-300 text-blue-600 focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-slate-700 dark:text-slate-300">
|
||||
{type}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Clear Filters */}
|
||||
{(selectedProvinces.length > 0 ||
|
||||
selectedCategories.length > 0 ||
|
||||
selectedTypes.length > 0) && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedProvinces([]);
|
||||
setSelectedCategories([]);
|
||||
setSelectedTypes([]);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="w-full py-2 px-3 bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-lg text-sm font-medium hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors"
|
||||
>
|
||||
Clear Filters
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Job Listings */}
|
||||
<div className="lg:col-span-3">
|
||||
{/* Results Count */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400">
|
||||
Showing {paginatedJobs.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0} to{" "}
|
||||
{Math.min(currentPage * itemsPerPage, filteredJobs.length)} of{" "}
|
||||
<span className="font-semibold text-slate-900 dark:text-white">
|
||||
{filteredJobs.length}
|
||||
</span>{" "}
|
||||
jobs
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Job Cards Grid */}
|
||||
{paginatedJobs.length > 0 ? (
|
||||
<div className="grid gap-4 mb-8">
|
||||
{paginatedJobs.map((job) => (
|
||||
<div
|
||||
key={job.id}
|
||||
className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg p-6 hover:shadow-lg hover:border-blue-300 dark:hover:border-blue-600 transition-all duration-300 cursor-pointer group"
|
||||
>
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
|
||||
{job.title}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400 mt-1">
|
||||
{job.company}
|
||||
</p>
|
||||
</div>
|
||||
<span className="px-3 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-xs font-medium rounded-full whitespace-nowrap">
|
||||
{job.type}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400 mb-4 line-clamp-2">
|
||||
{job.description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3 text-sm">
|
||||
<div className="flex items-center gap-1 text-slate-600 dark:text-slate-400">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>{job.location}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-slate-600 dark:text-slate-400">
|
||||
<DollarSign className="w-4 h-4" />
|
||||
<span>{job.salary}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-slate-600 dark:text-slate-400">
|
||||
<Briefcase className="w-4 h-4" />
|
||||
<span>{job.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right text-xs text-slate-500 dark:text-slate-500 whitespace-nowrap">
|
||||
<p>{job.posted}</p>
|
||||
<button className="mt-2 px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-slate-600 dark:text-slate-400 mb-2">
|
||||
No jobs found matching your criteria.
|
||||
</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-500">
|
||||
Try adjusting your filters or search query.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<button
|
||||
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
||||
disabled={currentPage === 1}
|
||||
className="p-2 rounded-lg border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
|
||||
<button
|
||||
key={page}
|
||||
onClick={() => setCurrentPage(page)}
|
||||
className={`w-10 h-10 rounded-lg font-medium transition-colors ${
|
||||
currentPage === page
|
||||
? "bg-blue-600 text-white"
|
||||
: "border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700"
|
||||
}`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
className="p-2 rounded-lg border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
@@ -124,4 +397,4 @@ export default function SearchPage() {
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user