Initial React project

This commit is contained in:
Johan
2026-03-14 20:19:22 +01:00
parent 4803e966b5
commit efa11c1fc6
12 changed files with 372 additions and 61 deletions

View File

@@ -0,0 +1,303 @@
import { useMemo, useState, type CSSProperties } from 'react';
import './faq.css';
import { SiteFooter } from '../../shared/components/SiteFooter';
import { SiteNavbar } from '../../shared/components/SiteNavbar';
interface IconifyIconProps {
className?: string;
icon: string;
style?: CSSProperties;
}
function IconifyIcon({ className, icon, style }: IconifyIconProps) {
return <iconify-icon className={className} icon={icon} style={style} />;
}
type FaqCategory = 'jobseekers' | 'companies' | 'account' | 'payment';
interface FaqItem {
answer: string;
question: string;
}
interface FaqSection {
description: string;
icon: string;
items: FaqItem[];
label: string;
title: string;
}
const faqSections: Record<FaqCategory, FaqSection> = {
jobseekers: {
label: 'For jobsøgere',
title: 'For jobsøgere',
icon: 'solar:user-search-linear',
description:
'Vi har gjort det enkelt at komme i gang med din jobsøgning. Opret en profil, bliv matchet med relevante virksomheder, og ansøg med ét klik.',
items: [
{
question: 'Hvad er fordelene ved at oprette en profil?',
answer:
'Når du opretter en profil, bliver du synlig for virksomheder, der leder efter kandidater med netop dine kompetencer. Du får også personlige jobforslag og et samlet overblik over din jobsøgning.',
},
{
question: 'Hvordan søger jeg job med ét klik?',
answer:
'Når din profil er sat op, kan du ansøge direkte på relevante stillinger med få klik. Systemet genbruger dine oplysninger, så du kan søge hurtigere.',
},
{
question: 'Hvordan bliver jeg matchet med de rette job?',
answer:
'Vores matching tager udgangspunkt i din profil, erfaring, præferencer og de krav, virksomhederne har i deres opslag. Jo mere komplet din profil er, desto bedre bliver matchene.',
},
{
question: 'Hvordan fungerer priserne for jobsøgere?',
answer:
'Du kan starte gratis og opgradere, når du ønsker adgang til flere funktioner. På prissiden kan du vælge mellem forskellige pakker afhængigt af dit behov.',
},
{
question: 'Hvilke abonnementsmuligheder har I?',
answer:
'Vi tilbyder flere forløb, så du kan vælge den periode, der passer bedst til din jobsøgning. Alle premium pakker giver adgang til de avancerede værktøjer.',
},
{
question: 'Hvordan fungerer notifikationer?',
answer:
'Du modtager notifikationer, når der er nye job, som matcher din profil, eller når der sker opdateringer på dine ansøgninger.',
},
{
question: 'Hvad er Arbejd.com Karriere Agent?',
answer:
'Karriere Agenten hjælper dig med anbefalinger til din profil og peger på muligheder, der kan øge dine chancer for at komme i samtale.',
},
{
question: 'Hvordan kontakter jeg support?',
answer:
'Du kan kontakte os via kontaktsiden, hvis du har spørgsmål til din profil, abonnement eller brug af platformen.',
},
],
},
companies: {
label: 'For virksomheder',
title: 'For virksomheder',
icon: 'solar:buildings-2-linear',
description:
'Arbejd.com giver virksomheder adgang til effektiv rekruttering med ubegrænset oprettelse af jobannoncer og mulighed for at matche med relevante kandidater.',
items: [
{
question: 'Hvordan opretter man en jobannonce?',
answer:
'Du kan oprette jobannoncer direkte på platformen gennem et enkelt flow. Når annoncen er aktiv, bliver den synlig for relevante kandidater.',
},
{
question: 'Hvordan fungerer betalingen?',
answer:
'Det er gratis at oprette annoncer. Betaling sker først, når du ønsker adgang til kontaktoplysninger på ansøgere og kandidater.',
},
{
question: 'Er der ingen begrænsning på antal jobannoncer?',
answer:
'Nej. Du kan oprette så mange jobannoncer, du har behov for, uden ekstra oprettelsesomkostning.',
},
{
question: 'Hvordan matcher vi med relevante kandidater?',
answer:
'Systemet bruger kriterier fra jobannoncen og kandidatprofiler til at foreslå de mest relevante matches, så du hurtigere kan finde de rigtige personer.',
},
{
question: 'Hvordan kontakter vi kandidater?',
answer:
'Når du har valgt de kandidater, du vil gå videre med, får du adgang til kontaktoplysninger og kan starte dialogen direkte.',
},
{
question: 'Tilbyder I support til virksomheder?',
answer:
'Ja. Vi hjælper med onboarding, spørgsmål til platformen og løbende optimering af jeres rekrutteringsflow.',
},
],
},
account: {
label: 'Brugerprofil og login',
title: 'Brugerprofil og login',
icon: 'solar:login-3-linear',
description:
'Her finder du svar på de mest almindelige spørgsmål om konto, login, profiloprettelse og sikkerhed.',
items: [
{
question: 'Hvordan opretter jeg en brugerprofil?',
answer:
'Du opretter en profil ved at vælge “Opret dig” og udfylde dine grundoplysninger. Herefter kan du færdiggøre din profil og begynde at bruge platformen.',
},
{
question: 'Hvad gør jeg, hvis jeg har glemt mit password?',
answer:
'Brug “Glemt password” på login-siden. Du modtager en e-mail med vejledning til at nulstille dit kodeord.',
},
{
question: 'Hvordan redigerer jeg min profil?',
answer:
'Når du er logget ind, kan du opdatere profiloplysninger, erfaring og præferencer løbende i dine profilindstillinger.',
},
{
question: 'Kan jeg have flere profiler?',
answer:
'Vi anbefaler én profil pr. bruger for at sikre den mest præcise matching og den bedste oplevelse på platformen.',
},
{
question: 'Hvordan sikrer I mine data?',
answer:
'Vi arbejder med tekniske og organisatoriske sikkerhedstiltag, så dine data håndteres ansvarligt og i overensstemmelse med gældende regler.',
},
],
},
payment: {
label: 'Abonnement og betaling',
title: 'Abonnement og betaling',
icon: 'solar:wallet-money-linear',
description:
'Få overblik over abonnementstyper, opgradering, betaling og fakturering på Arbejd.com.',
items: [
{
question: 'Hvilke abonnementer tilbyder I?',
answer:
'Vi tilbyder flere pakker, så både jobsøgere og virksomheder kan vælge en løsning, der passer til deres behov og tidsramme.',
},
{
question: 'Kan jeg starte gratis?',
answer:
'Ja, du kan komme i gang gratis. Du kan opgradere senere, hvis du ønsker adgang til premium funktioner.',
},
{
question: 'Hvordan opgraderer jeg mit abonnement?',
answer:
'Du kan opgradere direkte fra platformen ved at vælge den ønskede plan. Ændringen træder i kraft med det samme.',
},
{
question: 'Hvordan fungerer betaling for virksomheder?',
answer:
'Virksomheder betaler først, når de ønsker adgang til kontaktoplysninger på relevante kandidater.',
},
{
question: 'Kan jeg opsige mit abonnement?',
answer:
'Ja, abonnement kan opsiges fra dine kontoindstillinger. Eventuelle ændringer følger den valgte planperiode.',
},
],
},
};
const categoryOrder: FaqCategory[] = ['jobseekers', 'companies', 'account', 'payment'];
function initialCategoryFromHash(): FaqCategory {
const hash = window.location.hash.toLowerCase();
if (hash.includes('for-jobs-gere') || hash.includes('for-jobsogere')) return 'jobseekers';
if (hash.includes('for-virksomheder')) return 'companies';
if (hash.includes('brugerprofil')) return 'account';
if (hash.includes('betaling') || hash.includes('abonnement')) return 'payment';
return 'jobseekers';
}
export function FaqPage() {
const [activeCategory, setActiveCategory] = useState<FaqCategory>(initialCategoryFromHash);
const [openItem, setOpenItem] = useState<number>(0);
const activeSection = useMemo(() => faqSections[activeCategory], [activeCategory]);
return (
<div className="faq-react-root scroll-smooth bg-[#f8fafc] relative min-h-screen text-gray-600 selection:bg-teal-100 selection:text-teal-900 overflow-x-hidden flex flex-col font-normal faq-scrollbar">
<div className="fixed top-[-15%] left-[-10%] w-[60vw] h-[60vw] rounded-full bg-gradient-to-br from-teal-400/30 to-emerald-300/10 blur-[140px] pointer-events-none z-0" />
<div className="fixed bottom-[-15%] right-[-10%] w-[70vw] h-[70vw] rounded-full bg-gradient-to-tl from-indigo-500/20 to-purple-400/10 blur-[160px] pointer-events-none z-0" />
<div className="fixed top-[20%] right-[15%] w-[40vw] h-[40vw] rounded-full bg-gradient-to-tr from-cyan-400/20 to-blue-300/10 blur-[130px] pointer-events-none z-0" />
<SiteNavbar />
<main className="flex-1 relative z-10 pt-16">
<section className="pt-24 pb-10 px-6 lg:px-12 max-w-7xl mx-auto text-center">
<h1 className="text-4xl md:text-6xl font-medium tracking-tight text-gradient-subtle mb-5">
FAQ
</h1>
<p className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed">
svar de mest stillede spørgsmål om Arbejd.com for jobsøgere, virksomheder, profil/login og betaling.
</p>
</section>
<section className="pb-24 px-6 lg:px-12 max-w-7xl mx-auto w-full">
<div className="flex justify-center mb-10">
<div className="inline-flex p-1.5 bg-white/45 backdrop-blur-xl border border-white/80 rounded-full shadow-[0_4px_20px_rgba(0,0,0,0.03)] flex-wrap gap-1">
{categoryOrder.map((category) => {
const isActive = activeCategory === category;
return (
<button
key={category}
type="button"
className={`px-4 md:px-6 py-2.5 text-sm md:text-base font-medium rounded-full transition-all outline-none ${isActive ? 'text-gray-900 bg-white shadow-sm border border-gray-100' : 'text-gray-500 hover:text-gray-900'}`}
onClick={() => {
setActiveCategory(category);
setOpenItem(0);
}}
>
{faqSections[category].label}
</button>
);
})}
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-[0.9fr_1.1fr] gap-8">
<div className="rounded-[2.2rem] p-7 md:p-9 bg-gradient-to-br from-white/70 to-white/25 border border-white/80 backdrop-blur-2xl shadow-[0_12px_40px_rgba(0,0,0,0.05)] h-fit">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-teal-50 to-white border border-teal-100/60 shadow-sm flex items-center justify-center mb-5">
<IconifyIcon icon={activeSection.icon} className="text-2xl text-teal-600" style={{ strokeWidth: 1.5 }} />
</div>
<h2 className="text-2xl md:text-3xl font-medium text-gray-900 tracking-tight mb-4">{activeSection.title}</h2>
<p className="text-base md:text-lg text-gray-600 leading-relaxed">{activeSection.description}</p>
</div>
<div className="rounded-[2.2rem] p-4 md:p-5 bg-gradient-to-br from-white/75 to-white/35 border border-white/80 backdrop-blur-2xl shadow-[0_12px_40px_rgba(0,0,0,0.05)]">
<div className="space-y-3">
{activeSection.items.map((item, index) => {
const isOpen = openItem === index;
return (
<article key={item.question} className="rounded-2xl border border-white/90 bg-white/70 shadow-sm overflow-hidden">
<button
type="button"
className="w-full px-5 py-4 text-left flex items-center justify-between gap-4"
onClick={() => setOpenItem(isOpen ? -1 : index)}
>
<span className="text-base md:text-lg font-medium text-gray-900 leading-snug">{item.question}</span>
<IconifyIcon
icon={isOpen ? 'solar:minus-circle-linear' : 'solar:add-circle-linear'}
className={`text-2xl flex-shrink-0 transition-transform ${isOpen ? 'text-teal-600 rotate-180' : 'text-gray-500'}`}
style={{ strokeWidth: 1.5 }}
/>
</button>
<div className={`grid transition-all duration-300 ${isOpen ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'}`}>
<div className="overflow-hidden">
<p className="px-5 pb-5 text-sm md:text-base text-gray-600 leading-relaxed">{item.answer}</p>
</div>
</div>
</article>
);
})}
</div>
</div>
</div>
<div className="mt-10 rounded-[2rem] p-6 md:p-8 bg-gradient-to-br from-teal-400/15 via-indigo-400/10 to-cyan-400/15 border border-white/70 backdrop-blur-xl shadow-[0_10px_30px_rgba(0,0,0,0.04)] flex flex-col md:flex-row md:items-center md:justify-between gap-5">
<div>
<h3 className="text-xl md:text-2xl font-medium text-gray-900 tracking-tight mb-2">Har du stadig spørgsmål?</h3>
<p className="text-base text-gray-600">Hvis du ikke fandt det, du søgte, kan du kontakte os direkte.</p>
</div>
<a href="/kontakt" className="inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-2xl bg-gray-900 text-white border border-gray-800 hover:bg-gray-800 transition-all shadow-[0_8px_20px_rgba(17,24,39,0.2)]">
Kontakt os
<IconifyIcon icon="solar:arrow-right-linear" className="text-lg" style={{ strokeWidth: 1.5 }} />
</a>
</div>
</section>
</main>
<SiteFooter />
</div>
);
}