login-seiten neu designt, homepage neu designt. "so funktioniert's" ergänzt und Demo-Seite hinzugefügt. Paketansicht in mobile verbessert.
This commit is contained in:
423
resources/js/pages/marketing/HowItWorks.tsx
Normal file
423
resources/js/pages/marketing/HowItWorks.tsx
Normal file
@@ -0,0 +1,423 @@
|
||||
import React from 'react';
|
||||
import { Head, Link } from '@inertiajs/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MarketingLayout from '@/layouts/mainWebsite';
|
||||
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { CheckCircle2, Images, Sparkles, Users } from 'lucide-react';
|
||||
|
||||
type HeroStat = { value: string; label: string };
|
||||
type ExperienceStep = { title: string; description: string };
|
||||
type ExperienceGroup = {
|
||||
label: string;
|
||||
intro: string;
|
||||
steps: ExperienceStep[];
|
||||
callouts: string[];
|
||||
};
|
||||
type TimelineItem = { title: string; body: string; tips: string[] };
|
||||
type UseCase = {
|
||||
value: string;
|
||||
label: string;
|
||||
goal: string;
|
||||
recommendations: string[];
|
||||
ideas: string[];
|
||||
};
|
||||
type FaqItem = { question: string; answer: string };
|
||||
|
||||
const iconByUseCase: Record<string, React.ReactNode> = {
|
||||
wedding: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
|
||||
birthday: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
|
||||
corporate: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
|
||||
confirmation: <Sparkles className="h-6 w-6 text-pink-500" aria-hidden />,
|
||||
public: <Users className="h-6 w-6 text-pink-500" aria-hidden />,
|
||||
};
|
||||
|
||||
const HowItWorks: React.FC = () => {
|
||||
const { t } = useTranslation('marketing');
|
||||
const { localizedPath } = useLocalizedRoutes();
|
||||
|
||||
const hero = t('how_it_works_page.hero', { returnObjects: true }) as {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
primaryCta: string;
|
||||
secondaryCta: string;
|
||||
stats: HeroStat[];
|
||||
};
|
||||
|
||||
const experience = t('how_it_works_page.experience', { returnObjects: true }) as {
|
||||
host: ExperienceGroup;
|
||||
guest: ExperienceGroup;
|
||||
};
|
||||
|
||||
const pillars = t('how_it_works_page.pillars', { returnObjects: true }) as Array<{
|
||||
title: string;
|
||||
description: string;
|
||||
}>;
|
||||
|
||||
const timeline = t('how_it_works_page.timeline', { returnObjects: true }) as TimelineItem[];
|
||||
|
||||
const useCases = t('how_it_works_page.use_cases', { returnObjects: true }) as {
|
||||
title: string;
|
||||
description: string;
|
||||
tabs: UseCase[];
|
||||
};
|
||||
|
||||
const checklist = t('how_it_works_page.checklist', { returnObjects: true }) as {
|
||||
title: string;
|
||||
items: string[];
|
||||
cta: string;
|
||||
};
|
||||
|
||||
const faq = t('how_it_works_page.faq', { returnObjects: true }) as {
|
||||
title: string;
|
||||
items: FaqItem[];
|
||||
};
|
||||
|
||||
const support = t('how_it_works_page.support', { returnObjects: true }) as {
|
||||
title: string;
|
||||
description: string;
|
||||
cta: string;
|
||||
};
|
||||
|
||||
return (
|
||||
<MarketingLayout title={hero.title}>
|
||||
<Head title={hero.title} />
|
||||
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-pink-100 via-white to-white px-4 py-16 dark:from-pink-950/40 dark:via-gray-950 dark:to-gray-950">
|
||||
<div className="absolute -top-24 right-24 hidden h-64 w-64 rounded-full bg-pink-200/60 blur-3xl dark:bg-pink-900/50 lg:block" />
|
||||
<div className="container mx-auto relative z-10 flex max-w-6xl flex-col gap-12 lg:flex-row lg:items-center">
|
||||
<div className="flex-1 space-y-6">
|
||||
<Badge variant="outline" className="border-pink-400 text-pink-600 dark:border-pink-500 dark:text-pink-300">
|
||||
Fotospiel Flow
|
||||
</Badge>
|
||||
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-50 md:text-5xl">
|
||||
{hero.title}
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg text-gray-600 dark:text-gray-300">
|
||||
{hero.subtitle}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button asChild size="lg" className="bg-pink-500 hover:bg-pink-600">
|
||||
<Link href={localizedPath('/register')}>
|
||||
{hero.primaryCta}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild size="lg" variant="outline" className="border-pink-200 text-pink-600 hover:bg-pink-100 dark:border-pink-900 dark:text-pink-300 dark:hover:bg-pink-900/40">
|
||||
<Link href={localizedPath('/kontakt')}>
|
||||
{hero.secondaryCta}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
{hero.stats.map((stat) => (
|
||||
<Card key={stat.label} className="border-pink-100/70 shadow-none dark:border-pink-900/40">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-2xl font-semibold text-pink-600 dark:text-pink-300">
|
||||
{stat.value}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-sm text-gray-600 dark:text-gray-300">
|
||||
{stat.label}
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto px-4 py-16">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<Tabs defaultValue="host" className="w-full">
|
||||
<TabsList className="flex w-full justify-start gap-2 bg-pink-50/60 p-2 dark:bg-gray-900/60">
|
||||
<TabsTrigger value="host" className="flex-1 text-base">
|
||||
{experience.host.label}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="guest" className="flex-1 text-base">
|
||||
{experience.guest.label}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="host" className="mt-6">
|
||||
<ExperiencePanel data={experience.host} />
|
||||
</TabsContent>
|
||||
<TabsContent value="guest" className="mt-6">
|
||||
<ExperiencePanel data={experience.guest} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto px-4 py-16">
|
||||
<div className="mx-auto max-w-6xl text-center">
|
||||
<Badge variant="secondary" className="mb-4">Core Features</Badge>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-gray-50 md:text-4xl">
|
||||
{t('home.features_title', 'Warum Fotospiel?')}
|
||||
</h2>
|
||||
<p className="mt-3 text-lg text-gray-600 dark:text-gray-300">
|
||||
{t('home.hero_description')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mx-auto mt-10 grid max-w-6xl gap-6 md:grid-cols-2">
|
||||
{pillars.map((pillar) => (
|
||||
<Card key={pillar.title} className="border-gray-100 shadow-sm dark:border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-3 text-left text-xl">
|
||||
<Sparkles className="h-5 w-5 text-pink-500" aria-hidden />
|
||||
{pillar.title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-left text-gray-600 dark:text-gray-300">
|
||||
{pillar.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-gradient-to-br from-white via-pink-50/40 to-white px-4 py-16 dark:from-gray-950 dark:via-gray-900 dark:to-gray-950">
|
||||
<div className="container mx-auto max-w-5xl">
|
||||
<div className="mb-8 text-center">
|
||||
<Badge variant="outline" className="border-pink-400 text-pink-600 dark:border-pink-500 dark:text-pink-300">
|
||||
{t('how_it_works_page.timeline_title', 'Der Ablauf im Detail')}
|
||||
</Badge>
|
||||
<h2 className="mt-3 text-3xl font-bold text-gray-900 dark:text-gray-50">
|
||||
Ein klarer Fahrplan für dein Event
|
||||
</h2>
|
||||
</div>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{timeline.map((item, index) => (
|
||||
<AccordionItem key={item.title} value={`step-${index}`}>
|
||||
<AccordionTrigger className="text-left text-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="secondary" className="rounded-full">{index + 1}</Badge>
|
||||
<span>{item.title}</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{item.body}
|
||||
</p>
|
||||
{item.tips?.length ? (
|
||||
<div className="mt-4 rounded-lg border border-pink-100 bg-white p-4 text-sm text-gray-600 shadow-sm dark:border-pink-900/40 dark:bg-gray-900 dark:text-gray-300">
|
||||
<p className="mb-2 font-semibold text-pink-600 dark:text-pink-300">
|
||||
{t('marketing.actions.tips', 'Tipps')}
|
||||
</p>
|
||||
<ul className="space-y-1">
|
||||
{item.tips.map((tip) => (
|
||||
<li key={tip} className="flex items-start gap-2">
|
||||
<span className="mt-1 inline-flex h-1.5 w-1.5 rounded-full bg-pink-400" aria-hidden />
|
||||
<span>{tip}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto px-4 py-16">
|
||||
<div className="mx-auto max-w-5xl text-center">
|
||||
<Badge variant="secondary" className="mb-4">
|
||||
{useCases.title}
|
||||
</Badge>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-gray-50 md:text-4xl">
|
||||
{useCases.description}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="mx-auto mt-10 max-w-5xl">
|
||||
<Tabs defaultValue={useCases.tabs[0]?.value ?? ''}>
|
||||
<TabsList className="flex flex-wrap gap-2 bg-transparent p-0">
|
||||
{useCases.tabs.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
className="rounded-full border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 shadow-sm data-[state=active]:border-pink-500 data-[state=active]:bg-pink-50 data-[state=active]:text-pink-600 dark:border-gray-800 dark:text-gray-200 dark:data-[state=active]:border-pink-500 dark:data-[state=active]:bg-pink-900/40"
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{useCases.tabs.map((tab) => (
|
||||
<TabsContent key={tab.value} value={tab.value} className="mt-6">
|
||||
<Card className="border-gray-100 shadow-md dark:border-gray-800">
|
||||
<CardHeader className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-2xl">
|
||||
{tab.label}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-base text-gray-600 dark:text-gray-300">
|
||||
{tab.goal}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-pink-100 dark:bg-pink-900/40">
|
||||
{iconByUseCase[tab.value] ?? <Images className="h-6 w-6 text-pink-500" aria-hidden />}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-pink-600 dark:text-pink-300">
|
||||
{t('marketing.labels.recommendations', 'Empfehlungen')}
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-300">
|
||||
{tab.recommendations.map((item) => (
|
||||
<li key={item} className="flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 text-pink-500" aria-hidden />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-pink-600 dark:text-pink-300">
|
||||
{t('marketing.labels.challengeIdeas', 'Ideen für Challenges')}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{tab.ideas.map((idea) => (
|
||||
<Badge key={idea} variant="outline" className="border-pink-200 text-pink-600 dark:border-pink-900/40 dark:text-pink-300">
|
||||
{idea}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto px-4 py-16">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<Card className="border-gray-100 shadow-sm dark:border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle>{checklist.title}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('marketing.labels.prepHint', 'Alles, was du vor dem Event abhaken solltest.')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-3 text-gray-600 dark:text-gray-300">
|
||||
{checklist.items.map((item) => (
|
||||
<li key={item} className="flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-5 w-5 text-pink-500" aria-hidden />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button asChild className="mt-6 bg-pink-500 hover:bg-pink-600">
|
||||
<Link href={localizedPath('/register')}>
|
||||
{checklist.cta}
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto px-4 pb-16">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-gray-50">
|
||||
{faq.title}
|
||||
</h2>
|
||||
<Accordion type="single" collapsible className="mt-6">
|
||||
{faq.items.map((item, index) => (
|
||||
<AccordionItem key={item.question} value={`faq-${index}`}>
|
||||
<AccordionTrigger className="text-left text-lg">
|
||||
{item.question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="text-gray-600 dark:text-gray-300">
|
||||
{item.answer}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-pink-50/80 px-4 py-14 dark:bg-pink-950/30">
|
||||
<div className="container mx-auto max-w-4xl">
|
||||
<Alert className="border-pink-200 bg-white shadow-lg dark:border-pink-800 dark:bg-gray-950">
|
||||
<AlertTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{support.title}
|
||||
</AlertTitle>
|
||||
<AlertDescription className="mt-2 flex flex-col gap-4 text-gray-600 dark:text-gray-300 md:flex-row md:items-center md:justify-between">
|
||||
<span className="max-w-xl text-base">{support.description}</span>
|
||||
<Button asChild className="bg-pink-500 hover:bg-pink-600">
|
||||
<Link href={localizedPath('/kontakt')}>
|
||||
{support.cta}
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</section>
|
||||
</MarketingLayout>
|
||||
);
|
||||
};
|
||||
|
||||
const ExperiencePanel: React.FC<{ data: ExperienceGroup }> = ({ data }) => {
|
||||
const { t } = useTranslation('marketing');
|
||||
|
||||
return (
|
||||
<Card className="border-gray-100 shadow-md dark:border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl text-gray-900 dark:text-gray-50">
|
||||
{data.label}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-base text-gray-600 dark:text-gray-300">
|
||||
{data.intro}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{data.steps.map((step, index) => (
|
||||
<div key={step.title} className="rounded-lg border border-gray-100 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
||||
<div className="mb-3 inline-flex h-9 w-9 items-center justify-center rounded-full bg-pink-100 text-base font-semibold text-pink-600 dark:bg-pink-900/40 dark:text-pink-300">
|
||||
{index + 1}
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{data.callouts?.length ? (
|
||||
<div className="rounded-xl border border-pink-100 bg-pink-50/60 p-6 dark:border-pink-900/40 dark:bg-pink-950/30">
|
||||
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-pink-600 dark:text-pink-300">
|
||||
{t('how_it_works_page.labels.good_to_know', 'Gut zu wissen')}
|
||||
</p>
|
||||
<ul className="grid gap-3 md:grid-cols-3">
|
||||
{data.callouts.map((item) => (
|
||||
<li key={item} className="flex items-start gap-2 text-sm text-pink-700 dark:text-pink-200">
|
||||
<Sparkles className="mt-0.5 h-4 w-4" aria-hidden />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default HowItWorks;
|
||||
Reference in New Issue
Block a user