feat: Complete checkout overhaul with Stripe PaymentIntent integration and abandoned cart recovery

This commit is contained in:
Codex Agent
2025-10-07 22:25:03 +02:00
parent dd5545605c
commit aa8c6c67c5
38 changed files with 1848 additions and 878 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback } from 'react';
import { usePage } from '@inertiajs/react';
import { Link, router } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
@@ -13,7 +13,7 @@ import { Sun, Moon } from 'lucide-react';
import { cn } from '@/lib/utils';
const Header: React.FC = () => {
const { auth, locale } = usePage().props as any;
const { auth } = usePage().props as any;
const { t } = useTranslation('auth');
const { appearance, updateAppearance } = useAppearance();
const { localizedPath } = useLocalizedRoutes();
@@ -23,15 +23,29 @@ const Header: React.FC = () => {
updateAppearance(newAppearance);
};
const handleLanguageChange = (value: string) => {
router.post('/set-locale', { locale: value }, {
preserveState: true,
replace: true,
onSuccess: () => {
const handleLanguageChange = useCallback(async (value: string) => {
console.log('handleLanguageChange called with:', value);
try {
const response = await fetch('/set-locale', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
},
body: JSON.stringify({ locale: value }),
});
console.log('fetch response:', response.status);
if (response.ok) {
console.log('calling i18n.changeLanguage with:', value);
i18n.changeLanguage(value);
},
});
};
// Reload only the locale prop to update the page props
router.reload({ only: ['locale'] });
}
} catch (error) {
console.error('Failed to change locale:', error);
}
}, []);
const handleLogout = () => {
router.post('/logout');
@@ -45,18 +59,24 @@ const Header: React.FC = () => {
Fotospiel
</Link>
<nav className="flex space-x-8">
<Link href={localizedPath('/')} className="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white font-sans-marketing text-lg">
{t('header.home', 'Home')}
</Link>
<Link href={localizedPath('/packages')} className="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white font-sans-marketing text-lg">
{t('header.packages', 'Pakete')}
</Link>
<Link href={localizedPath('/blog')} className="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white font-sans-marketing text-lg">
{t('header.blog', 'Blog')}
</Link>
<Button asChild variant="ghost" className="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 transition-all duration-200">
<Link href={localizedPath('/')}>
{t('header.home', 'Home')}
</Link>
</Button>
<Button asChild variant="ghost" className="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 transition-all duration-200">
<Link href={localizedPath('/packages')}>
{t('header.packages', 'Pakete')}
</Link>
</Button>
<Button asChild variant="ghost" className="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 transition-all duration-200">
<Link href={localizedPath('/blog')}>
{t('header.blog', 'Blog')}
</Link>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white font-sans-marketing text-lg">
<Button variant="ghost" className="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 transition-all duration-200">
Anlässe
</Button>
</DropdownMenuTrigger>
@@ -78,9 +98,11 @@ const Header: React.FC = () => {
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Link href={localizedPath('/kontakt')} className="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white font-sans-marketing text-lg">
{t('header.contact', 'Kontakt')}
</Link>
<Button asChild variant="ghost" className="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 transition-all duration-200">
<Link href={localizedPath('/kontakt')}>
{t('header.contact', 'Kontakt')}
</Link>
</Button>
</nav>
<div className="flex items-center space-x-4">
<Button
@@ -93,7 +115,7 @@ const Header: React.FC = () => {
<Moon className={cn("h-4 w-4", appearance !== "dark" && "hidden")} />
<span className="sr-only">Theme Toggle</span>
</Button>
<Select value={locale} onValueChange={handleLanguageChange}>
<Select value={i18n.language || 'de'} onValueChange={handleLanguageChange}>
<SelectTrigger className="w-[70px] h-8">
<SelectValue placeholder="DE" />
</SelectTrigger>
@@ -140,7 +162,7 @@ const Header: React.FC = () => {
<>
<Link
href={localizedPath('/login')}
className="text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
className="text-gray-700 hover:text-pink-600 dark:text-gray-300 dark:hover:text-pink-400 font-medium transition-colors duration-200"
>
{t('header.login')}
</Link>

View File

@@ -8,6 +8,7 @@ interface Step {
id: string
title: string
description: string
details?: string
}
interface StepsProps {
@@ -33,10 +34,18 @@ const Steps = React.forwardRef<HTMLDivElement, StepsProps>(
{index + 1}
</div>
<div className="mt-2 text-xs font-medium text-center">
<p className={cn(index === currentStep ? "text-blue-600" : "text-gray-500")}>
<p className={cn(
"font-semibold",
index === currentStep ? "text-blue-600 dark:text-blue-400" : "text-gray-500 dark:text-gray-400"
)}>
{step.title}
</p>
<p className="text-xs text-gray-400">{step.description}</p>
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1">{step.description}</p>
{step.details && index === currentStep && (
<p className="text-xs text-blue-500 dark:text-blue-300 mt-1 font-medium">
{step.details}
</p>
)}
</div>
{index < steps.length - 1 && (
<div className="flex-1 h-px bg-gray-200 dark:bg-gray-700 mx-2">