Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
- Restructure to pnpm monorepo (site moved to apps/web) - Integrate @mintel/tsconfig, @mintel/eslint-config, @mintel/husky-config - Implement Docker service architecture (Varnish, Directus, Gatekeeper) - Setup environment-aware Gitea Actions deployment
169 lines
7.3 KiB
TypeScript
169 lines
7.3 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { FormState } from '../types';
|
|
import { Checkbox } from '../components/Checkbox';
|
|
import { RepeatableList } from '../components/RepeatableList';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { Minus, Plus, FileText, ListPlus, HelpCircle, ArrowRight } from 'lucide-react';
|
|
import { Input } from '../components/Input';
|
|
|
|
interface BaseStepProps {
|
|
state: FormState;
|
|
updateState: (updates: Partial<FormState>) => void;
|
|
toggleItem: (list: string[], id: string) => string[];
|
|
}
|
|
|
|
export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) {
|
|
const toggleDontKnow = (id: string) => {
|
|
const current = state.dontKnows || [];
|
|
if (current.includes(id)) {
|
|
updateState({ dontKnows: current.filter(i => i !== id) });
|
|
} else {
|
|
updateState({ dontKnows: [...current, id] });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-16">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="space-y-8"
|
|
>
|
|
<div className="relative">
|
|
<Input
|
|
label="Thema der Website"
|
|
placeholder="z.B. Portfolio für Architektur, Onlineshop für Bio-Tee..."
|
|
value={state.websiteTopic}
|
|
onChange={(e) => updateState({ websiteTopic: e.target.value })}
|
|
/>
|
|
<div className="absolute top-0 right-4 px-2 py-1 bg-slate-100 text-slate-500 text-[10px] font-bold uppercase tracking-wider rounded">
|
|
Essenziell
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
<div className="space-y-8">
|
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-14 h-14 bg-slate-900 text-white rounded-2xl flex items-center justify-center shadow-lg shadow-slate-200">
|
|
<FileText size={28} />
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center gap-3">
|
|
<h4 className="text-3xl font-bold text-slate-900 tracking-tight">Die Seitenstruktur</h4>
|
|
<span className="px-2 py-1 bg-slate-100 text-slate-500 text-[10px] font-bold uppercase tracking-wider rounded">Essenziell</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-slate-400 mt-1">
|
|
<HelpCircle size={14} className="shrink-0" />
|
|
<span className="text-base">Wählen Sie die Bausteine Ihrer neuen Website.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<motion.button
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
type="button"
|
|
onClick={() => toggleDontKnow('pages')}
|
|
className={`px-6 py-3 rounded-full text-sm font-bold transition-all whitespace-nowrap shrink-0 ${
|
|
state.dontKnows?.includes('pages') ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
Ich weiß es nicht
|
|
</motion.button>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{[
|
|
{ id: 'Home', label: 'Startseite', desc: 'Der erste Eindruck Ihrer Marke.' },
|
|
{ id: 'About', label: 'Über uns', desc: 'Ihre Geschichte und Ihr Team.' },
|
|
{ id: 'Services', label: 'Leistungen', desc: 'Übersicht Ihres Angebots.' },
|
|
{ id: 'Contact', label: 'Kontakt', desc: 'Anlaufstelle für Ihre Kunden.' },
|
|
{ id: 'Landing', label: 'Landingpage', desc: 'Optimiert für Marketing-Kampagnen.' },
|
|
{ id: 'Legal', label: 'Rechtliches', desc: 'Impressum & Datenschutz.' },
|
|
].map((opt, index) => (
|
|
<motion.div
|
|
key={opt.id}
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: index * 0.05 }}
|
|
>
|
|
<Checkbox
|
|
label={opt.label} desc={opt.desc}
|
|
checked={state.selectedPages.includes(opt.id)}
|
|
onChange={() => updateState({ selectedPages: toggleItem(state.selectedPages, opt.id) })}
|
|
/>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-12">
|
|
<div className="space-y-8">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-slate-50 rounded-2xl flex items-center justify-center text-black">
|
|
<ListPlus size={24} />
|
|
</div>
|
|
<h4 className="text-2xl font-bold text-slate-900">Weitere individuelle Seiten?</h4>
|
|
</div>
|
|
<RepeatableList
|
|
items={state.otherPages}
|
|
onAdd={(v) => updateState({ otherPages: [...state.otherPages, v] })}
|
|
onRemove={(i) => updateState({ otherPages: state.otherPages.filter((_, idx) => idx !== i) })}
|
|
placeholder="z.B. Karriere, FAQ, Team-Detail..."
|
|
/>
|
|
</div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ delay: 0.4 }}
|
|
className="p-10 bg-slate-900 text-white rounded-[3rem] space-y-6 shadow-2xl shadow-slate-200 relative overflow-hidden group"
|
|
>
|
|
<div className="absolute top-0 right-0 p-8 opacity-10 group-hover:opacity-20 transition-opacity">
|
|
<ListPlus size={120} />
|
|
</div>
|
|
|
|
<div className="flex flex-col md:flex-row justify-between items-center gap-8 relative z-10">
|
|
<div>
|
|
<h4 className="text-2xl font-bold text-white">Noch mehr Seiten?</h4>
|
|
<p className="text-lg text-slate-400 mt-1">Falls Sie die Namen noch nicht wissen, aber die Menge schätzen können.</p>
|
|
</div>
|
|
<div className="flex items-center gap-8">
|
|
<motion.button
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
type="button"
|
|
onClick={() => updateState({ otherPagesCount: Math.max(0, state.otherPagesCount - 1) })}
|
|
className="w-16 h-16 rounded-full bg-white/10 border border-white/10 flex items-center justify-center hover:bg-white/20 transition-colors focus:outline-none"
|
|
>
|
|
<Minus size={28} />
|
|
</motion.button>
|
|
<AnimatePresence mode="wait">
|
|
<motion.span
|
|
key={state.otherPagesCount}
|
|
initial={{ opacity: 0, scale: 0.5 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
exit={{ opacity: 0, scale: 1.5 }}
|
|
className="text-6xl font-bold w-16 text-center"
|
|
>
|
|
{state.otherPagesCount}
|
|
</motion.span>
|
|
</AnimatePresence>
|
|
<motion.button
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
type="button"
|
|
onClick={() => updateState({ otherPagesCount: state.otherPagesCount + 1 })}
|
|
className="w-16 h-16 rounded-full bg-white/10 border border-white/10 flex items-center justify-center hover:bg-white/20 transition-colors focus:outline-none"
|
|
>
|
|
<Plus size={28} />
|
|
</motion.button>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|