import React, { useState, useEffect, useMemo, useRef, useLayoutEffect } from 'react'; import { Link } from '@inertiajs/react'; import { useTranslation } from 'react-i18next'; import type { TFunction } from 'i18next'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Sheet, SheetContent } from '@/components/ui/sheet'; import { cn } from '@/lib/utils'; import MarketingLayout from '@/layouts/mainWebsite'; import { useAnalytics } from '@/hooks/useAnalytics'; import { useCtaExperiment } from '@/hooks/useCtaExperiment'; import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes'; import { ArrowRight, Check, Shield, Star, Sparkles } from 'lucide-react'; interface Package { id: number; name: string; slug: string; description: string; description_breakdown: DescriptionEntry[]; gallery_duration_label?: string; price: number; events: number | null; features: string[]; max_events_per_year?: number | null; limits?: { max_photos?: number; max_guests?: number; max_tenants?: number; max_events_per_year?: number; gallery_days?: number; }; watermark_allowed?: boolean; branding_allowed?: boolean; } const sortPackagesByPrice = (packages: Package[]): Package[] => [...packages].sort((a, b) => Number(a.price ?? 0) - Number(b.price ?? 0)); interface PackageComparisonProps { packages: Package[]; variant: 'endcustomer' | 'reseller'; } function PackageComparison({ packages, variant }: PackageComparisonProps) { const { t } = useTranslation('marketing'); const { t: tCommon } = useTranslation('common'); if (packages.length === 0) { return null; } const formatPrice = (pkg: Package) => pkg.price === 0 ? t('packages.free') : `${pkg.price.toLocaleString()} ${t('packages.currency.euro')}`; const limits = variant === 'endcustomer' ? [ { key: 'price', label: t('packages.price'), value: (pkg: Package) => `${formatPrice(pkg)} / ${t('packages.billing_per_event')}`, }, { key: 'max_photos', label: t('packages.max_photos_label'), value: (pkg: Package) => pkg.limits?.max_photos?.toLocaleString() ?? tCommon('unlimited'), }, { key: 'max_guests', label: t('packages.max_guests_label'), value: (pkg: Package) => pkg.limits?.max_guests?.toLocaleString() ?? tCommon('unlimited'), }, { key: 'gallery_days', label: t('packages.gallery_days_label'), value: (pkg: Package) => pkg.gallery_duration_label ?? pkg.limits?.gallery_days?.toLocaleString() ?? tCommon('unlimited'), }, ] : [ { key: 'price', label: t('packages.price'), value: (pkg: Package) => `${formatPrice(pkg)} / ${t('packages.billing_per_year')}`, }, { key: 'max_tenants', label: t('packages.max_tenants'), value: (pkg: Package) => pkg.limits?.max_tenants?.toLocaleString() ?? tCommon('unlimited'), }, { key: 'max_events_per_year', label: t('packages.max_events_year'), value: (pkg: Package) => pkg.limits?.max_events_per_year?.toLocaleString() ?? tCommon('unlimited'), }, ]; const features = [ { key: 'watermark', label: t('packages.watermark_label'), value: (pkg: Package) => pkg.watermark_allowed === false ? t('packages.no_watermark') : t('packages.feature_watermark'), }, { key: 'branding', label: t('packages.feature_custom_branding'), value: (pkg: Package) => (pkg.branding_allowed ? t('packages.available') : t('packages.not_available')), }, { key: 'support', label: t('packages.feature_support'), value: (pkg: Package) => pkg.features.includes('priority_support') ? t('packages.priority_support') : t('packages.standard_support'), }, ]; return (

{t('packages.comparison_title')}

{t('packages.comparison_subtitle')}

{t('packages.comparison_limits')} {t('packages.comparison_features')} {t('packages.feature')} {packages.map((pkg) => ( {pkg.name} ))} {limits.map((row) => ( {row.label} {packages.map((pkg) => ( {row.value(pkg)} ))} ))}
{t('packages.feature')} {packages.map((pkg) => ( {pkg.name} ))} {features.map((row) => ( {row.label} {packages.map((pkg) => ( {row.value(pkg)} ))} ))}
); } type DescriptionEntry = { title?: string | null; value: string; }; interface PackagesProps { endcustomerPackages: Package[]; resellerPackages: Package[]; } const Packages: React.FC = ({ endcustomerPackages, resellerPackages }) => { const [open, setOpen] = useState(false); const [selectedPackage, setSelectedPackage] = useState(null); const [isMobile, setIsMobile] = useState(false); const dialogScrollRef = useRef(null); const dialogHeadingRef = useRef(null); const mobileEndcustomerRef = useRef(null); const mobileResellerRef = useRef(null); const { localizedPath } = useLocalizedRoutes(); const { t } = useTranslation('marketing'); const { t: tCommon } = useTranslation('common'); const { variant: packagesHeroVariant, trackClick: trackPackagesHeroClick, } = useCtaExperiment('packages_hero_cta'); useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const packageId = urlParams.get('package_id'); if (packageId) { const id = parseInt(packageId); const pkg = [...endcustomerPackages, ...resellerPackages].find(p => p.id === id); if (pkg) { setSelectedPackage(pkg); setOpen(true); setCurrentStep('overview'); } } }, [endcustomerPackages, resellerPackages]); useLayoutEffect(() => { if (open && dialogScrollRef.current) { dialogScrollRef.current.scrollTo({ top: 0 }); } }, [open, selectedPackage]); const highlightEndcustomerId = useMemo( () => selectHighlightPackageId(endcustomerPackages), [endcustomerPackages], ); const highlightResellerId = useMemo( () => selectHighlightPackageId(resellerPackages), [resellerPackages], ); const orderedEndcustomerPackages = useMemo( () => sortPackagesByPrice(endcustomerPackages), [endcustomerPackages], ); const orderedResellerPackages = useMemo( () => sortPackagesByPrice(resellerPackages), [resellerPackages], ); useEffect(() => { if (typeof window === 'undefined') { return; } const media = window.matchMedia('(max-width: 768px)'); const update = () => setIsMobile(media.matches); update(); if (media.addEventListener) { media.addEventListener('change', update); } else { media.addListener(update); } return () => { if (media.removeEventListener) { media.removeEventListener('change', update); } else { media.removeListener(update); } }; }, []); const scrollMobileListToHighlight = ( container: HTMLDivElement | null, packages: Package[], highlightId: number | null, ) => { if (!container || !highlightId) { return; } const index = packages.findIndex((pkg) => pkg.id === highlightId); if (index < 0) { return; } const child = container.children[index] as HTMLElement | undefined; if (!child) { return; } const targetLeft = child.offsetLeft - container.clientWidth / 2 + child.clientWidth / 2; container.scrollTo({ left: Math.max(targetLeft, 0), behavior: 'smooth' }); }; useLayoutEffect(() => { scrollMobileListToHighlight(mobileEndcustomerRef.current, orderedEndcustomerPackages, highlightEndcustomerId); }, [orderedEndcustomerPackages, highlightEndcustomerId]); useLayoutEffect(() => { scrollMobileListToHighlight(mobileResellerRef.current, orderedResellerPackages, highlightResellerId); }, [orderedResellerPackages, highlightResellerId]); const testimonials = [ { name: tCommon('testimonials.anna.name'), text: t('packages.testimonials.anna'), rating: 5 }, { name: tCommon('testimonials.max.name'), text: t('packages.testimonials.max'), rating: 5 }, { name: tCommon('testimonials.lisa.name'), text: t('packages.testimonials.lisa'), rating: 5 }, ]; const renderDetailBody = (wrapperClass: string) => { if (!selectedPackage) { return null; } return (

{selectedVariant === 'reseller' ? t('packages.subscription') : t('packages.one_time')}

{selectedPackage.name}

{selectedPackage.description}

{ handleCtaClick(selectedPackage, selectedVariant); localStorage.setItem('preferred_package', JSON.stringify(selectedPackage)); }} t={t} tCommon={tCommon} testimonials={testimonials} close={() => setOpen(false)} />
); }; function selectHighlightPackageId(packages: Package[]): number | null { const count = packages.length; if (count <= 1) { return null; } const sortedByPrice = [...packages].sort((a, b) => a.price - b.price); if (count === 2) { return sortedByPrice[1]?.id ?? null; } if (count === 3) { return sortedByPrice[1]?.id ?? null; } return sortedByPrice[count - 2]?.id ?? null; } function isHighlightedPackage(pkg: Package, variant: 'endcustomer' | 'reseller') { return variant === 'reseller' ? pkg.id === highlightResellerId : pkg.id === highlightEndcustomerId; } const selectedVariant = useMemo<'endcustomer' | 'reseller'>(() => { if (!selectedPackage) return 'endcustomer'; return resellerPackages.some((pkg) => pkg.id === selectedPackage.id) ? 'reseller' : 'endcustomer'; }, [selectedPackage, resellerPackages]); const selectedHighlight = selectedPackage ? isHighlightedPackage(selectedPackage, selectedVariant) : false; const purchaseUrl = selectedPackage ? `/purchase-wizard/${selectedPackage.id}` : '#'; const { trackEvent } = useAnalytics(); const handleCardClick = (pkg: Package, variant: 'endcustomer' | 'reseller') => { trackEvent({ category: 'marketing_packages', action: 'open_dialog', name: `${variant}:${pkg.name}`, value: pkg.price, }); setSelectedPackage(pkg); setOpen(true); }; const handleCtaClick = (pkg: Package, variant: 'endcustomer' | 'reseller') => { trackEvent({ category: 'marketing_packages', action: 'cta_dialog', name: `${variant}:${pkg.name}`, value: pkg.price, }); }; // nextStep entfernt, da Tabs nun parallel sind const getAccentTheme = (variant: 'endcustomer' | 'reseller') => variant === 'reseller' ? { badge: 'bg-amber-50 text-amber-700', price: 'text-amber-600', buttonHighlight: 'bg-gray-900 text-white hover:bg-gray-800', buttonDefault: 'border border-amber-200 text-amber-700 hover:bg-amber-50', cardBorder: 'border border-amber-100', highlightShadow: 'shadow-lg shadow-amber-100/60 bg-gradient-to-br from-amber-50/70 via-white to-amber-100/60', } : { badge: 'bg-rose-50 text-rose-700', price: 'text-rose-600', buttonHighlight: 'bg-gray-900 text-white hover:bg-gray-800', buttonDefault: 'border border-rose-100 text-rose-700 hover:bg-rose-50', cardBorder: 'border border-rose-100', highlightShadow: 'shadow-lg shadow-rose-100/60 bg-gradient-to-br from-rose-50/70 via-white to-rose-100/60', }; type PackageMetric = { key: string; label: string; value: string; }; const resolvePackageMetrics = ( pkg: Package, variant: 'endcustomer' | 'reseller', t: TFunction, tCommon: TFunction, ): PackageMetric[] => { if (variant === 'reseller') { return [ { key: 'max_tenants', label: t('packages.max_tenants'), value: pkg.limits?.max_tenants ? pkg.limits.max_tenants.toLocaleString() : tCommon('unlimited'), }, { key: 'max_events_per_year', label: t('packages.max_events_year'), value: pkg.limits?.max_events_per_year ? pkg.limits.max_events_per_year.toLocaleString() : tCommon('unlimited'), }, { key: 'branding', label: t('packages.feature_custom_branding'), value: pkg.branding_allowed ? tCommon('included') : t('packages.feature_no_branding'), }, ]; } return [ { key: 'max_photos', label: t('packages.max_photos_label'), value: pkg.limits?.max_photos ? pkg.limits.max_photos.toLocaleString() : tCommon('unlimited'), }, { key: 'max_guests', label: t('packages.max_guests_label'), value: pkg.limits?.max_guests ? pkg.limits.max_guests.toLocaleString() : tCommon('unlimited'), }, { key: 'gallery_days', label: t('packages.gallery_days_label'), value: pkg.gallery_duration_label ?? (pkg.limits?.gallery_days ? pkg.limits.gallery_days.toLocaleString() : tCommon('unlimited')), }, ]; }; interface PackageCardProps { pkg: Package; variant: 'endcustomer' | 'reseller'; highlight?: boolean; onSelect?: (pkg: Package) => void; className?: string; showCTA?: boolean; ctaLabel?: string; compact?: boolean; } function PackageCard({ pkg, variant, highlight = false, onSelect, className, showCTA = true, ctaLabel, compact = false, }: PackageCardProps) { const { t } = useTranslation('marketing'); const { t: tCommon } = useTranslation('common'); const accent = getAccentTheme(variant); const numericPrice = Number(pkg.price); const priceLabel = numericPrice === 0 ? t('packages.free') : `${numericPrice.toLocaleString()} ${t('packages.currency.euro')}`; const cadenceLabel = variant === 'reseller' ? t('packages.billing_per_year') : t('packages.billing_per_event'); const typeLabel = variant === 'reseller' ? t('packages.subscription') : t('packages.one_time'); const badgeLabel = highlight ? (variant === 'reseller' ? t('packages.badge_best_value') : t('packages.badge_most_popular')) : pkg.price === 0 ? t('packages.badge_starter') : null; const keyFeatures = pkg.features.slice(0, 3); const metrics = resolvePackageMetrics(pkg, variant, t, tCommon); const metricList = compact ? (
{metrics.map((metric) => (
{metric.label} {metric.value}
))}
) : (
{metrics.map((metric) => (

{metric.value}

{metric.label}

))}
); const featureList = compact ? (
    {keyFeatures.map((feature) => (
  • {t(`packages.feature_${feature}`)}
  • ))} {pkg.watermark_allowed === false && (
  • {t('packages.no_watermark')}
  • )} {pkg.branding_allowed && (
  • {t('packages.custom_branding')}
  • )}
) : (
    {keyFeatures.map((feature) => (
  • {t(`packages.feature_${feature}`)}
  • ))} {pkg.watermark_allowed === false && (
  • {t('packages.no_watermark')}
  • )} {pkg.branding_allowed && (
  • {t('packages.custom_branding')}
  • )}
); return (
{typeLabel} {badgeLabel && ( {badgeLabel} )}
{pkg.name} {pkg.description}
{priceLabel} {pkg.price !== 0 && ( / {cadenceLabel} )}
{variant === 'endcustomer' && (

{pkg.events} × {t('packages.one_time')}

)}
{metricList} {featureList}
{showCTA && onSelect && ( )}
); } interface PackageDetailGridProps { packageData: Package; variant: 'endcustomer' | 'reseller'; isHighlight: boolean; purchaseUrl: string; onCtaClick: () => void; t: TFunction; tCommon: TFunction; testimonials: { name: string; text: string; rating: number }[]; close: () => void; } const PackageDetailGrid: React.FC = ({ packageData, variant, isHighlight, purchaseUrl, onCtaClick, t, tCommon, testimonials, close, }) => { const metrics = resolvePackageMetrics(packageData, variant, t, tCommon); return (

{t('packages.price')}

{Number(packageData.price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2, })}{' '} {t('packages.currency.euro')}

{packageData.price > 0 && (

/ {variant === 'reseller' ? t('packages.billing_per_year') : t('packages.billing_per_event')}

)}
{isHighlight && ( {variant === 'reseller' ? t('packages.badge_best_value') : t('packages.badge_most_popular')} )}
{metrics.map((metric) => (

{metric.value}

{metric.label}

))}

{t('packages.order_hint')}

{t('packages.feature_highlights')}

    {packageData.features.slice(0, 5).map((feature) => (
  • {t(`packages.feature_${feature}`)}
  • ))} {packageData.watermark_allowed === false && (
  • {t('packages.no_watermark')}
  • )} {packageData.branding_allowed && (
  • {t('packages.custom_branding')}
  • )}

{t('packages.more_details_tab')}

{t('packages.breakdown_label')} {t('packages.testimonials_title')} {packageData.description_breakdown?.length ? ( {packageData.description_breakdown.map((entry, index) => ( {entry.title ?? t('packages.limits_label')} {entry.value} ))} ) : (

{t('packages.breakdown_label_hint')}

)}
{testimonials.map((testimonial, index) => (

{testimonial.name}

{packageData.name}

{[...Array(testimonial.rating)].map((_, i) => ( ))}

“{testimonial.text}”

))}
); }; return (

{t('packages.for_endcustomers')} · {t('packages.for_resellers')}

{t('packages.hero_title')}

{t('packages.hero_description')}

{t('packages.hero_secondary')}

{t('packages.tab_endcustomer')} {t('packages.tab_reseller')}

{t('packages.comparison_hint')}

{orderedEndcustomerPackages.map((pkg) => (
handleCardClick(selected, 'endcustomer')} className="h-full" compact />
))}
{orderedEndcustomerPackages.map((pkg) => ( handleCardClick(selected, 'endcustomer')} className="h-full" compact /> ))}
{orderedResellerPackages.map((pkg) => (
handleCardClick(selected, 'reseller')} className="h-full" compact />
))}
{orderedResellerPackages.map((pkg) => ( handleCardClick(selected, 'reseller')} className="h-full" compact /> ))}

{t('packages.faq_title')}

{t('packages.faq_lead')}

{[ { title: t('packages.faq_free'), body: t('packages.faq_free_desc') }, { title: t('packages.faq_upgrade'), body: t('packages.faq_upgrade_desc') }, { title: t('packages.faq_reseller'), body: t('packages.faq_reseller_desc') }, { title: t('packages.faq_payment'), body: t('packages.faq_payment_desc') }, ].map((item) => ( {item.title}

{item.body}

))}
{/* Details overlay */} {selectedPackage && ( isMobile ? ( {renderDetailBody('h-full overflow-y-auto space-y-8 p-6')} ) : ( {renderDetailBody('max-h-[88vh] overflow-y-auto space-y-8 p-6 md:p-10')} ) )} {/* Testimonials Section entfernt, da nun im Dialog */}
); }; const handleDetailAutoFocus = (event: Event) => { event.preventDefault(); dialogScrollRef.current?.scrollTo({ top: 0 }); dialogHeadingRef.current?.focus(); }; Packages.layout = (page: React.ReactNode) => page; export default Packages;