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')}
) : (
)
)} {/* 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;