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:
Codex Agent
2025-11-03 11:47:19 +01:00
parent 073b51e2d5
commit 20eda6b4f8
23 changed files with 2481 additions and 587 deletions

View File

@@ -8,11 +8,11 @@ import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
import { Separator } from '@/components/ui/separator';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
import { Sun, Moon, Menu, X, ChevronRight } from 'lucide-react';
import { Sun, Moon, Menu, X, Languages, UserRound } from 'lucide-react';
import { cn } from '@/lib/utils';
import {
NavigationMenu,
@@ -31,10 +31,15 @@ const Header: React.FC = () => {
const { localizedPath } = useLocalizedRoutes();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const toggleTheme = () => {
const newAppearance = appearance === 'dark' ? 'light' : 'dark';
updateAppearance(newAppearance);
const setTheme = useCallback((mode: 'light' | 'dark') => {
if (appearance !== mode) {
updateAppearance(mode);
}
setMobileMenuOpen(false);
}, [appearance, updateAppearance]);
const toggleTheme = () => {
setTheme(appearance === 'dark' ? 'light' : 'dark');
};
const handleLanguageChange = useCallback(async (value: string) => {
@@ -68,49 +73,61 @@ const Header: React.FC = () => {
});
};
const navItems = useMemo(() => ([
{
key: 'home',
label: t('header.home', 'Home'),
href: localizedPath('/'),
},
{
key: 'packages',
label: t('header.packages', 'Pakete'),
href: localizedPath('/packages'),
},
{
key: 'blog',
label: t('header.blog', 'Blog'),
href: localizedPath('/blog'),
},
{
key: 'occasions',
label: t('header.occasions.label', 'Anlässe'),
children: [
{
key: 'wedding',
label: t('header.occasions.wedding', 'Hochzeit'),
href: localizedPath('/anlaesse/hochzeit'),
},
{
key: 'birthday',
label: t('header.occasions.birthday', 'Geburtstag'),
href: localizedPath('/anlaesse/geburtstag'),
},
{
key: 'corporate',
label: t('header.occasions.corporate', 'Firmenevent'),
href: localizedPath('/anlaesse/firmenevent'),
},
],
},
{
key: 'contact',
label: t('header.contact', 'Kontakt'),
href: localizedPath('/kontakt'),
},
]), [localizedPath, t]);
const ctaHref = localizedPath('/demo');
const navItems = useMemo(() => {
const homeHref = localizedPath('/');
const howItWorksHref = localizedPath('/so-funktionierts');
return [
{
key: 'howItWorks',
label: t('header.how_it_works', "So geht's"),
href: howItWorksHref,
},
{
key: 'packages',
label: t('header.packages', 'Pakete'),
href: localizedPath('/packages'),
},
{
key: 'occasions',
label: t('header.occasions.label', 'Anlässe'),
children: [
{
key: 'wedding',
label: t('header.occasions.wedding', 'Hochzeiten'),
href: localizedPath('/anlaesse/hochzeit'),
},
{
key: 'birthday',
label: t('header.occasions.birthday', 'Geburtstage'),
href: localizedPath('/anlaesse/geburtstag'),
},
{
key: 'corporate',
label: t('header.occasions.corporate', 'Firmenfeiern'),
href: localizedPath('/anlaesse/firmenevent'),
},
{
key: 'confirmation',
label: t('header.occasions.confirmation', 'Konfirmation/Jugendweihe'),
href: localizedPath('/anlaesse/konfirmation'),
},
],
},
{
key: 'blog',
label: t('header.blog', 'Blog'),
href: localizedPath('/blog'),
},
{
key: 'contact',
label: t('header.contact', 'Kontakt'),
href: localizedPath('/kontakt'),
},
];
}, [localizedPath, t]);
const handleNavSelect = useCallback(() => setMobileMenuOpen(false), []);
@@ -125,25 +142,27 @@ const Header: React.FC = () => {
</span>
</Link>
<NavigationMenu className="hidden lg:flex flex-1 justify-center" viewport={false}>
<NavigationMenuList className="gap-2">
<NavigationMenuList className="gap-1.5">
{navItems.map((item) => (
<NavigationMenuItem key={item.key}>
{item.children ? (
<>
<NavigationMenuTrigger className="bg-transparent text-gray-700 hover:text-pink-600 hover:bg-pink-50 dark:text-gray-300 dark:hover:text-pink-400 dark:hover:bg-pink-950/20 font-sans-marketing !text-lg font-medium">
<NavigationMenuTrigger className="bg-transparent px-3 py-1.5 !text-lg font-medium text-gray-700 hover:text-pink-600 hover:bg-pink-50 dark:text-gray-300 dark:hover:text-pink-400 dark:hover:bg-pink-950/20 font-sans-marketing">
{item.label}
</NavigationMenuTrigger>
<NavigationMenuContent className="min-w-[220px] rounded-md border bg-popover p-3 shadow-lg">
<NavigationMenuContent className="min-w-[260px] rounded-md border bg-popover p-3 shadow-lg">
<ul className="flex flex-col gap-1">
{item.children.map((child) => (
<li key={child.key}>
<NavigationMenuLink asChild>
<Link
href={child.href}
className="flex items-center justify-between rounded-md px-3 py-2 !text-lg font-medium text-gray-700 transition hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-900/60 font-sans-marketing"
className="flex w-full items-center rounded-md px-3 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-900/60 font-sans-marketing"
>
{child.label}
<ChevronRight className="h-4 w-4" />
<span aria-hidden className="mr-2 flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground">
</span>
<span className="whitespace-nowrap">{child.label}</span>
</Link>
</NavigationMenuLink>
</li>
@@ -157,7 +176,7 @@ const Header: React.FC = () => {
href={item.href}
className={cn(
navigationMenuTriggerStyle(),
"bg-transparent !text-lg font-medium text-gray-700 hover:bg-pink-50 hover:text-pink-600 dark:text-gray-300 dark:hover:bg-pink-950/20 dark:hover:text-pink-400 font-sans-marketing"
"bg-transparent px-3 py-1.5 !text-lg font-medium text-gray-700 hover:bg-pink-50 hover:text-pink-600 dark:text-gray-300 dark:hover:bg-pink-950/20 dark:hover:text-pink-400 font-sans-marketing"
)}
>
{item.label}
@@ -168,26 +187,49 @@ const Header: React.FC = () => {
))}
</NavigationMenuList>
</NavigationMenu>
<div className="hidden lg:flex items-center space-x-4">
<Button
variant="ghost"
size="icon"
onClick={toggleTheme}
className="h-8 w-8"
>
<Sun className={cn("h-4 w-4", appearance === "dark" && "hidden")} />
<Moon className={cn("h-4 w-4", appearance !== "dark" && "hidden")} />
<span className="sr-only">Theme Toggle</span>
<div className="hidden lg:flex items-center space-x-2">
<Button asChild size="sm" className="bg-[#FF5F87] hover:bg-[#ff4674] text-white shadow-md shadow-rose-500/20">
<Link href={ctaHref} className="font-sans-marketing font-semibold px-3">
{t('header.cta', 'Jetzt ausprobieren')}
</Link>
</Button>
<Select value={i18n.language || 'de'} onValueChange={handleLanguageChange}>
<SelectTrigger className="w-[70px] h-8">
<SelectValue placeholder={t('common.ui.language_select')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="de">DE</SelectItem>
<SelectItem value="en">EN</SelectItem>
</SelectContent>
</Select>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
aria-label={t('header.utility', 'Darstellung und Sprache öffnen')}
>
<Languages className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="end" forceMount>
<DropdownMenuLabel className="font-sans-marketing text-xs uppercase tracking-wide text-muted-foreground">
{t('header.appearance', 'Darstellung')}
</DropdownMenuLabel>
<DropdownMenuItem onClick={() => setTheme('light')} className="font-sans-marketing">
<Sun className="mr-2 h-4 w-4" />
{t('header.appearance_light', 'Hell')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')} className="font-sans-marketing">
<Moon className="mr-2 h-4 w-4" />
{t('header.appearance_dark', 'Dunkel')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel className="font-sans-marketing text-xs uppercase tracking-wide text-muted-foreground">
{t('header.language', 'Sprache')}
</DropdownMenuLabel>
<DropdownMenuRadioGroup value={i18n.language || 'de'} onValueChange={handleLanguageChange}>
<DropdownMenuRadioItem value="de" className="font-sans-marketing">
Deutsch
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="en" className="font-sans-marketing">
English
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
{auth.user ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -223,14 +265,16 @@ const Header: React.FC = () => {
</DropdownMenuContent>
</DropdownMenu>
) : (
<>
<Button asChild variant="ghost" size="icon" className="h-8 w-8">
<Link
href={localizedPath('/login')}
className="text-gray-700 hover:text-pink-600 dark:text-gray-300 dark:hover:text-pink-400 font-medium transition-colors duration-200 font-sans-marketing"
className="flex items-center justify-center text-gray-700 hover:text-pink-600 dark:text-gray-300 dark:hover:text-pink-400"
aria-label={t('header.login')}
>
{t('header.login')}
<UserRound className="h-4 w-4" />
<span className="sr-only">{t('header.login')}</span>
</Link>
</>
</Button>
)}
</div>
<div className="flex items-center lg:hidden">
@@ -279,11 +323,11 @@ const Header: React.FC = () => {
<SheetClose asChild key={child.key}>
<Link
href={child.href}
className="flex items-center justify-between rounded-md border border-transparent px-3 py-2 text-base font-medium text-gray-700 transition hover:border-gray-200 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-900/60 font-sans-marketing"
className="flex w-full items-center rounded-md border border-transparent px-3 py-2 text-sm font-medium text-gray-700 transition hover:border-gray-200 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-900/60 font-sans-marketing"
onClick={handleNavSelect}
>
<span aria-hidden className="mr-2 text-muted-foreground"></span>
<span>{child.label}</span>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
</Link>
</SheetClose>
))}
@@ -305,6 +349,15 @@ const Header: React.FC = () => {
</nav>
<Separator />
<div className="flex flex-col gap-4">
<SheetClose asChild>
<Link
href={ctaHref}
className="rounded-full bg-[#FF5F87] px-4 py-3 text-center text-base font-semibold text-white shadow-md shadow-rose-500/20 transition hover:bg-[#ff4674] font-sans-marketing"
onClick={handleNavSelect}
>
{t('header.cta', 'Jetzt ausprobieren')}
</Link>
</SheetClose>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-muted-foreground">Darstellung</span>
<Button