From d87d22fa22c196fee68bc3fa6b1490130c678e46 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sat, 24 Jan 2026 22:30:03 +0100 Subject: [PATCH] Redesign marketing packages layout --- resources/js/pages/marketing/Packages.tsx | 451 ++++++++++++++++++---- resources/lang/de/marketing.json | 13 + resources/lang/en/marketing.json | 14 + 3 files changed, 399 insertions(+), 79 deletions(-) diff --git a/resources/js/pages/marketing/Packages.tsx b/resources/js/pages/marketing/Packages.tsx index bf60a18..78c17be 100644 --- a/resources/js/pages/marketing/Packages.tsx +++ b/resources/js/pages/marketing/Packages.tsx @@ -6,16 +6,18 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u 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 { Badge } from '@/components/ui/badge'; 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 { Separator } from '@/components/ui/separator'; 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 { useLocale } from '@/hooks/useLocale'; -import { ArrowRight, Check, Star } from 'lucide-react'; +import { ArrowRight, Check, Headphones, LayoutGrid, Star } from 'lucide-react'; import toast from 'react-hot-toast'; import { motion, useReducedMotion } from 'framer-motion'; @@ -404,6 +406,57 @@ const Packages: React.FC = ({ endcustomerPackages, resellerPackag [resellerPackages], ); + const highlightResellerPackage = useMemo( + () => orderedResellerPackages.find((pkg) => pkg.id === highlightResellerId) ?? null, + [orderedResellerPackages, highlightResellerId], + ); + + const resellerBundles = useMemo( + () => orderedResellerPackages.filter((pkg) => pkg.id !== highlightResellerId), + [orderedResellerPackages, highlightResellerId], + ); + + const guestSliderMin = 1; + const guestSliderMax = useMemo(() => { + const limits = orderedEndcustomerPackages + .map((pkg) => pkg.limits?.max_guests ?? pkg.max_guests) + .filter((value): value is number => typeof value === 'number' && Number.isFinite(value)); + + if (limits.length === 0) { + return 500; + } + + return Math.max(500, ...limits); + }, [orderedEndcustomerPackages]); + + const [guestCount, setGuestCount] = useState(() => Math.min(100, guestSliderMax)); + + useEffect(() => { + setGuestCount((current) => { + if (current < guestSliderMin) { + return guestSliderMin; + } + if (current > guestSliderMax) { + return guestSliderMax; + } + return current; + }); + }, [guestSliderMax, guestSliderMin]); + + const recommendedPackage = useMemo(() => { + if (orderedEndcustomerPackages.length === 0) { + return null; + } + + const match = orderedEndcustomerPackages.find((pkg) => { + const limit = pkg.limits?.max_guests ?? pkg.max_guests; + const limitValue = typeof limit === 'number' ? limit : Number.POSITIVE_INFINITY; + return limitValue >= guestCount; + }); + + return match ?? orderedEndcustomerPackages[orderedEndcustomerPackages.length - 1]; + }, [orderedEndcustomerPackages, guestCount]); + useEffect(() => { if (typeof window === 'undefined') { return; @@ -584,20 +637,20 @@ function selectHighlightPackageId(packages: Package[]): number | null { const getAccentTheme = (variant: 'endcustomer' | 'reseller') => variant === 'reseller' ? { - badge: 'bg-amber-50 text-amber-700 dark:bg-amber-500/20 dark:text-amber-100', - price: 'text-amber-600 dark:text-amber-100', + badge: 'border border-amber-200/70 bg-amber-100 text-amber-700 dark:border-amber-500/40 dark:bg-amber-500/20 dark:text-amber-100', + price: 'text-amber-600 dark:text-amber-200', buttonHighlight: 'bg-gray-900 text-white hover:bg-gray-800 dark:bg-amber-600 dark:hover:bg-amber-500', - buttonDefault: 'border border-amber-200 text-amber-700 hover:bg-amber-50 dark:border-amber-500/50 dark:text-amber-100 dark:hover:bg-amber-500/10', - cardBorder: 'border border-amber-100 dark:border-amber-500/40', - highlightShadow: 'shadow-lg shadow-amber-100/60 bg-gradient-to-br from-amber-50/70 via-white to-amber-100/60 dark:shadow-amber-900/40 dark:from-amber-900/40 dark:via-gray-900 dark:to-amber-900/20', + buttonDefault: 'border border-amber-200 text-amber-700 hover:bg-amber-50 dark:border-amber-500/40 dark:text-amber-100 dark:hover:bg-amber-500/10', + cardBorder: 'border-amber-200/70 dark:border-amber-500/40', + highlightShadow: 'shadow-xl shadow-amber-200/40 ring-1 ring-amber-200/70 dark:shadow-amber-900/40 dark:ring-amber-500/30', } : { - badge: 'bg-rose-50 text-rose-700 dark:bg-pink-500/20 dark:text-pink-100', - price: 'text-rose-600 dark:text-pink-100', + badge: 'border border-rose-200/70 bg-rose-100 text-rose-700 dark:border-pink-500/40 dark:bg-pink-500/20 dark:text-pink-100', + price: 'text-rose-600 dark:text-pink-200', buttonHighlight: 'bg-gray-900 text-white hover:bg-gray-800 dark:bg-pink-600 dark:hover:bg-pink-500', - buttonDefault: 'border border-rose-100 text-rose-700 hover:bg-rose-50 dark:border-pink-500/50 dark:text-pink-100 dark:hover:bg-pink-500/10', - cardBorder: 'border border-rose-100 dark:border-pink-500/40', - highlightShadow: 'shadow-lg shadow-rose-100/60 bg-gradient-to-br from-rose-50/70 via-white to-rose-100/60 dark:shadow-pink-900/40 dark:from-pink-900/40 dark:via-gray-900 dark:to-pink-900/10', + buttonDefault: 'border border-rose-200 text-rose-700 hover:bg-rose-50 dark:border-pink-500/40 dark:text-pink-100 dark:hover:bg-pink-500/10', + cardBorder: 'border-rose-200/70 dark:border-pink-500/40', + highlightShadow: 'shadow-xl shadow-rose-200/40 ring-1 ring-rose-200/70 dark:shadow-pink-900/40 dark:ring-pink-500/30', }; type PackageMetric = { @@ -726,50 +779,38 @@ function PackageCard({ const badgeLabel = highlight ? (variant === 'reseller' ? t('packages.badge_best_value') - : t('packages.badge_most_popular')) + : t('packages.badge_recommended')) : pkg.price === 0 ? t('packages.badge_starter') : null; + const eventBadge = variant === 'reseller' && pkg.events + ? t('packages.events_badge', { count: pkg.events, defaultValue: `${pkg.events} Events` }) + : null; + const displayFeatures = buildDisplayFeatures(pkg, variant); - const keyFeatures = displayFeatures.slice(0, 3); const visibleFeatures = compact ? displayFeatures.slice(0, 3) : displayFeatures.slice(0, 5); const metrics = resolvePackageMetrics(pkg, variant, t, tCommon); - const metricList = compact ? ( -
+ const metricList = ( +
    {metrics.map((metric) => ( -
    - {metric.label} - {metric.value} -
    - ))} -
- ) : ( -
- {metrics.map((metric) => ( -
-

{metric.value}

-

{metric.label}

-
- ))} -
- ); - - const featureList = compact ? ( - - ) : ( -