-
+
-
@@ -159,9 +186,78 @@ export default function BrandKitPage() {
-
{logos.map((logo) => (
-
+
- Put both logos at the same height, with a thin × between them. +
- Use our black logo on light backgrounds and the white one on dark backgrounds. +
- Keep the seven bars under our wordmark; do not crop or recolor them. +
+ Dev Weekends is a free, international community where engineers learn, build and grow together. +
++ Tagline, if you have room: Great engineers. Greater impact. +
++ Send your draft to{" "} + + info@devweekends.com + {" "} + before it goes public. +
+Partner badges
+-
+ {badges.map((badge) => (
+
-
+
+ ++ {/* eslint-disable-next-line @next/next/no-img-element */} +++
+ {badge.name} + + + SVG + ++
+ ))}
+
-
+
-
diff --git a/app/careers/layout.tsx b/app/careers/layout.tsx new file mode 100644 index 0000000..0656c7c --- /dev/null +++ b/app/careers/layout.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from "next" +import type { ReactNode } from "react" + +export const metadata: Metadata = { + title: "Careers", + description: + "Hand-picked opportunities for students and professionals across Dev Weekends and Dev Weekends partner companies.", + alternates: { canonical: "/careers" }, + robots: { index: false }, +} + +export default function CareersLayout({ children }: { children: ReactNode }) { + return children +} diff --git a/app/careers/page.tsx b/app/careers/page.tsx index f634109..fb811d9 100644 --- a/app/careers/page.tsx +++ b/app/careers/page.tsx @@ -1,277 +1,279 @@ -'use client'; +"use client" -import { useEffect, useMemo, useState } from 'react'; -import Link from 'next/link'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; +import { useEffect, useMemo, useState } from "react" + +import { Button, Card, PageHero, Section } from "@/components/site/page-kit" +import { Input } from "@/components/ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" type JobDoc = { - _id: string; - title: string; - company: string; - location?: string; - workplaceType?: string; - employmentType?: string; - audience?: string; - description: string; - requirements?: string; - applyUrl?: string; - applyEmail?: string; - deadline?: string; - tags?: string[]; - createdAt?: string; -}; + _id: string + title: string + company: string + location?: string + workplaceType?: string + employmentType?: string + audience?: string + description: string + requirements?: string + applyUrl?: string + applyEmail?: string + deadline?: string + tags?: string[] + createdAt?: string +} + +type Audience = "all" | "Students" | "Professionals" | "Both" +type Workplace = "all" | "Remote" | "Hybrid" | "Onsite" +type Employment = "all" | "Internship" | "Full-time" | "Part-time" | "Contract" + +const focusRing = + "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0A0A0A]" + +const fieldClass = `h-12 rounded-xl border-[#D4D4D4] bg-white px-4 text-base text-[#0A0A0A] ${focusRing}` +const triggerClass = `${fieldClass} w-full sm:w-[190px]` + +const primaryButton = `inline-flex min-h-[44px] w-full items-center justify-center rounded-xl bg-[#0A0A0A] px-5 text-[15px] font-semibold text-white transition-colors hover:bg-[#262626] disabled:cursor-not-allowed disabled:opacity-40 ${focusRing}` +const secondaryButton = `inline-flex min-h-[44px] w-full items-center justify-center rounded-xl border border-[#D4D4D4] px-5 text-[15px] font-semibold text-[#0A0A0A] transition-colors hover:bg-[#F7F7F7] disabled:cursor-not-allowed disabled:opacity-40 ${focusRing}` + +function Tag({ children, solid }: { children: string; solid?: boolean }) { + return ( + + {children} + + ) +} -export default function CarrersPage() { - const [jobs, setJobs] = useState ([]); - const [loading, setLoading] = useState(true); - const [search, setSearch] = useState(''); - const [audience, setAudience] = useState<'all' | 'Students' | 'Professionals' | 'Both'>('all'); - const [workplace, setWorkplace] = useState<'all' | 'Remote' | 'Hybrid' | 'Onsite'>('all'); - const [employment, setEmployment] = useState<'all' | 'Internship' | 'Full-time' | 'Part-time' | 'Contract'>('all'); - const [applyOpen, setApplyOpen] = useState(false); - const [selectedJob, setSelectedJob] = useState (null); - const [detailsOpen, setDetailsOpen] = useState(false); - const [detailsJob, setDetailsJob] = useState (null); +function Fact({ label, value }: { label: string; value: string }) { + return ( + ++ ) +} + +function formatDeadline(deadline?: string) { + return deadline ? new Date(deadline).toLocaleDateString() : "Not specified" +} + +export default function CareersPage() { + const [jobs, setJobs] = useState- {label}:
+- {value}
+([]) + const [loading, setLoading] = useState(true) + const [search, setSearch] = useState("") + const [audience, setAudience] = useState ("all") + const [workplace, setWorkplace] = useState ("all") + const [employment, setEmployment] = useState ("all") + const [applyOpen, setApplyOpen] = useState(false) + const [selectedJob, setSelectedJob] = useState (null) + const [detailsOpen, setDetailsOpen] = useState(false) + const [detailsJob, setDetailsJob] = useState (null) useEffect(() => { - fetch('/api/jobs') + fetch("/api/jobs") .then((res) => res.json()) .then((data) => setJobs(Array.isArray(data) ? data : [])) - .finally(() => setLoading(false)); - }, []); + .finally(() => setLoading(false)) + }, []) const filteredJobs = useMemo(() => { - const q = search.trim().toLowerCase(); + const q = search.trim().toLowerCase() return jobs.filter((j) => { - if (audience !== 'all' && (j.audience || 'Both') !== audience) return false; - if (workplace !== 'all' && (j.workplaceType || 'Remote') !== workplace) return false; - if (employment !== 'all' && (j.employmentType || 'Full-time') !== employment) return false; - if (!q) return true; + if (audience !== "all" && (j.audience || "Both") !== audience) return false + if (workplace !== "all" && (j.workplaceType || "Remote") !== workplace) return false + if (employment !== "all" && (j.employmentType || "Full-time") !== employment) return false + if (!q) return true return ( j.title?.toLowerCase().includes(q) || j.company?.toLowerCase().includes(q) || - (j.location || '').toLowerCase().includes(q) || - (j.tags || []).join(' ').toLowerCase().includes(q) - ); - }); - }, [jobs, search, audience, workplace, employment]); + (j.location || "").toLowerCase().includes(q) || + (j.tags || []).join(" ").toLowerCase().includes(q) + ) + }) + }, [jobs, search, audience, workplace, employment]) const openApply = (job: JobDoc) => { - setSelectedJob(job); - setApplyOpen(true); - }; + setSelectedJob(job) + setApplyOpen(true) + } const openDetails = (job: JobDoc) => { - setDetailsJob(job); - setDetailsOpen(true); - }; + setDetailsJob(job) + setDetailsOpen(true) + } const copyToClipboard = async (text: string) => { try { - await navigator.clipboard.writeText(text); + await navigator.clipboard.writeText(text) } catch { - // ignore + // Clipboard blocked; the value stays visible in the field. } - }; + } + + const applyUrl = selectedJob?.applyUrl?.trim() + const applyEmail = selectedJob?.applyEmail?.trim() return ( - --+ <> +Careers
-- Hand-picked opportunities for students and professionals across Dev Weekends and Dev Weekends partner companies. -
-Share a Job} + /> - - setSearch(e.target.value)} - className="sm:max-w-md" - /> - - - - - -++ + setSearch(e.target.value)} + className={`${fieldClass} sm:max-w-md sm:flex-1`} + /> + + + +-- {loading ? ( -Loading jobs...- ) : filteredJobs.length === 0 ? ( -- - ) : ( -- -No jobs right now-Check back soon — we post opportunities regularly.-- {filteredJobs.map((job) => { - return ( -- - - {job.workplaceType ?+{job.workplaceType} : null} - {job.employmentType ?{job.employmentType} : null} - {job.audience ?{job.audience} : null} ++ {loading ? ( ++ + + ))} + + )} ++ Loading jobs... +
+ ) : filteredJobs.length === 0 ? ( ++ + ) : ( +No jobs right now
+Check back soon. We post opportunities regularly.
+-
+ {filteredJobs.map((job) => (
+
-
+
+ + {job.workplaceType ?-{job.workplaceType} : null} + {job.employmentType ?{job.employmentType} : null} + {job.audience ?{job.audience} : null}{job.title} --- -- Company:{' '} - {job.company} --- Location:{' '} - {job.location || 'Not specified'} --- Deadline:{' '} - {job.deadline ? new Date(job.deadline).toLocaleDateString() : 'Not specified'} --- {job.description}
+{job.title}
+-
+
+ + + {job.description}
{Array.isArray(job.tags) && job.tags.length > 0 ? ( -++-
{job.tags.slice(0, 6).map((t, idx) => (
-
-
+
{t} +
))}
-
- {t} - + -
+
- -- )} -
-
{guidelines.map((rule, index) => (