Fix auth form errors and redirects: Add React keys/useEffects for error rendering and scroll, Inertia::location in controllers for SPA navigation, extend RegistrationTest and add E2E. Update docs (changes/2025-10-02-registration-fixes.md, prp/13-backend-authentication.md). Add new UI components (accordion, carousel, progress, table, tabs), marketing/legal pages (Blog, Kontakt, Datenschutz, etc.), fonts, user migration (remove_name), views/css/package updates, seeders/factories.

This commit is contained in:
Codex Agent
2025-10-02 11:40:48 +02:00
parent 1945f664c6
commit 7475210893
101 changed files with 3406 additions and 376 deletions

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { Link } from '@inertiajs/react';
const MarketingFooter: React.FC = () => {
return (
<footer className="bg-gray-800 text-white py-8 px-4">
<div className="container mx-auto text-center">
<p>&copy; 2025 Fotospiel GmbH. Alle Rechte vorbehalten.</p>
<div className="mt-4 space-x-4">
<Link href="/impressum" className="hover:text-[#FFB6C1]">
Impressum
</Link>
<Link href="/datenschutz" className="hover:text-[#FFB6C1]">
Datenschutz
</Link>
<Link href="/kontakt" className="hover:text-[#FFB6C1]">
Kontakt
</Link>
</div>
</div>
</footer>
);
};
export default MarketingFooter;

View File

@@ -0,0 +1,79 @@
import React, { useState } from 'react';
import { Link } from '@inertiajs/react';
import { usePage } from '@inertiajs/react';
const MarketingHeader: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
const { url } = usePage();
const occasions = [
{ href: '/de/occasions/weddings', label: 'Hochzeiten' },
{ href: '/de/occasions/birthdays', label: 'Geburtstage' },
{ href: '/de/occasions/corporate-events', label: 'Firmenevents' },
{ href: '/de/occasions/family-celebrations', label: 'Familienfeiern' },
];
return (
<header className="bg-white shadow-md sticky top-0 z-50">
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center space-x-2">
<Link href="/" className="text-2xl font-bold text-gray-900">
Die Fotospiel.App
</Link>
<svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<nav className="hidden md:flex space-x-6 items-center">
<Link href="/#how-it-works" className="text-gray-600 hover:text-gray-900">
So funktioniert es
</Link>
<Link href="/#features" className="text-gray-600 hover:text-gray-900">
Features
</Link>
<div className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="text-gray-600 hover:text-gray-900"
>
Anlässe
</button>
{isOpen && (
<div className="absolute top-full left-0 mt-2 bg-white border rounded shadow-lg z-10">
{occasions.map((occasion) => (
<Link
key={occasion.href}
href={occasion.href}
className="block px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-50 transition"
onClick={() => setIsOpen(false)}
>
{occasion.label}
</Link>
))}
</div>
)}
</div>
<Link href="/blog" className="text-gray-600 hover:text-gray-900">
Blog
</Link>
<Link href="/packages" className="text-gray-600 hover:text-gray-900">
Packages
</Link>
<Link href="/kontakt" className="text-gray-600 hover:text-gray-900">
Kontakt
</Link>
<Link
href="/packages"
className="bg-[#FFB6C1] text-white px-6 py-2 rounded-full font-semibold hover:bg-[#FF69B4] transition"
>
Packages entdecken
</Link>
</nav>
<button className="md:hidden text-gray-600"></button>
</div>
</header>
);
};
export default MarketingHeader;

View File

@@ -0,0 +1,55 @@
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,144 @@
"use client"
import * as React from "react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import Autoplay from "embla-carousel-autoplay"
import useEmblaCarousel from "embla-carousel-react"
interface CarouselApi {
slideNodes(): HTMLElement[]
on(event: string, listener: (...args: any[]) => void): void
scrollPrev(): void
scrollNext(): void
reInit(): void
}
const CarouselContext = React.createContext<CarouselApi | null>(null)
interface CarouselProps {
opts?: {
align?: "start" | "center" | "end"
loop?: boolean
}
plugins?: any[]
setApi?: (api: CarouselApi) => void
[key: string]: any
}
const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
({ opts, plugins = [Autoplay()], setApi, className, children, ...props }, ref) => {
const [api, setApiInternal] = React.useState<CarouselApi | null>(null)
const [current, setCurrent] = React.useState(0)
const [count, setCount] = React.useState(0)
const [emblaRef] = useEmblaCarousel(opts, plugins)
React.useEffect(() => {
if (!api) {
return
}
setCount(api.slideNodes().length)
api.on("reInit", setCount)
api.on("slideChanged", ({ slide }: { slide: number }) => setCurrent(slide))
setApi?.(api)
}, [api, setApi])
return (
<CarouselContext.Provider value={api}>
<div
ref={ref}
className={cn(
"relative w-full",
className
)}
{...props}
>
<div
className="overflow-hidden"
ref={emblaRef}
>
<div className="flex">{children}</div>
</div>
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
interface CarouselContentProps {
children: React.ReactNode
className?: string
}
const CarouselContent = React.forwardRef<HTMLDivElement, CarouselContentProps>(
({ children, className }, ref) => (
<div ref={ref} className={cn("flex", className)}>
{children}
</div>
)
)
CarouselContent.displayName = "CarouselContent"
interface CarouselItemProps {
children: React.ReactNode
className?: string
}
const CarouselItem = React.forwardRef<HTMLDivElement, CarouselItemProps>(
({ children, className }, ref) => (
<div
ref={ref}
className={cn("min-w-0 shrink-0 grow-0 basis-full pl-4 md:pl-6", className)}
>
{children}
</div>
)
)
CarouselItem.displayName = "CarouselItem"
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement>
>(({ className, ...props }, ref) => (
<Button
ref={ref}
variant="outline"
size="icon"
className={cn(
"absolute -left-1 top-1/2 -translate-y-1/2 rounded-full h-8 w-8 bg-background/80 backdrop-blur-sm hover:bg-background/90 disabled:pointer-events-none disabled:opacity-50",
className
)}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
))
CarouselPrevious.displayName = "CarouselPrevious"
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement>
>(({ className, ...props }, ref) => (
<Button
ref={ref}
variant="outline"
size="icon"
className={cn(
"absolute -right-1 top-1/2 -translate-y-1/2 rounded-full h-8 w-8 bg-background/80 backdrop-blur-sm hover:bg-background/90 disabled:pointer-events-none disabled:opacity-50",
className
)}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
))
CarouselNext.displayName = "CarouselNext"
export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext }

View File

@@ -0,0 +1,26 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@@ -0,0 +1,120 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@@ -0,0 +1,53 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }