Update marketing packages and checkout copy
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
tests / ui (push) Waiting to run

This commit is contained in:
Codex Agent
2026-02-01 13:04:11 +01:00
parent 386d0004ed
commit 2e78f3ab8d
35 changed files with 136 additions and 157 deletions

View File

@@ -38,6 +38,9 @@ This repository hosts a multi-tenant event photo platform (Laravel 12, PHP 8.3,
- resources/js/admin/ — Tenant Admin PWA source (React 19, Capacitor/TWA ready). - resources/js/admin/ — Tenant Admin PWA source (React 19, Capacitor/TWA ready).
- resources/js/pages/ — Inertia pages (React). - resources/js/pages/ — Inertia pages (React).
- docs/archive/README.md — historical PRP context. - docs/archive/README.md — historical PRP context.
- Marketing frontend language files:
- Source translations: `resources/lang/{de,en}/marketing.php` and `resources/lang/{de,en}/marketing.json`.
- Runtime i18next JSON served to the frontend: `public/lang/{de,en}/marketing.json` (must stay in sync with the source files).
## Standard Workflows ## Standard Workflows
- Coding tasks (Codegen Agent): - Coding tasks (Codegen Agent):

View File

@@ -30,21 +30,21 @@ return [
], ],
[ [
'key' => 'gift-standard', 'key' => 'gift-standard',
'label' => 'Geschenk Standard', 'label' => 'Geschenk Classic',
'amount' => 59.00, 'amount' => 59.00,
'currency' => 'EUR', 'currency' => 'EUR',
'paddle_price_id' => env('PADDLE_GIFT_PRICE_STANDARD', 'pri_01kbwccfvzrf4z2f1r62vns7gh'), 'paddle_price_id' => env('PADDLE_GIFT_PRICE_STANDARD', 'pri_01kbwccfvzrf4z2f1r62vns7gh'),
], ],
[ [
'key' => 'gift-standard-usd', 'key' => 'gift-standard-usd',
'label' => 'Gift Standard (USD)', 'label' => 'Gift Classic (USD)',
'amount' => 65.00, 'amount' => 65.00,
'currency' => 'USD', 'currency' => 'USD',
'paddle_price_id' => env('PADDLE_GIFT_PRICE_STANDARD_USD'), 'paddle_price_id' => env('PADDLE_GIFT_PRICE_STANDARD_USD'),
], ],
[ [
'key' => 'gift-standard-gbp', 'key' => 'gift-standard-gbp',
'label' => 'Gift Standard (GBP)', 'label' => 'Gift Classic (GBP)',
'amount' => 55.00, 'amount' => 55.00,
'currency' => 'GBP', 'currency' => 'GBP',
'paddle_price_id' => env('PADDLE_GIFT_PRICE_STANDARD_GBP'), 'paddle_price_id' => env('PADDLE_GIFT_PRICE_STANDARD_GBP'),
@@ -70,27 +70,6 @@ return [
'currency' => 'GBP', 'currency' => 'GBP',
'paddle_price_id' => env('PADDLE_GIFT_PRICE_PREMIUM_GBP'), 'paddle_price_id' => env('PADDLE_GIFT_PRICE_PREMIUM_GBP'),
], ],
[
'key' => 'gift-premium-plus',
'label' => 'Geschenk Premium Plus',
'amount' => 149.00,
'currency' => 'EUR',
'paddle_price_id' => env('PADDLE_GIFT_PRICE_PREMIUM_PLUS', 'pri_01kbwccgnjzwrjy5xg1yp981p6'),
],
[
'key' => 'gift-premium-plus-usd',
'label' => 'Gift Premium Plus (USD)',
'amount' => 159.00,
'currency' => 'USD',
'paddle_price_id' => env('PADDLE_GIFT_PRICE_PREMIUM_PLUS_USD'),
],
[
'key' => 'gift-premium-plus-gbp',
'label' => 'Gift Premium Plus (GBP)',
'amount' => 139.00,
'currency' => 'GBP',
'paddle_price_id' => env('PADDLE_GIFT_PRICE_PREMIUM_PLUS_GBP'),
],
], ],
// Package types a voucher coupon should apply to. // Package types a voucher coupon should apply to.

View File

@@ -60,7 +60,7 @@ class CouponSeeder extends Seeder
[ [
'code' => 'UPGRADE30', 'code' => 'UPGRADE30',
'name' => 'Upgrade 30 €', 'name' => 'Upgrade 30 €',
'description' => '30 € Nachlass als Upgrade-Anreiz von Starter auf Standard/Premium.', 'description' => '30 € Nachlass als Upgrade-Anreiz von Starter auf Classic/Premium.',
'type' => CouponType::FLAT, 'type' => CouponType::FLAT,
'amount' => 30.00, 'amount' => 30.00,
'currency' => 'EUR', 'currency' => 'EUR',
@@ -77,7 +77,7 @@ class CouponSeeder extends Seeder
[ [
'code' => 'SEASON50', 'code' => 'SEASON50',
'name' => 'Hochzeits-Saison 50 €', 'name' => 'Hochzeits-Saison 50 €',
'description' => 'Saisonaler 50 € Rabatt für die Hochzeitssaison auf Standard/Premium.', 'description' => 'Saisonaler 50 € Rabatt für die Hochzeitssaison auf Classic/Premium.',
'type' => CouponType::FLAT, 'type' => CouponType::FLAT,
'amount' => 50.00, 'amount' => 50.00,
'currency' => 'EUR', 'currency' => 'EUR',

View File

@@ -31,7 +31,7 @@ class PackageSeeder extends Seeder
'max_events_per_year' => 1, 'max_events_per_year' => 1,
'watermark_allowed' => false, 'watermark_allowed' => false,
'branding_allowed' => false, 'branding_allowed' => false,
'features' => ['basic_uploads', 'limited_sharing', 'custom_tasks'], 'features' => ['basic_uploads', 'limited_sharing', 'custom_tasks', 'live_slideshow'],
'paddle_product_id' => 'pro_01k8jcxx2g1vj9snqbga4283ej', 'paddle_product_id' => 'pro_01k8jcxx2g1vj9snqbga4283ej',
'paddle_price_id' => 'pri_01k8jcxx8qktxvqzzv0nkjjj27', 'paddle_price_id' => 'pri_01k8jcxx8qktxvqzzv0nkjjj27',
'description' => <<<'TEXT' 'description' => <<<'TEXT'
@@ -51,10 +51,10 @@ TEXT,
], ],
[ [
'slug' => 'standard', 'slug' => 'standard',
'name' => 'Standard', 'name' => 'Classic',
'name_translations' => [ 'name_translations' => [
'de' => 'Standard', 'de' => 'Classic',
'en' => 'Standard', 'en' => 'Classic',
], ],
'type' => PackageType::ENDCUSTOMER, 'type' => PackageType::ENDCUSTOMER,
'price' => 59.00, 'price' => 59.00,
@@ -151,10 +151,10 @@ TEXT,
], ],
[ [
'slug' => 'm-medium-reseller', 'slug' => 'm-medium-reseller',
'name' => 'Partner Standard', 'name' => 'Partner Classic',
'name_translations' => [ 'name_translations' => [
'de' => 'Partner Standard', 'de' => 'Partner Classic',
'en' => 'Partner Standard', 'en' => 'Partner Classic',
], ],
'type' => PackageType::RESELLER, 'type' => PackageType::RESELLER,
'included_package_slug' => 'standard', 'included_package_slug' => 'standard',
@@ -171,15 +171,15 @@ TEXT,
'paddle_product_id' => 'pro_01k8jcxtrxw7jsew52jnax901q', 'paddle_product_id' => 'pro_01k8jcxtrxw7jsew52jnax901q',
'paddle_price_id' => 'pri_01k8jcxv06nsgy8ym8mnfrfm5v', 'paddle_price_id' => 'pri_01k8jcxv06nsgy8ym8mnfrfm5v',
'description' => <<<'TEXT' 'description' => <<<'TEXT'
Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf StandardNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen. Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf ClassicNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen.
TEXT, TEXT,
'description_translations' => [ 'description_translations' => [
'de' => 'Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf StandardNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen.', 'de' => 'Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf ClassicNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen.',
'en' => 'Event-Kontingent for Partner / Agencies: {{max_events_per_year}} events at Standard level. Recommended to use within 24 months.', 'en' => 'Event-Kontingent for Partner / Agencies: {{max_events_per_year}} events at Classic level. Recommended to use within 24 months.',
], ],
'description_table' => [ 'description_table' => [
['title' => 'Events', 'value' => '{{max_events_per_year}} Events'], ['title' => 'Events', 'value' => '{{max_events_per_year}} Events'],
['title' => 'Inklusive Event-Level', 'value' => 'Standard'], ['title' => 'Inklusive Event-Level', 'value' => 'Classic'],
['title' => 'Empfehlung', 'value' => 'Empfohlen innerhalb von 24 Monaten zu nutzen.'], ['title' => 'Empfehlung', 'value' => 'Empfohlen innerhalb von 24 Monaten zu nutzen.'],
], ],
], ],
@@ -273,15 +273,15 @@ TEXT,
'paddle_product_id' => 'pro_01k8jct3gz9ks5mg6z61q6nrxb', 'paddle_product_id' => 'pro_01k8jct3gz9ks5mg6z61q6nrxb',
'paddle_price_id' => 'pri_01k8jcxsa8axwpjnybhjbcrb06', 'paddle_price_id' => 'pri_01k8jcxsa8axwpjnybhjbcrb06',
'description' => <<<'TEXT' 'description' => <<<'TEXT'
Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf StandardNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen. Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf ClassicNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen.
TEXT, TEXT,
'description_translations' => [ 'description_translations' => [
'de' => 'Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf StandardNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen.', 'de' => 'Event-Kontingent für Partner / Agenturen: {{max_events_per_year}} Events auf ClassicNiveau. Empfohlen innerhalb von 24 Monaten zu nutzen.',
'en' => 'Event-Kontingent for Partner / Agencies: {{max_events_per_year}} events at Standard level. Recommended to use within 24 months.', 'en' => 'Event-Kontingent for Partner / Agencies: {{max_events_per_year}} events at Classic level. Recommended to use within 24 months.',
], ],
'description_table' => [ 'description_table' => [
['title' => 'Events', 'value' => '{{max_events_per_year}} Events'], ['title' => 'Events', 'value' => '{{max_events_per_year}} Events'],
['title' => 'Inklusive Event-Level', 'value' => 'Standard'], ['title' => 'Inklusive Event-Level', 'value' => 'Classic'],
['title' => 'Empfehlung', 'value' => 'Empfohlen innerhalb von 24 Monaten zu nutzen.'], ['title' => 'Empfehlung', 'value' => 'Empfohlen innerhalb von 24 Monaten zu nutzen.'],
], ],
], ],

View File

@@ -109,7 +109,7 @@
"priority_support": "Priorisierter Support", "priority_support": "Priorisierter Support",
"cancel_link": "Paket verwalten: :link", "cancel_link": "Paket verwalten: :link",
"hero_kicker": "Pakete, die mit eurem Event mitwachsen", "hero_kicker": "Pakete, die mit eurem Event mitwachsen",
"hero_title": "Entdecken Sie unsere flexiblen Packages", "hero_title": "Entdecken Sie unsere flexiblen Event-Pakete",
"hero_description": "Von kostenlosem Einstieg bis Premium-Features: Passen Sie Ihr Event-Paket an Ihre Bedürfnisse an. Einfach, sicher und skalierbar.", "hero_description": "Von kostenlosem Einstieg bis Premium-Features: Passen Sie Ihr Event-Paket an Ihre Bedürfnisse an. Einfach, sicher und skalierbar.",
"hero_secondary": "Teste den kompletten Gäste-Flow in unserer Live-Demo kein Login, kein App-Store.", "hero_secondary": "Teste den kompletten Gäste-Flow in unserer Live-Demo kein Login, kein App-Store.",
"cta_demo": "Demo ansehen", "cta_demo": "Demo ansehen",
@@ -117,7 +117,7 @@
"cta_explore_highlight": "Lieblingspaket sichern", "cta_explore_highlight": "Lieblingspaket sichern",
"gift_cta": "Paket verschenken", "gift_cta": "Paket verschenken",
"tab_endcustomer": "Einzel-Events", "tab_endcustomer": "Einzel-Events",
"tab_reseller": "Partner / Agentur", "tab_reseller": "mehrere Events",
"section_endcustomer": "Packages für Endkunden (Einmalkauf pro Event)", "section_endcustomer": "Packages für Endkunden (Einmalkauf pro Event)",
"section_reseller": "Packages für Partner / Agenturen (Event-Kontingent)", "section_reseller": "Packages für Partner / Agenturen (Event-Kontingent)",
"bundles_title": "Partner & Agentur Bundles", "bundles_title": "Partner & Agentur Bundles",
@@ -156,7 +156,7 @@
"feature_watermark_custom": "Eigenes Wasserzeichen", "feature_watermark_custom": "Eigenes Wasserzeichen",
"feature_branding": "Branding", "feature_branding": "Branding",
"feature_support": "Support", "feature_support": "Support",
"feature_basic_uploads": "Basis-Uploads", "feature_basic_uploads": "Download aller Fotos",
"feature_unlimited_sharing": "Unbegrenztes Teilen", "feature_unlimited_sharing": "Unbegrenztes Teilen",
"feature_no_watermark": "Kein Wasserzeichen", "feature_no_watermark": "Kein Wasserzeichen",
"feature_custom_tasks": "Benutzerdefinierte Tasks", "feature_custom_tasks": "Benutzerdefinierte Tasks",
@@ -523,9 +523,9 @@
"family": "Familienfeiern" "family": "Familienfeiern"
}, },
"blog": "Blog", "blog": "Blog",
"packages": "Packages", "packages": "Pakete",
"contact": "Kontakt", "contact": "Kontakt",
"discover_packages": "Packages entdecken", "discover_packages": "Pakete entdecken",
"language": "Sprache", "language": "Sprache",
"language_de": "Deutsch", "language_de": "Deutsch",
"language_en": "English", "language_en": "English",
@@ -837,7 +837,7 @@
"label": "Setup vom Account zur Galerie" "label": "Setup vom Account zur Galerie"
}, },
{ {
"value": "0 Apps", "value": "keine App-Installation erforderlich",
"label": "Gäste nutzen nur ihren Browser" "label": "Gäste nutzen nur ihren Browser"
}, },
{ {

View File

@@ -104,7 +104,7 @@
"cta_explore_highlight": "Explore top packages", "cta_explore_highlight": "Explore top packages",
"gift_cta": "Gift a package", "gift_cta": "Gift a package",
"tab_endcustomer": "End Customers", "tab_endcustomer": "End Customers",
"tab_reseller": "Partner / Agency", "tab_reseller": "Bundles",
"section_endcustomer": "Packages for End Customers (One-time purchase per event)", "section_endcustomer": "Packages for End Customers (One-time purchase per event)",
"section_reseller": "Packages for Partner / Agencies (Event bundle)", "section_reseller": "Packages for Partner / Agencies (Event bundle)",
"bundles_title": "Partner & Agency Bundles", "bundles_title": "Partner & Agency Bundles",

View File

@@ -7,7 +7,7 @@ import { useTheme } from '@tamagui/core';
const DEV_TENANT_KEYS = [ const DEV_TENANT_KEYS = [
{ key: 'cust-standard-empty', label: 'Endkunde Starter (kein Event)' }, { key: 'cust-standard-empty', label: 'Endkunde Starter (kein Event)' },
{ key: 'cust-starter-wedding', label: 'Endkunde Standard (Hochzeit)' }, { key: 'cust-starter-wedding', label: 'Endkunde Classic (Hochzeit)' },
{ key: 'reseller-s-active', label: 'Reseller S 3 aktive Events' }, { key: 'reseller-s-active', label: 'Reseller S 3 aktive Events' },
{ key: 'reseller-s-full', label: 'Reseller S voll belegt (5/5)' }, { key: 'reseller-s-full', label: 'Reseller S voll belegt (5/5)' },
] as const; ] as const;

View File

@@ -200,7 +200,7 @@
"plans": { "plans": {
"title": "Pakete im Überblick", "title": "Pakete im Überblick",
"subtitle": "Wähle das passende Kontingent", "subtitle": "Wähle das passende Kontingent",
"hint": "Starter, Standard oder Partner alles mit Moderation & QR-Codes.", "hint": "Starter, Classic oder Partner alles mit Moderation & QR-Codes.",
"starter": { "starter": {
"title": "Starter", "title": "Starter",
"badge": "Für ein Event", "badge": "Für ein Event",
@@ -209,7 +209,7 @@
"p3": "Moderation & Galerie-Link" "p3": "Moderation & Galerie-Link"
}, },
"standard": { "standard": {
"title": "Standard", "title": "Classic",
"badge": "Beliebt", "badge": "Beliebt",
"highlight": "Mehr Kontingent & Branding", "highlight": "Mehr Kontingent & Branding",
"p1": "Mehr Events pro Jahr", "p1": "Mehr Events pro Jahr",

View File

@@ -763,7 +763,7 @@
"surface": "Fläche", "surface": "Fläche",
"lockedBranding": "Branding ist in diesem Paket gesperrt.", "lockedBranding": "Branding ist in diesem Paket gesperrt.",
"lockedTitle": "Branding freischalten", "lockedTitle": "Branding freischalten",
"lockedBody": "Upgrade auf Standard oder Premium, um Event-Branding zu nutzen.", "lockedBody": "Upgrade auf Classic oder Premium, um Event-Branding zu nutzen.",
"upgradeAction": "Paket upgraden", "upgradeAction": "Paket upgraden",
"source": "Branding-Quelle", "source": "Branding-Quelle",
"sourceHint": "Nutze das Standard-Branding oder passe nur dieses Event an.", "sourceHint": "Nutze das Standard-Branding oder passe nur dieses Event an.",
@@ -2934,7 +2934,7 @@
"recommendedUsage": "Empfohlen innerhalb von 24 Monaten zu nutzen.", "recommendedUsage": "Empfohlen innerhalb von 24 Monaten zu nutzen.",
"tiers": { "tiers": {
"starter": "Starter", "starter": "Starter",
"standard": "Standard", "standard": "Classic",
"premium": "Premium" "premium": "Premium"
}, },
"compare": { "compare": {

View File

@@ -200,7 +200,7 @@
"plans": { "plans": {
"title": "Packages at a glance", "title": "Packages at a glance",
"subtitle": "Choose the right quota", "subtitle": "Choose the right quota",
"hint": "Starter, Standard or Partner all include moderation & invites.", "hint": "Starter, Classic or Partner all include moderation & invites.",
"starter": { "starter": {
"title": "Starter", "title": "Starter",
"badge": "For one event", "badge": "For one event",
@@ -209,7 +209,7 @@
"p3": "Moderation & gallery link" "p3": "Moderation & gallery link"
}, },
"standard": { "standard": {
"title": "Standard", "title": "Classic",
"badge": "Popular", "badge": "Popular",
"highlight": "More quota & branding", "highlight": "More quota & branding",
"p1": "More events per year", "p1": "More events per year",

View File

@@ -759,7 +759,7 @@
"surface": "Surface", "surface": "Surface",
"lockedBranding": "Branding is locked for this package.", "lockedBranding": "Branding is locked for this package.",
"lockedTitle": "Unlock branding", "lockedTitle": "Unlock branding",
"lockedBody": "Upgrade to Standard or Premium to unlock event branding.", "lockedBody": "Upgrade to Classic or Premium to unlock event branding.",
"upgradeAction": "Upgrade package", "upgradeAction": "Upgrade package",
"source": "Branding source", "source": "Branding source",
"sourceHint": "Use the default branding or customize this event only.", "sourceHint": "Use the default branding or customize this event only.",
@@ -2936,7 +2936,7 @@
"recommendedUsage": "Recommended to use within 24 months.", "recommendedUsage": "Recommended to use within 24 months.",
"tiers": { "tiers": {
"starter": "Starter", "starter": "Starter",
"standard": "Standard", "standard": "Classic",
"premium": "Premium" "premium": "Premium"
}, },
"compare": { "compare": {

View File

@@ -524,7 +524,7 @@ function PackageCard({
pkg.included_package_slug === 'starter' pkg.included_package_slug === 'starter'
? t('shop.partner.tiers.starter', 'Starter') ? t('shop.partner.tiers.starter', 'Starter')
: pkg.included_package_slug === 'standard' : pkg.included_package_slug === 'standard'
? t('shop.partner.tiers.standard', 'Standard') ? t('shop.partner.tiers.standard', 'Classic')
: pkg.included_package_slug === 'pro' : pkg.included_package_slug === 'pro'
? t('shop.partner.tiers.premium', 'Premium') ? t('shop.partner.tiers.premium', 'Premium')
: pkg.included_package_slug; : pkg.included_package_slug;

View File

@@ -694,7 +694,7 @@ export default function MobileBrandingPage() {
{!brandingAllowed ? ( {!brandingAllowed ? (
<UpgradeCard <UpgradeCard
title={t('events.branding.lockedTitle', 'Unlock branding')} title={t('events.branding.lockedTitle', 'Unlock branding')}
body={t('events.branding.lockedBody', 'Upgrade to Standard or Premium to unlock event branding.')} body={t('events.branding.lockedBody', 'Upgrade to Classic or Premium to unlock event branding.')}
actionLabel={t('events.branding.upgradeAction', 'Upgrade package')} actionLabel={t('events.branding.upgradeAction', 'Upgrade package')}
onPress={() => navigate(adminPath('/mobile/billing/shop?feature=custom_branding'))} onPress={() => navigate(adminPath('/mobile/billing/shop?feature=custom_branding'))}
/> />

View File

@@ -245,7 +245,7 @@ export default function MobileEventFormPage() {
} }
if (slugValue === 'standard') { if (slugValue === 'standard') {
return 'Standard'; return 'Classic';
} }
if (slugValue === 'pro') { if (slugValue === 'pro') {

View File

@@ -704,7 +704,7 @@ function resolveIncludedTierLabel(
} }
if (slug === 'standard') { if (slug === 'standard') {
return t('shop.partner.tiers.standard', 'Standard'); return t('shop.partner.tiers.standard', 'Classic');
} }
if (slug === 'pro') { if (slug === 'pro') {

View File

@@ -24,7 +24,7 @@ const fixtures = vi.hoisted(() => ({
activePackage: { activePackage: {
id: 1, id: 1,
package_id: 1, package_id: 1,
package_name: 'Standard', package_name: 'Classic',
package_type: 'reseller', package_type: 'reseller',
included_package_slug: null, included_package_slug: null,
active: true, active: true,

View File

@@ -68,7 +68,7 @@ vi.mock('../../api', () => ({
{ {
id: 1, id: 1,
package_id: 1, package_id: 1,
package_name: 'Standard', package_name: 'Classic',
package_type: 'endcustomer', package_type: 'endcustomer',
included_package_slug: null, included_package_slug: null,
active: true, active: true,
@@ -84,7 +84,7 @@ vi.mock('../../api', () => ({
activePackage: { activePackage: {
id: 1, id: 1,
package_id: 1, package_id: 1,
package_name: 'Standard', package_name: 'Classic',
package_type: 'endcustomer', package_type: 'endcustomer',
included_package_slug: null, included_package_slug: null,
active: true, active: true,

View File

@@ -252,7 +252,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
<div className="space-y-6"> <div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="md:col-span-1"> <div className="md:col-span-1">
<label htmlFor="first_name" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="first_name" className="block text-sm font-medium text-gray-700 dark:text-gray-100 mb-1">
{t('register.first_name')} {t('common:required')} {t('register.first_name')} {t('common:required')}
</label> </label>
<div className="relative"> <div className="relative">
@@ -269,7 +269,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
clearErrors('first_name'); clearErrors('first_name');
} }
}} }}
className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.first_name ? 'border-red-500' : 'border-gray-300'}`} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm dark:bg-gray-900 dark:text-gray-100 ${errors.first_name ? 'border-red-500' : 'border-gray-300 dark:border-gray-700'}`}
placeholder={t('register.first_name_placeholder')} placeholder={t('register.first_name_placeholder')}
/> />
</div> </div>
@@ -277,7 +277,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
</div> </div>
<div className="md:col-span-1"> <div className="md:col-span-1">
<label htmlFor="last_name" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="last_name" className="block text-sm font-medium text-gray-700 dark:text-gray-100 mb-1">
{t('register.last_name')} {t('common:required')} {t('register.last_name')} {t('common:required')}
</label> </label>
<div className="relative"> <div className="relative">
@@ -294,7 +294,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
clearErrors('last_name'); clearErrors('last_name');
} }
}} }}
className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.last_name ? 'border-red-500' : 'border-gray-300'}`} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm dark:bg-gray-900 dark:text-gray-100 ${errors.last_name ? 'border-red-500' : 'border-gray-300 dark:border-gray-700'}`}
placeholder={t('register.last_name_placeholder')} placeholder={t('register.last_name_placeholder')}
/> />
</div> </div>
@@ -302,7 +302,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-100 mb-1">
{t('register.email')} {t('common:required')} {t('register.email')} {t('common:required')}
</label> </label>
<div className="relative"> <div className="relative">
@@ -319,7 +319,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
clearErrors('email'); clearErrors('email');
} }
}} }}
className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.email ? 'border-red-500' : 'border-gray-300'}`} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm dark:bg-gray-900 dark:text-gray-100 ${errors.email ? 'border-red-500' : 'border-gray-300 dark:border-gray-700'}`}
placeholder={t('register.email_placeholder')} placeholder={t('register.email_placeholder')}
/> />
</div> </div>
@@ -327,7 +327,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
</div> </div>
<div className="md:col-span-1"> <div className="md:col-span-1">
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-100 mb-1">
{t('register.password')} {t('common:required')} {t('register.password')} {t('common:required')}
</label> </label>
<div className="relative"> <div className="relative">
@@ -347,7 +347,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
clearErrors('password_confirmation'); clearErrors('password_confirmation');
} }
}} }}
className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.password ? 'border-red-500' : 'border-gray-300'}`} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm dark:bg-gray-900 dark:text-gray-100 ${errors.password ? 'border-red-500' : 'border-gray-300 dark:border-gray-700'}`}
placeholder={t('register.password_placeholder')} placeholder={t('register.password_placeholder')}
/> />
</div> </div>
@@ -355,7 +355,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
</div> </div>
<div className="md:col-span-1"> <div className="md:col-span-1">
<label htmlFor="password_confirmation" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="password_confirmation" className="block text-sm font-medium text-gray-700 dark:text-gray-100 mb-1">
{t('register.password_confirmation')} {t('common:required')} {t('register.password_confirmation')} {t('common:required')}
</label> </label>
<div className="relative"> <div className="relative">
@@ -375,7 +375,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
clearErrors('password_confirmation'); clearErrors('password_confirmation');
} }
}} }}
className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.password_confirmation ? 'border-red-500' : 'border-gray-300'}`} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm dark:bg-gray-900 dark:text-gray-100 ${errors.password_confirmation ? 'border-red-500' : 'border-gray-300 dark:border-gray-700'}`}
placeholder={t('register.password_confirmation_placeholder')} placeholder={t('register.password_confirmation_placeholder')}
/> />
</div> </div>
@@ -399,14 +399,14 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
clearErrors('terms'); clearErrors('terms');
} }
}} }}
className="h-4 w-4 text-[#FFB6C1] focus:ring-[#FFB6C1] border-gray-300 rounded" className="h-4 w-4 text-[#FFB6C1] focus:ring-[#FFB6C1] border-gray-300 dark:border-gray-600 rounded"
/> />
<label htmlFor="privacy_consent" className="ml-2 block text-sm text-gray-900"> <label htmlFor="privacy_consent" className="ml-2 block text-sm text-gray-900 dark:text-gray-100">
{t('register.privacy_consent')}{' '} {t('register.privacy_consent')}{' '}
<button <button
type="button" type="button"
onClick={() => setPrivacyOpen(true)} onClick={() => setPrivacyOpen(true)}
className="text-[#FFB6C1] hover:underline inline bg-transparent border-none cursor-pointer p-0 font-medium" className="text-[#FFB6C1] hover:underline inline bg-transparent border-none cursor-pointer p-0 font-medium dark:text-pink-300"
> >
{t('register.privacy_policy_link')} {t('register.privacy_policy_link')}
</button>. </button>.

View File

@@ -664,7 +664,7 @@ const resolveServiceTierLabel = (slug: string | null | undefined): string => {
} }
if (slug === 'standard') { if (slug === 'standard') {
return 'Standard'; return 'Classic';
} }
if (slug === 'pro') { if (slug === 'pro') {
@@ -784,7 +784,7 @@ function PackageCard({
: null; : null;
const displayFeatures = buildDisplayFeatures(pkg, variant); const displayFeatures = buildDisplayFeatures(pkg, variant);
const visibleFeatures = compact ? displayFeatures.slice(0, 3) : displayFeatures.slice(0, 5); const visibleFeatures = displayFeatures.slice(0, 5);
const metrics = resolvePackageMetrics(pkg, variant, t, tCommon); const metrics = resolvePackageMetrics(pkg, variant, t, tCommon);
const metricList = ( const metricList = (
@@ -1200,18 +1200,6 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
{t('packages.hero_description')} {t('packages.hero_description')}
</p> </p>
</motion.div> </motion.div>
<motion.div className="flex justify-center md:justify-end" variants={revealUp}>
<Link
href={localizedPath(locale === 'en' ? '/gift-card' : '/gutschein')}
className="group inline-flex items-center gap-3 rounded-full border border-white/50 bg-white/70 px-4 py-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-700 shadow-sm backdrop-blur transition hover:bg-white/90 dark:border-gray-800 dark:bg-gray-900/70 dark:text-gray-100"
>
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-pink-500/15 text-pink-600 dark:text-pink-300">
<Gift className="h-4 w-4" aria-hidden />
</span>
<span>{t('packages.gift_cta')}</span>
<ArrowRight className="h-3.5 w-3.5 text-gray-400 transition group-hover:translate-x-1" aria-hidden />
</Link>
</motion.div>
</motion.div> </motion.div>
</section> </section>
@@ -1238,16 +1226,15 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
<TabsContent value="endcustomer" className="space-y-8 pt-8"> <TabsContent value="endcustomer" className="space-y-8 pt-8">
<div className="md:hidden"> <div className="md:hidden">
<div className="relative"> <div className="relative">
<div className="pointer-events-none absolute inset-y-0 left-0 w-6 bg-gradient-to-r from-white to-transparent dark:from-gray-950" /> <div className="pointer-events-none absolute inset-y-0 left-0 w-4 bg-gradient-to-r from-white/70 to-transparent dark:from-gray-950/70" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-6 bg-gradient-to-l from-white to-transparent dark:from-gray-950" /> <div className="pointer-events-none absolute inset-y-0 right-0 w-4 bg-gradient-to-l from-white/70 to-transparent dark:from-gray-950/70" />
<div <div
ref={mobileEndcustomerRef} ref={mobileEndcustomerRef}
className="flex snap-x snap-mandatory gap-4 overflow-x-auto pb-6" className="flex snap-x snap-mandatory gap-4 overflow-x-auto pb-6"
style={{ scrollPaddingLeft: '16px', scrollBehavior: 'smooth' }} style={{ scrollPaddingLeft: '16px', scrollPaddingRight: '16px', scrollBehavior: 'smooth' }}
> >
{orderedEndcustomerPackages.map((pkg) => ( {orderedEndcustomerPackages.map((pkg) => (
<div key={pkg.id} className="snap-start basis-[72vw] shrink-0 sm:basis-[60vw]"> <div key={pkg.id} className="snap-center basis-[64vw] shrink-0 sm:basis-[56vw]">
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
<PackageCard <PackageCard
pkg={pkg} pkg={pkg}
variant="endcustomer" variant="endcustomer"
@@ -1257,7 +1244,6 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
className="h-full" className="h-full"
compact compact
/> />
</motion.div>
</div> </div>
))} ))}
</div> </div>
@@ -1319,16 +1305,15 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
</motion.div> </motion.div>
<div className="md:hidden"> <div className="md:hidden">
<div className="relative"> <div className="relative">
<div className="pointer-events-none absolute inset-y-0 left-0 w-6 bg-gradient-to-r from-white to-transparent dark:from-gray-950" /> <div className="pointer-events-none absolute inset-y-0 left-0 w-4 bg-gradient-to-r from-white/70 to-transparent dark:from-gray-950/70" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-6 bg-gradient-to-l from-white to-transparent dark:from-gray-950" /> <div className="pointer-events-none absolute inset-y-0 right-0 w-4 bg-gradient-to-l from-white/70 to-transparent dark:from-gray-950/70" />
<div <div
ref={mobileResellerRef} ref={mobileResellerRef}
className="flex snap-x snap-mandatory gap-4 overflow-x-auto pb-6" className="flex snap-x snap-mandatory gap-4 overflow-x-auto pb-6"
style={{ scrollPaddingLeft: '16px', scrollBehavior: 'smooth' }} style={{ scrollPaddingLeft: '16px', scrollPaddingRight: '16px', scrollBehavior: 'smooth' }}
> >
{orderedResellerPackages.map((pkg) => ( {orderedResellerPackages.map((pkg) => (
<div key={pkg.id} className="snap-start basis-[72vw] shrink-0 sm:basis-[60vw]"> <div key={pkg.id} className="snap-center basis-[64vw] shrink-0 sm:basis-[56vw]">
<motion.div variants={revealUp} initial="hidden" whileInView="visible" viewport={viewportOnce}>
<PackageCard <PackageCard
pkg={pkg} pkg={pkg}
variant="reseller" variant="reseller"
@@ -1338,7 +1323,6 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
className="h-full" className="h-full"
compact compact
/> />
</motion.div>
</div> </div>
))} ))}
</div> </div>
@@ -1386,6 +1370,19 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
<PackageComparison packages={orderedResellerPackages} variant="reseller" serviceTierNames={serviceTierNames} /> <PackageComparison packages={orderedResellerPackages} variant="reseller" serviceTierNames={serviceTierNames} />
</motion.div> </motion.div>
</TabsContent> </TabsContent>
<div className="flex justify-center pt-2 md:pt-6">
<Link
href={localizedPath(locale === 'en' ? '/gift-card' : '/gutschein')}
className="group inline-flex items-center gap-3 rounded-full border border-gray-200 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-700 shadow-sm transition hover:bg-gray-50 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:hover:bg-gray-900/80"
>
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-pink-500/15 text-pink-600 dark:text-pink-300">
<Gift className="h-4 w-4" aria-hidden />
</span>
<span>{t('packages.gift_cta')}</span>
<ArrowRight className="h-3.5 w-3.5 text-gray-400 transition group-hover:translate-x-1" aria-hidden />
</Link>
</div>
</Tabs> </Tabs>
</div> </div>
</section> </section>

View File

@@ -72,7 +72,7 @@ describe('Home', () => {
<Home <Home
packages={[ packages={[
{ id: 1, name: 'Starter', description: 'Desc', price: 29 }, { id: 1, name: 'Starter', description: 'Desc', price: 29 },
{ id: 2, name: 'Standard', description: 'Desc', price: 59 }, { id: 2, name: 'Classic', description: 'Desc', price: 59 },
]} ]}
/> />
); );

View File

@@ -104,7 +104,7 @@ describe('Packages', () => {
}, },
{ {
id: 2, id: 2,
name: 'Standard', name: 'Classic',
slug: 'standard', slug: 'standard',
description: 'Desc', description: 'Desc',
description_breakdown: [], description_breakdown: [],

View File

@@ -44,12 +44,12 @@
"packages": { "packages": {
"title": "Unsere Packages", "title": "Unsere Packages",
"hero_kicker": "Pakete, die mit eurem Event mitwachsen", "hero_kicker": "Pakete, die mit eurem Event mitwachsen",
"hero_title": "Entdecken Sie unsere flexiblen Packages", "hero_title": "Entdecken Sie unsere flexiblen Event-Pakete",
"hero_description": "Von kostenlosem Einstieg bis Premium-Features: Passen Sie Ihr Event-Paket an Ihre Bedürfnisse an. Einfach, sicher und skalierbar.", "hero_description": "Von kostenlosem Einstieg bis Premium-Features: Passen Sie Ihr Event-Paket an Ihre Bedürfnisse an. Einfach, sicher und skalierbar.",
"cta_explore": "Pakete entdecken", "cta_explore": "Pakete entdecken",
"gift_cta": "Paket verschenken", "gift_cta": "Paket verschenken",
"tab_endcustomer": "Einzel-Events", "tab_endcustomer": "Einzel-Events",
"tab_reseller": "Partner / Agentur", "tab_reseller": "mehrere Events",
"section_endcustomer": "Packages für Endkunden (Einmalkauf pro Event)", "section_endcustomer": "Packages für Endkunden (Einmalkauf pro Event)",
"section_reseller": "Packages für Partner / Agenturen (Event-Kontingent)", "section_reseller": "Packages für Partner / Agenturen (Event-Kontingent)",
"bundles_title": "Partner & Agentur Bundles", "bundles_title": "Partner & Agentur Bundles",
@@ -100,7 +100,7 @@
"feature_watermark_custom": "Eigenes Wasserzeichen", "feature_watermark_custom": "Eigenes Wasserzeichen",
"feature_branding": "Branding", "feature_branding": "Branding",
"feature_support": "Support", "feature_support": "Support",
"feature_basic_uploads": "Basis-Uploads", "feature_basic_uploads": "Download aller Fotos",
"feature_unlimited_sharing": "Unbegrenztes Teilen", "feature_unlimited_sharing": "Unbegrenztes Teilen",
"feature_no_watermark": "Fotospiel-Wasserzeichen entfernen", "feature_no_watermark": "Fotospiel-Wasserzeichen entfernen",
"feature_custom_tasks": "Benutzerdefinierte Tasks", "feature_custom_tasks": "Benutzerdefinierte Tasks",
@@ -164,7 +164,7 @@
"standard": [ "standard": [
{ {
"name": "Lena & Jonas", "name": "Lena & Jonas",
"text": "Standard fühlt sich wie das Allround-Paket an: genug Gäste und Fotos plus ein Jahr Galerie." "text": "Classic fühlt sich wie das Allround-Paket an: genug Gäste und Fotos plus ein Jahr Galerie."
}, },
{ {
"name": "Marco P.", "name": "Marco P.",
@@ -206,7 +206,7 @@
"m-medium-reseller": [ "m-medium-reseller": [
{ {
"name": "Eventbüro Lenz", "name": "Eventbüro Lenz",
"text": "15 Events decken unsere Saison meistens ab. Standard-Level ist für viele Kunden passend." "text": "15 Events decken unsere Saison meistens ab. Classic-Level ist für viele Kunden passend."
}, },
{ {
"name": "Jasmin & Co.", "name": "Jasmin & Co.",
@@ -256,7 +256,7 @@
}, },
{ {
"name": "Agentur Süd", "name": "Agentur Süd",
"text": "Standard-Level ist ein guter Mittelweg für verschiedene Event-Typen." "text": "Classic-Level ist ein guter Mittelweg für verschiedene Event-Typen."
} }
] ]
} }

View File

@@ -3,11 +3,11 @@
return [ return [
'packages' => [ 'packages' => [
'title' => 'Unsere Packages Wählen Sie Ihr Event-Paket', 'title' => 'Unsere Packages Wählen Sie Ihr Event-Paket',
'hero_title' => 'Entdecken Sie unsere flexiblen Packages', 'hero_title' => 'Entdecken Sie unsere flexiblen Event-Pakete',
'hero_description' => 'Von kostenlosem Einstieg bis Premium-Features: Passen Sie Ihr Event-Paket an Ihre Bedürfnisse an. Einfach, sicher und skalierbar.', 'hero_description' => 'Von kostenlosem Einstieg bis Premium-Features: Passen Sie Ihr Event-Paket an Ihre Bedürfnisse an. Einfach, sicher und skalierbar.',
'cta_explore' => 'Packages entdecken', 'cta_explore' => 'Packages entdecken',
'tab_endcustomer' => 'Einzel-Events', 'tab_endcustomer' => 'Einzel-Events',
'tab_reseller' => 'Partner / Agenturen', 'tab_reseller' => 'mehrere Events',
'section_endcustomer' => 'Packages für Endkunden (Einmalkauf pro Event)', 'section_endcustomer' => 'Packages für Endkunden (Einmalkauf pro Event)',
'section_reseller' => 'Packages für Partner / Agenturen (Event-Kontingent)', 'section_reseller' => 'Packages für Partner / Agenturen (Event-Kontingent)',
'free' => 'Kostenlos', 'free' => 'Kostenlos',
@@ -40,7 +40,7 @@ return [
'feature_watermark_custom' => 'Eigenes Wasserzeichen', 'feature_watermark_custom' => 'Eigenes Wasserzeichen',
'feature_branding' => 'Branding', 'feature_branding' => 'Branding',
'feature_support' => 'Support', 'feature_support' => 'Support',
'feature_basic_uploads' => 'Grundlegende Uploads', 'feature_basic_uploads' => 'Download aller Fotos',
'feature_unlimited_sharing' => 'Unbegrenztes Teilen', 'feature_unlimited_sharing' => 'Unbegrenztes Teilen',
'feature_no_watermark' => 'Fotospiel-Wasserzeichen entfernen', 'feature_no_watermark' => 'Fotospiel-Wasserzeichen entfernen',
'feature_custom_tasks' => 'Benutzerdefinierte Tasks', 'feature_custom_tasks' => 'Benutzerdefinierte Tasks',
@@ -85,9 +85,9 @@ return [
'family' => 'Familienfeiern', 'family' => 'Familienfeiern',
], ],
'blog' => 'Blog', 'blog' => 'Blog',
'packages' => 'Packages', 'packages' => 'Pakete',
'contact' => 'Kontakt', 'contact' => 'Kontakt',
'discover_packages' => 'Packages entdecken', 'discover_packages' => 'Pakete entdecken',
'language' => 'Sprache', 'language' => 'Sprache',
'open_menu' => 'Menü öffnen', 'open_menu' => 'Menü öffnen',
'close_menu' => 'Menü schließen', 'close_menu' => 'Menü schließen',

View File

@@ -49,7 +49,7 @@
"cta_explore": "Discover Packages", "cta_explore": "Discover Packages",
"gift_cta": "Gift a package", "gift_cta": "Gift a package",
"tab_endcustomer": "End Customers", "tab_endcustomer": "End Customers",
"tab_reseller": "Partner / Agency", "tab_reseller": "Bundles",
"section_endcustomer": "Packages for End Customers (One-time purchase per Event)", "section_endcustomer": "Packages for End Customers (One-time purchase per Event)",
"section_reseller": "Packages for Partner / Agencies (Event-Bundle)", "section_reseller": "Packages for Partner / Agencies (Event-Bundle)",
"bundles_title": "Partner & Agency Bundles", "bundles_title": "Partner & Agency Bundles",
@@ -165,7 +165,7 @@
"standard": [ "standard": [
{ {
"name": "Lena & Jonas", "name": "Lena & Jonas",
"text": "Standard feels like the all-round package: plenty of guests and photos plus a full year of gallery." "text": "Classic feels like the all-round package: plenty of guests and photos plus a full year of gallery."
}, },
{ {
"name": "Marco P.", "name": "Marco P.",
@@ -207,7 +207,7 @@
"m-medium-reseller": [ "m-medium-reseller": [
{ {
"name": "Event Bureau Lenz", "name": "Event Bureau Lenz",
"text": "Fifteen events usually cover our season. Standard level fits most clients." "text": "Fifteen events usually cover our season. Classic level fits most clients."
}, },
{ {
"name": "Jasmin & Co.", "name": "Jasmin & Co.",
@@ -257,7 +257,7 @@
}, },
{ {
"name": "Agency South", "name": "Agency South",
"text": "Standard level is a solid middle ground for varied event types." "text": "Classic level is a solid middle ground for varied event types."
} }
] ]
} }

View File

@@ -7,7 +7,7 @@ return [
'hero_description' => 'From free entry to premium features: Tailor your event package to your needs. Simple, secure and scalable.', 'hero_description' => 'From free entry to premium features: Tailor your event package to your needs. Simple, secure and scalable.',
'cta_explore' => 'Discover Packages', 'cta_explore' => 'Discover Packages',
'tab_endcustomer' => 'End Customers', 'tab_endcustomer' => 'End Customers',
'tab_reseller' => 'Partner / Agencies', 'tab_reseller' => 'Bundles',
'section_endcustomer' => 'Packages for End Customers (One-time purchase per Event)', 'section_endcustomer' => 'Packages for End Customers (One-time purchase per Event)',
'section_reseller' => 'Packages for Partner / Agencies (Event bundle)', 'section_reseller' => 'Packages for Partner / Agencies (Event bundle)',
'free' => 'Free', 'free' => 'Free',

View File

@@ -107,7 +107,7 @@ class CustomerEmailRenderTest extends TestCase
$user->forceFill(['tenant_id' => $tenant->id])->save(); $user->forceFill(['tenant_id' => $tenant->id])->save();
$package = Package::factory()->create([ $package = Package::factory()->create([
'name' => 'Standard', 'name' => 'Classic',
'type' => 'endcustomer', 'type' => 'endcustomer',
'max_photos' => 500, 'max_photos' => 500,
'max_guests' => 200, 'max_guests' => 200,

View File

@@ -26,7 +26,7 @@ class PurchaseConfirmationMailTest extends TestCase
$user->forceFill(['tenant_id' => $tenant->id])->save(); $user->forceFill(['tenant_id' => $tenant->id])->save();
$package = Package::factory()->create([ $package = Package::factory()->create([
'name' => 'Standard', 'name' => 'Classic',
'type' => 'endcustomer', 'type' => 'endcustomer',
'max_photos' => 500, 'max_photos' => 500,
'max_guests' => 200, 'max_guests' => 200,
@@ -51,7 +51,7 @@ class PurchaseConfirmationMailTest extends TestCase
$html = $mailable->render(); $html = $mailable->render();
$this->assertStringContainsString('Die Fotospiel.App', $html); $this->assertStringContainsString('Die Fotospiel.App', $html);
$this->assertStringContainsString('Standard', $html); $this->assertStringContainsString('Classic', $html);
$this->assertStringContainsString('txn_123', $html); $this->assertStringContainsString('txn_123', $html);
$this->assertStringContainsString('https://paddle.test/invoice/123', $html); $this->assertStringContainsString('https://paddle.test/invoice/123', $html);
} }

View File

@@ -61,7 +61,7 @@ class SeedDemoSwitcherTenantsTest extends TestCase
Package::factory()->create([ Package::factory()->create([
'slug' => 'standard', 'slug' => 'standard',
'name' => 'Standard', 'name' => 'Classic',
'type' => 'endcustomer', 'type' => 'endcustomer',
'max_events_per_year' => 5, 'max_events_per_year' => 5,
]); ]);

View File

@@ -65,8 +65,8 @@ class DashboardSummaryTest extends TestCase
$package = Package::factory() $package = Package::factory()
->reseller() ->reseller()
->create([ ->create([
'name' => 'Standard', 'name' => 'Classic',
'name_translations' => ['de' => 'Standard', 'en' => 'Standard'], 'name_translations' => ['de' => 'Classic', 'en' => 'Classic'],
'price' => 59, 'price' => 59,
]); ]);
@@ -96,7 +96,7 @@ class DashboardSummaryTest extends TestCase
$activePackagePayload = Arr::get($payload, 'active_package'); $activePackagePayload = Arr::get($payload, 'active_package');
$this->assertIsArray($activePackagePayload); $this->assertIsArray($activePackagePayload);
$this->assertSame('Standard', Arr::get($activePackagePayload, 'name')); $this->assertSame('Classic', Arr::get($activePackagePayload, 'name'));
$this->assertSame($activePackage->remaining_events, Arr::get($activePackagePayload, 'remaining_events')); $this->assertSame($activePackage->remaining_events, Arr::get($activePackagePayload, 'remaining_events'));
$this->assertSame( $this->assertSame(

View File

@@ -37,10 +37,10 @@ class EventListTest extends TenantTestCase
{ {
$package = Package::factory()->create([ $package = Package::factory()->create([
'type' => 'endcustomer', 'type' => 'endcustomer',
'name' => 'Standard', 'name' => 'Classic',
'name_translations' => [ 'name_translations' => [
'de' => 'Standard', 'de' => 'Classic',
'en' => 'Standard', 'en' => 'Classic',
], ],
'price' => 59, 'price' => 59,
'gallery_days' => 45, 'gallery_days' => 45,
@@ -75,7 +75,7 @@ class EventListTest extends TenantTestCase
$this->assertIsArray($matchingEvent['package']); $this->assertIsArray($matchingEvent['package']);
$this->assertSame($package->id, $matchingEvent['package']['id']); $this->assertSame($package->id, $matchingEvent['package']['id']);
$this->assertSame('Standard', $matchingEvent['package']['name']); $this->assertSame('Classic', $matchingEvent['package']['name']);
$this->assertSame('59.00', $matchingEvent['package']['price']); $this->assertSame('59.00', $matchingEvent['package']['price']);
} }

View File

@@ -129,7 +129,7 @@ class GiftVoucherServiceTest extends TestCase
config()->set('gift-vouchers.tiers', [ config()->set('gift-vouchers.tiers', [
[ [
'key' => 'gift-standard-usd', 'key' => 'gift-standard-usd',
'label' => 'Gift Standard (USD)', 'label' => 'Gift Classic (USD)',
'amount' => 65.00, 'amount' => 65.00,
'currency' => 'USD', 'currency' => 'USD',
'paddle_price_id' => 'pri_usd_123', 'paddle_price_id' => 'pri_usd_123',

View File

@@ -40,7 +40,7 @@ test.describe('Marketing frontend smoke', () => {
await acceptCookies.click(); await acceptCookies.click();
} }
const packageCards = page.locator('section >> text=/Starter|Standard|Premium/'); const packageCards = page.locator('section >> text=/Starter|Classic|Premium/');
await expect(packageCards.first()).toBeVisible(); await expect(packageCards.first()).toBeVisible();
}); });
}); });

View File

@@ -65,7 +65,7 @@ test.describe('Paddle sandbox full flow (staging)', () => {
}); });
try { try {
// Jump directly into wizard for Standard package (2) // Jump directly into wizard for Classic package (2)
await page.goto(`${baseUrl}/${locale}/${checkoutSlug}/2`); await page.goto(`${baseUrl}/${locale}/${checkoutSlug}/2`);
await dismissConsentBanner(page); await dismissConsentBanner(page);

View File

@@ -2,7 +2,7 @@ import { test, expectFixture as expect } from '../helpers/test-fixtures';
const shouldRun = process.env.E2E_TESTING_API === '1'; const shouldRun = process.env.E2E_TESTING_API === '1';
test.describe('Standard package checkout with Paddle completion', () => { test.describe('Classic package checkout with Paddle completion', () => {
test.skip(!shouldRun, 'Set E2E_TESTING_API=1 to enable checkout tests that use /api/_testing endpoints.'); test.skip(!shouldRun, 'Set E2E_TESTING_API=1 to enable checkout tests that use /api/_testing endpoints.');
test('registers, applies coupon, and reaches confirmation', async ({ test('registers, applies coupon, and reaches confirmation', async ({
page, page,
@@ -67,7 +67,7 @@ test.describe('Standard package checkout with Paddle completion', () => {
const standardDetailsButton = page const standardDetailsButton = page
.locator('[data-slot="card"]') .locator('[data-slot="card"]')
.filter({ hasText: /Standard/i }) .filter({ hasText: /Classic/i })
.getByRole('button', { name: /Details ansehen|Details anzeigen|View details/i }) .getByRole('button', { name: /Details ansehen|Details anzeigen|View details/i })
.first(); .first();

View File

@@ -6,7 +6,7 @@ const demoTenantCredentials = {
password: process.env.E2E_DEMO_TENANT_PASSWORD ?? 'Demo1234!', password: process.env.E2E_DEMO_TENANT_PASSWORD ?? 'Demo1234!',
}; };
test.describe('Standard package checkout with coupons', () => { test.describe('Classic package checkout with coupons', () => {
test.skip(!shouldRun, 'Set E2E_TESTING_API=1 to enable checkout tests that use /api/_testing endpoints.'); test.skip(!shouldRun, 'Set E2E_TESTING_API=1 to enable checkout tests that use /api/_testing endpoints.');
test('applies seeded coupon and shows discount summary', async ({ test('applies seeded coupon and shows discount summary', async ({
page, page,
@@ -25,7 +25,7 @@ test.describe('Standard package checkout with coupons', () => {
const dialog = page.getByRole('dialog'); const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible(); await expect(dialog).toBeVisible();
await expect(dialog.getByRole('heading', { name: /Standard/i })).toBeVisible(); await expect(dialog.getByRole('heading', { name: /Classic/i })).toBeVisible();
await dialog.getByRole('link', { name: /Jetzt bestellen|Order now|Jetzt buchen/i }).click(); await dialog.getByRole('link', { name: /Jetzt bestellen|Order now|Jetzt buchen/i }).click();