Update marketing packages testimonials and demo
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Emotion;
|
||||
use App\Models\EventType;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class WeddingTasksSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$weddingType = EventType::where('slug', 'wedding')->first();
|
||||
if (! $weddingType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper to resolve emotion by English name (more stable given encoding issues)
|
||||
$by = fn (string $en) => Emotion::where('name->en', $en)->first();
|
||||
|
||||
$emLove = $by('Love');
|
||||
$emJoy = $by('Joy');
|
||||
$emTouched = $by('Touched');
|
||||
$emNostalgia = $by('Nostalgia');
|
||||
$emSurprise = $by('Surprise');
|
||||
$emPride = $by('Pride');
|
||||
|
||||
$tasks = [
|
||||
// Liebe (10)
|
||||
[$emLove, 'Kuss-Foto', 'Kiss Photo', 'Macht ein romantisches Kuss-Foto.', 'Take a romantic kiss photo.', 'easy'],
|
||||
[$emLove, 'Herz mit Haenden', 'Hands Heart', 'Formt ein Herz mit euren Haenden.', 'Make a heart with your hands.', 'easy'],
|
||||
[$emLove, 'Brautstrauss im Fokus', 'Bouquet Close-up', 'Brautstrauss nah an die Kamera halten.', 'Hold the bouquet close to the camera.', 'easy'],
|
||||
[$emLove, 'Stirnkuss', 'Forehead Kiss', 'Sanfter Stirnkuss – ganz verliebt.', 'A gentle forehead kiss.', 'easy'],
|
||||
[$emLove, 'Herzensblick', 'Loving Gaze', 'Schaut euch nur in die Augen.', 'Look only into each other’s eyes.', 'easy'],
|
||||
[$emLove, 'Schleiermoment', 'Veil Moment', 'Schleier ueber beide Koepfe legen.', 'Drape the veil over both of you.', 'medium'],
|
||||
[$emLove, 'Ringnahaufnahme', 'Ring Macro', 'Zeigt eure Ringe nah an der Kamera.', 'Show your rings close to the camera.', 'easy'],
|
||||
[$emLove, 'Hand in Hand', 'Holding Hands', 'Haende greifen, Kamera im Hintergrund.', 'Hold hands with the camera behind.', 'easy'],
|
||||
[$emLove, 'Tanzschritt', 'First Dance Step', 'Ein kleiner Tanzschritt fuer das Foto.', 'A small dance step for the photo.', 'medium'],
|
||||
[$emLove, 'Kuss hinter dem Strauss', 'Peek-a-boo Kiss', 'Kuss hinter dem Brautstrauss verstecken.', 'Hide a kiss behind the bouquet.', 'easy'],
|
||||
|
||||
// Freude (10)
|
||||
[$emJoy, 'Sprung-Foto', 'Jump Photo', 'Alle springen gleichzeitig!', 'Everyone jump together!', 'medium'],
|
||||
[$emJoy, 'Lachendes Gruppenfoto', 'Laughing Group', 'Erzaehlt einen Witz und klick!', 'Tell a joke and click!', 'easy'],
|
||||
[$emJoy, 'Konfetti-Moment', 'Confetti Moment', 'Konfetti werfen (oder so tun).', 'Throw confetti (or pretend).', 'easy'],
|
||||
[$emJoy, 'Cheers!', 'Cheers!', 'Glaser anstossen in die Kamera.', 'Clink glasses toward the camera.', 'easy'],
|
||||
[$emJoy, 'Freudensprung zu zweit', 'Couple Jump', 'Brautpaar springt gemeinsam.', 'Couple jumps together.', 'medium'],
|
||||
[$emJoy, 'Luftkuesse', 'Blowing Kisses', 'Luftkuesse in Richtung Kamera.', 'Blow kisses toward the camera.', 'easy'],
|
||||
[$emJoy, 'Scherzbrillen', 'Silly Glasses', 'Accessoires aufsetzen und lachen.', 'Wear props and laugh.', 'easy'],
|
||||
[$emJoy, 'Freudige Umarmung', 'Happy Hug', 'Grosse Umarmung in der Runde.', 'Big group hug.', 'easy'],
|
||||
[$emJoy, 'Daumen hoch', 'Thumbs Up', 'Alle Daumen nach oben!', 'Thumbs up, everyone!', 'easy'],
|
||||
[$emJoy, 'Victory-Zeichen', 'Peace Sign', 'Peace-Zeichen in die Kamera.', 'Peace sign to the camera.', 'easy'],
|
||||
|
||||
// Touched (8)
|
||||
[$emTouched, 'Traenen des Gluecks', 'Tears of Joy', 'Sanftes Traenchen abtupfen.', 'Dab a happy tear.', 'easy'],
|
||||
[$emTouched, 'Eltern-Umarmung', 'Parents’ Hug', 'Umarmung mit Eltern oder Trauzeugen.', 'Hug with parents or witnesses.', 'easy'],
|
||||
[$emTouched, 'Hand auf’s Herz', 'Hand on Heart', 'Hand aufs Herz – ehrlicher Moment.', 'Hand on heart — a sincere moment.', 'easy'],
|
||||
[$emTouched, 'Danke-Geste', 'Thank You Gesture', '„Danke“-Geste in die Kamera.', 'A “thank you” gesture to the camera.', 'easy'],
|
||||
[$emTouched, 'Enger Nasenstups', 'Nose Boop', 'Stirn an Stirn, sanfter Nasenstups.', 'Forehead to forehead, a soft nose boop.', 'easy'],
|
||||
[$emTouched, 'Geliebtes Andenken', 'Keepsake', 'Ein bedeutsames Andenken zeigen.', 'Show a meaningful keepsake.', 'easy'],
|
||||
[$emTouched, 'Leise Worte', 'Whisper', 'Ein leises Kompliment ins Ohr.', 'Whisper a compliment.', 'easy'],
|
||||
[$emTouched, 'Ruhe vor dem Sturm', 'Quiet Moment', 'Augen schliessen, tief durchatmen.', 'Close eyes and take a deep breath.', 'easy'],
|
||||
|
||||
// Nostalgia (8)
|
||||
[$emNostalgia, 'Altes Foto nachstellen', 'Recreate Old Photo', 'Ein altes Familienfoto nachstellen.', 'Recreate an old family photo.', 'medium'],
|
||||
[$emNostalgia, 'Kindheits-Pose', 'Childhood Pose', 'Lieblingspose aus der Kindheit.', 'Favorite childhood pose.', 'easy'],
|
||||
[$emNostalgia, 'Erste Nachricht', 'First Message', 'Handys mit erster Nachricht zeigen.', 'Show your first message on phones.', 'medium'],
|
||||
[$emNostalgia, 'Ringbox Vintage', 'Vintage Ring Box', 'Ringbox im Vintage-Stil inszenieren.', 'Stage the vintage ring box.', 'easy'],
|
||||
[$emNostalgia, 'Familienerbstueck', 'Family Heirloom', 'Ein Familienerbstueck ins Bild.', 'Feature a family heirloom.', 'easy'],
|
||||
[$emNostalgia, 'Schwarzweiss', 'Black & White', 'Schwarzweiss-Pose fuer klassisches Foto.', 'Pose for a black & white shot.', 'easy'],
|
||||
[$emNostalgia, 'Erster Tanz (Mini)', 'Mini First Dance', 'Ein Schritt vom ersten Tanz.', 'One step of the first dance.', 'easy'],
|
||||
[$emNostalgia, 'Gastebuch-Moment', 'Guestbook Moment', 'Eintrag ins Gaestebuch festhalten.', 'Capture a guestbook entry.', 'easy'],
|
||||
|
||||
// Surprise (7)
|
||||
[$emSurprise, 'Photobomb!', 'Photobomb!', 'Ueberraschung im Hintergrund.', 'Surprise in the background.', 'easy'],
|
||||
[$emSurprise, 'Erster Blick', 'First Look', 'Reaktion beim First Look nachstellen.', 'Recreate a first-look reaction.', 'medium'],
|
||||
[$emSurprise, 'Ueberraschungs-Dip', 'Surprise Dip', 'Ueberraschender Tanz-Dip.', 'A surprise dance dip.', 'medium'],
|
||||
[$emSurprise, 'Ballon-Pop', 'Balloon Pop', 'Ballon zerplatzen (oder so tun).', 'Pop a balloon (or pretend).', 'easy'],
|
||||
[$emSurprise, 'Hutwechsel', 'Hat Swap', 'Huete/Accessoires spontan tauschen.', 'Swap hats/props on the fly.', 'easy'],
|
||||
[$emSurprise, 'Versteckspiel', 'Peekaboo', 'Hinter Deko kurz verstecken.', 'Peek from behind decor.', 'easy'],
|
||||
[$emSurprise, 'Gespiegelte Pose', 'Mirror Pose', 'Gegensaetzliche, gespiegelte Pose.', 'Opposite mirrored pose.', 'easy'],
|
||||
|
||||
// Pride (7)
|
||||
[$emPride, 'Just Married', 'Just Married', '„Just Married“-Schild zeigen.', 'Show a “Just Married” sign.', 'easy'],
|
||||
[$emPride, 'Ring zeigen', 'Show the Ring', 'Ring zur Kamera strecken.', 'Stretch ring toward the camera.', 'easy'],
|
||||
[$emPride, 'Brautkleid-Detail', 'Dress Detail', 'Lieblingsdetail am Kleid zeigen.', 'Show a favorite dress detail.', 'easy'],
|
||||
[$emPride, 'Anzug-Detail', 'Suit Detail', 'Manschette/Knopfloch zeigen.', 'Show cuff/ boutonniere.', 'easy'],
|
||||
[$emPride, 'Team Braut', 'Team Bride', '„Team Braut“-Gruppenpose.', '“Team Bride” group pose.', 'easy'],
|
||||
[$emPride, 'Team Braeutigam', 'Team Groom', '„Team Braeutigam“-Gruppenpose.', '“Team Groom” group pose.', 'easy'],
|
||||
[$emPride, 'Siegesschrei', 'Victory Cheer', 'Arme hoch, Jubel in die Kamera.', 'Arms up, cheer to the camera.', 'easy'],
|
||||
];
|
||||
|
||||
$sort = 1;
|
||||
foreach ($tasks as [$emotion, $titleDe, $titleEn, $descDe, $descEn, $difficulty]) {
|
||||
if (! $emotion) {
|
||||
continue;
|
||||
}
|
||||
Task::updateOrCreate([
|
||||
'emotion_id' => $emotion->id,
|
||||
'title->de' => $titleDe,
|
||||
], [
|
||||
'emotion_id' => $emotion->id,
|
||||
'event_type_id' => $weddingType->id,
|
||||
'title' => ['de' => $titleDe, 'en' => $titleEn],
|
||||
'description' => ['de' => $descDe, 'en' => $descEn],
|
||||
'difficulty' => $difficulty,
|
||||
'sort_order' => $sort++,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,18 +204,128 @@
|
||||
"no_watermark": "Kein Wasserzeichen",
|
||||
"max_tenants": "Max. Tenants",
|
||||
"max_events": "Events enthalten",
|
||||
"faq_free": "Was ist das Free Package?",
|
||||
"faq_branding": "Was bedeutet „Eigenes Branding“?",
|
||||
"faq_branding_desc": "Du steuerst das Design der Gäste-App: Light/Dark/Auto-Modus, Farben, Schriften, Logo/Emoji und Button-Stil. Wenn dein Paket Branding erlaubt, kannst du es event-spezifisch überschreiben, sonst bleibt das Standard-Branding aktiv.",
|
||||
"faq_upgrade": "Kann ich upgraden?",
|
||||
"faq_upgrade_desc": "Ja, du kannst dein Paket jederzeit upgraden. Änderungen wirken sofort für das laufende Event.",
|
||||
"faq_reseller": "Was für Partner / Agenturen?",
|
||||
"faq_reseller_desc": "Partner-Pakete bündeln mehrere Events und enthalten Team-Features wie Reseller-Dashboard und Branding-Steuerung.",
|
||||
"faq_payment": "Zahlung sicher?",
|
||||
"faq_free_desc": "Das Free Package bietet grundlegende Features für kleine Events mit begrenzter Anzahl an Fotos und Gästen.",
|
||||
"faq_upgrade_desc": "Ja, Sie können jederzeit upgraden, um mehr Features und Limits zu erhalten. Der Upgrade ist nahtlos und Ihre Daten bleiben erhalten.",
|
||||
"faq_reseller_desc": "Partner-Pakete sind Event-Kontingente für Agenturen, die mehrere Events verwalten. Inklusive Dashboard und Branding-Optionen.",
|
||||
"faq_payment_desc": "Alle Zahlungen werden über sichere Provider wie Paddle abgewickelt. Ihre Daten sind GDPR-konform geschützt.",
|
||||
"faq_payment_desc": "Ja. Zahlungen werden über sichere Zahlungsanbieter abgewickelt und mit TLS verschlüsselt übertragen.",
|
||||
"testimonials_title": "Erfahrungen zum Paket",
|
||||
"testimonials": {
|
||||
"anna": "Fotospiel hat unsere Hochzeit perfekt gemacht! Die Gäste konnten einfach Fotos teilen, und die Galerie war ein Hit.",
|
||||
"max": "Als Event-Organisator liebe ich die Analytics und das einfache Branding. Super für Firmenevents!",
|
||||
"lisa": "Kostenloses Paket für Geburtstage – einfach und sicher. Kein Stress mit Apps!"
|
||||
"starter": [
|
||||
{
|
||||
"name": "Sarah K.",
|
||||
"text": "Für unsere Gartenparty mit rund 80 Gästen war Starter ideal. Foto-Limits und Aufgaben haben gut gepasst."
|
||||
},
|
||||
{
|
||||
"name": "Tom H.",
|
||||
"text": "Wir wollten eine einfache Lösung für QR-Uploads und Galerie. Starter war genau richtig."
|
||||
},
|
||||
{
|
||||
"name": "Maja L.",
|
||||
"text": "Die 6 Monate Galerie reichen uns, und die 600 Fotos waren ein guter Rahmen."
|
||||
}
|
||||
],
|
||||
"standard": [
|
||||
{
|
||||
"name": "Lena & Jonas",
|
||||
"text": "Standard fühlt sich wie das Allround-Paket an: genug Gäste und Fotos plus ein Jahr Galerie."
|
||||
},
|
||||
{
|
||||
"name": "Marco P.",
|
||||
"text": "Mit Branding und Live-Slideshow wirkte das Event richtig stimmig."
|
||||
},
|
||||
{
|
||||
"name": "Nadine R.",
|
||||
"text": "Kein Wasserzeichen und eigene Farben – für Hochzeiten einfach passend."
|
||||
}
|
||||
],
|
||||
"pro": [
|
||||
{
|
||||
"name": "Aylin B.",
|
||||
"text": "Bei großen Events ist Premium entspannt: keine Gästegrenze und viel Foto-Puffer."
|
||||
},
|
||||
{
|
||||
"name": "Robert M.",
|
||||
"text": "Die Analytics sind hilfreich für den Nachbericht, und der Support reagierte schnell."
|
||||
},
|
||||
{
|
||||
"name": "Clara F.",
|
||||
"text": "Zwei Jahre Galerie geben uns Ruhe, wenn Fotos später noch gebraucht werden."
|
||||
}
|
||||
],
|
||||
"s-small-reseller": [
|
||||
{
|
||||
"name": "Agentur Huber",
|
||||
"text": "Ein guter Einstieg für kleine Studios. Fünf Events lassen sich sauber planen."
|
||||
},
|
||||
{
|
||||
"name": "Studio Meyer",
|
||||
"text": "Das Partner-Dashboard spart Zeit, wenn mehrere Kunden parallel laufen."
|
||||
},
|
||||
{
|
||||
"name": "Kathrin T.",
|
||||
"text": "Für kleinere Feiern reicht Starter-Level völlig aus – der Ablauf ist zuverlässig."
|
||||
}
|
||||
],
|
||||
"m-medium-reseller": [
|
||||
{
|
||||
"name": "Eventbüro Lenz",
|
||||
"text": "15 Events decken unsere Saison meistens ab. Standard-Level ist für viele Kunden passend."
|
||||
},
|
||||
{
|
||||
"name": "Jasmin & Co.",
|
||||
"text": "Das Reporting unterstützt die Nachbereitung und Kunden-Recaps."
|
||||
},
|
||||
{
|
||||
"name": "Agentur Nord",
|
||||
"text": "Branding und Slideshow machen die Umsetzung professionell, ohne kompliziert zu werden."
|
||||
}
|
||||
],
|
||||
"l-large-reseller": [
|
||||
{
|
||||
"name": "Studio Westend",
|
||||
"text": "Bei hoher Auslastung ist Premium angenehm – keine Diskussionen über Limits."
|
||||
},
|
||||
{
|
||||
"name": "Eventagentur Krämer",
|
||||
"text": "Die Live-Slideshow kommt bei großen Events immer gut an."
|
||||
},
|
||||
{
|
||||
"name": "Fritz P.",
|
||||
"text": "Viele Events im Jahr lassen sich hier sauber bündeln."
|
||||
}
|
||||
],
|
||||
"partner-premium-5": [
|
||||
{
|
||||
"name": "Hochzeitsstudio Weiß",
|
||||
"text": "Perfekt für wenige, aber große Produktionen im Jahr."
|
||||
},
|
||||
{
|
||||
"name": "Agentur Rosen",
|
||||
"text": "Premium-Features wirken bei Kunden sehr hochwertig, ohne großes Kontingent."
|
||||
},
|
||||
{
|
||||
"name": "Laura S.",
|
||||
"text": "Wir nutzen es für unsere Top-Events – zuverlässig und planbar."
|
||||
}
|
||||
],
|
||||
"studio-annual": [
|
||||
{
|
||||
"name": "Studio Alster",
|
||||
"text": "Das Jahreskontingent passt gut zu wiederkehrenden Kunden."
|
||||
},
|
||||
{
|
||||
"name": "Eventservice Hahn",
|
||||
"text": "24 Events geben ausreichend Spielraum über die Saison."
|
||||
},
|
||||
{
|
||||
"name": "Agentur Süd",
|
||||
"text": "Standard-Level ist ein guter Mittelweg für verschiedene Event-Typen."
|
||||
}
|
||||
]
|
||||
},
|
||||
"what_customers_say": "Was unsere Kunden sagen",
|
||||
"close": "Schließen",
|
||||
|
||||
@@ -195,14 +195,128 @@
|
||||
"standard_support": "Standard support",
|
||||
"max_tenants": "Max. Tenants",
|
||||
"max_events": "Events included",
|
||||
"faq_free": "What is the Free Package?",
|
||||
"faq_branding": "What does “Custom Branding” mean?",
|
||||
"faq_branding_desc": "You control the guest app’s look: Light/Dark/Auto mode, colors, fonts, logo/emoji, and button style. If your package allows branding, you can override per event; otherwise the default branding stays active.",
|
||||
"faq_upgrade": "Can I upgrade?",
|
||||
"faq_upgrade_desc": "Yes, you can upgrade at any time. Changes apply immediately to the current event.",
|
||||
"faq_reseller": "What for Partner / Agencies?",
|
||||
"faq_reseller_desc": "Partner packages bundle multiple events and include team features like reseller dashboards and branding controls.",
|
||||
"faq_payment": "Payment secure?",
|
||||
"faq_payment_desc": "Yes. Payments are processed by secure providers and transmitted with TLS encryption.",
|
||||
"testimonials_title": "Package experiences",
|
||||
"testimonials": {
|
||||
"anna": "Fotospiel made our wedding perfect! Guests could easily share photos, and the gallery was a hit.",
|
||||
"max": "As an event organizer, I love the analytics and easy branding. Great for corporate events!",
|
||||
"lisa": "Free package for birthdays – simple and secure. No app hassle!"
|
||||
"starter": [
|
||||
{
|
||||
"name": "Sarah K.",
|
||||
"text": "Starter was ideal for our garden party of about 80 guests. The photo and task limits fit well."
|
||||
},
|
||||
{
|
||||
"name": "Tom H.",
|
||||
"text": "We wanted a simple QR upload + gallery flow. Starter did exactly that."
|
||||
},
|
||||
{
|
||||
"name": "Maja L.",
|
||||
"text": "Six months of gallery access was enough for us, and 600 photos was a good frame."
|
||||
}
|
||||
],
|
||||
"standard": [
|
||||
{
|
||||
"name": "Lena & Jonas",
|
||||
"text": "Standard feels like the all-round package: plenty of guests and photos plus a full year of gallery."
|
||||
},
|
||||
{
|
||||
"name": "Marco P.",
|
||||
"text": "Branding and the live slideshow made the event feel cohesive."
|
||||
},
|
||||
{
|
||||
"name": "Nadine R.",
|
||||
"text": "No watermark and custom colors fit our wedding perfectly."
|
||||
}
|
||||
],
|
||||
"pro": [
|
||||
{
|
||||
"name": "Aylin B.",
|
||||
"text": "For large events, Premium is stress-free: no guest limit and lots of photo headroom."
|
||||
},
|
||||
{
|
||||
"name": "Robert M.",
|
||||
"text": "Analytics help with post-event reporting, and support was quick."
|
||||
},
|
||||
{
|
||||
"name": "Clara F.",
|
||||
"text": "Two years of gallery access is a relief when photos are needed later."
|
||||
}
|
||||
],
|
||||
"s-small-reseller": [
|
||||
{
|
||||
"name": "Agency Huber",
|
||||
"text": "A solid entry for small studios. Five events per year are easy to plan."
|
||||
},
|
||||
{
|
||||
"name": "Studio Meyer",
|
||||
"text": "The partner dashboard saves time when running several clients."
|
||||
},
|
||||
{
|
||||
"name": "Kathrin T.",
|
||||
"text": "Starter-level coverage is enough for smaller events, and the flow is reliable."
|
||||
}
|
||||
],
|
||||
"m-medium-reseller": [
|
||||
{
|
||||
"name": "Event Bureau Lenz",
|
||||
"text": "Fifteen events usually cover our season. Standard level fits most clients."
|
||||
},
|
||||
{
|
||||
"name": "Jasmin & Co.",
|
||||
"text": "Reporting helps with recaps and client summaries."
|
||||
},
|
||||
{
|
||||
"name": "Agency North",
|
||||
"text": "Branding and slideshow make delivery feel professional without complexity."
|
||||
}
|
||||
],
|
||||
"l-large-reseller": [
|
||||
{
|
||||
"name": "Studio Westend",
|
||||
"text": "At high volume, Premium is comfortable—no back-and-forth about limits."
|
||||
},
|
||||
{
|
||||
"name": "Agency Krämer",
|
||||
"text": "The live slideshow is a consistent hit at larger events."
|
||||
},
|
||||
{
|
||||
"name": "Fritz P.",
|
||||
"text": "Plenty of events per year bundled in one plan."
|
||||
}
|
||||
],
|
||||
"partner-premium-5": [
|
||||
{
|
||||
"name": "Wedding Studio Weiß",
|
||||
"text": "Perfect for a few large productions each year."
|
||||
},
|
||||
{
|
||||
"name": "Agency Rosen",
|
||||
"text": "Premium features feel high-end without committing to a huge bundle."
|
||||
},
|
||||
{
|
||||
"name": "Laura S.",
|
||||
"text": "We use it for our top events—reliable and predictable."
|
||||
}
|
||||
],
|
||||
"studio-annual": [
|
||||
{
|
||||
"name": "Studio Alster",
|
||||
"text": "The annual bundle fits recurring clients well."
|
||||
},
|
||||
{
|
||||
"name": "Event Service Hahn",
|
||||
"text": "24 events give enough room across the season."
|
||||
},
|
||||
{
|
||||
"name": "Agency South",
|
||||
"text": "Standard level is a solid middle ground for varied event types."
|
||||
}
|
||||
]
|
||||
},
|
||||
"what_customers_say": "What our customers say",
|
||||
"close": "Close",
|
||||
|
||||
@@ -23,6 +23,7 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
const locale = useLocale();
|
||||
const embedUrl = demoToken ? `/e/${demoToken}` : '/e/demo?demo=1';
|
||||
const [isDemoOpen, setIsDemoOpen] = React.useState(false);
|
||||
const demoOpenLabel = t('labels.demoOpenOverlay', locale === 'en' ? 'Open demo overlay' : 'Demo im Overlay öffnen');
|
||||
|
||||
const demo = t('demo_page', { returnObjects: true }) as {
|
||||
title: string;
|
||||
@@ -35,17 +36,6 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
};
|
||||
const demoFeatures = Array.isArray(demo.features) ? demo.features : [];
|
||||
const handleOpenDemo = (): void => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const isMobile = window.matchMedia('(max-width: 768px)').matches;
|
||||
|
||||
if (isMobile) {
|
||||
window.open(embedUrl, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDemoOpen(true);
|
||||
};
|
||||
|
||||
@@ -84,18 +74,28 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
<div className="flex-1">
|
||||
{embedUrl ? (
|
||||
<>
|
||||
<div className="relative mx-auto w-full max-w-[300px] rounded-[2.5rem] border border-gray-200 bg-gray-900 px-3 pb-3 pt-4 shadow-2xl sm:max-w-[340px] md:max-w-[380px] dark:border-gray-700">
|
||||
<div className="relative mx-auto w-[min(92vw,calc(75dvh*10/14))] aspect-[10/14] rounded-[2.5rem] border border-gray-200 bg-gray-900 px-3 pb-3 pt-4 shadow-2xl dark:border-gray-700">
|
||||
<div
|
||||
className="absolute top-2 left-1/2 h-1.5 w-16 -translate-x-1/2 rounded-full bg-gray-300 dark:bg-gray-600"
|
||||
aria-hidden
|
||||
/>
|
||||
<iframe
|
||||
title="Demo der Fotospiel App"
|
||||
src={embedUrl}
|
||||
className="aspect-[9/19] w-full rounded-[1.75rem] border-0 bg-white shadow-inner dark:bg-gray-950"
|
||||
loading="lazy"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms"
|
||||
/>
|
||||
<div className="h-full w-full overflow-hidden rounded-[1.75rem] bg-white shadow-inner dark:bg-gray-950">
|
||||
{isDemoOpen ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2 bg-gradient-to-br from-pink-200 via-white to-white px-4 text-center dark:from-pink-900/40 dark:via-gray-950 dark:to-gray-950">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-pink-600/70 dark:text-pink-300/70">Live Demo</p>
|
||||
<p className="text-sm font-semibold text-gray-900 dark:text-gray-100">Demo läuft im Overlay</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300">Schließe das Overlay, um hier weiterzumachen.</p>
|
||||
</div>
|
||||
) : (
|
||||
<iframe
|
||||
title="Demo der Fotospiel App"
|
||||
src={embedUrl}
|
||||
className="h-full w-full border-0 bg-white dark:bg-gray-950"
|
||||
loading="lazy"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col items-center gap-1 text-center">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">{demo.iframeNote}</p>
|
||||
@@ -105,7 +105,7 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
className="text-pink-600 hover:text-pink-700 dark:text-pink-300"
|
||||
onClick={handleOpenDemo}
|
||||
>
|
||||
{demo.openFull}
|
||||
{demoOpenLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@@ -174,8 +174,8 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
</div>
|
||||
</section>
|
||||
<Dialog open={isDemoOpen} onOpenChange={setIsDemoOpen}>
|
||||
<DialogContent className="w-[388px] max-w-[90vw] border-0 bg-transparent p-0 shadow-none">
|
||||
<div className="relative w-full rounded-[2.5rem] border border-gray-200 bg-gray-900 p-3 shadow-2xl dark:border-gray-700">
|
||||
<DialogContent className="flex items-center justify-center border-0 bg-transparent p-0 shadow-none">
|
||||
<div className="relative h-[75dvh] w-auto max-w-[92vw] aspect-[10/14] rounded-[2.5rem] border border-gray-200 bg-gray-900 p-3 shadow-2xl dark:border-gray-700">
|
||||
<div
|
||||
className="absolute top-2 left-1/2 h-1.5 w-16 -translate-x-1/2 rounded-full bg-gray-300 dark:bg-gray-600"
|
||||
aria-hidden
|
||||
@@ -183,7 +183,7 @@ const DemoPage: React.FC<DemoPageProps> = ({ demoToken }) => {
|
||||
<iframe
|
||||
title="Demo der Fotospiel App"
|
||||
src={embedUrl}
|
||||
className="aspect-[9/16] w-full rounded-[1.75rem] border-0 bg-white shadow-inner dark:bg-gray-950"
|
||||
className="h-full w-full rounded-[1.75rem] border-0 bg-white shadow-inner dark:bg-gray-950"
|
||||
loading="lazy"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms"
|
||||
/>
|
||||
|
||||
@@ -431,11 +431,33 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
}, [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 testimonialsByPackage = useMemo(() => {
|
||||
const raw = t('packages.testimonials', { returnObjects: true });
|
||||
|
||||
if (!raw || typeof raw !== 'object') {
|
||||
return {};
|
||||
}
|
||||
|
||||
return raw as Record<string, { name?: string; text?: string; rating?: number }[]>;
|
||||
}, [locale, t]);
|
||||
|
||||
const testimonials = useMemo(() => {
|
||||
if (!selectedPackage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entries = testimonialsByPackage[selectedPackage.slug] ?? testimonialsByPackage.default ?? [];
|
||||
|
||||
if (!Array.isArray(entries)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return entries.map((entry) => ({
|
||||
name: entry.name ?? '',
|
||||
text: entry.text ?? '',
|
||||
rating: entry.rating ?? 4,
|
||||
}));
|
||||
}, [selectedPackage, testimonialsByPackage]);
|
||||
|
||||
const renderDetailBody = (wrapperClass: string) => {
|
||||
if (!selectedPackage) {
|
||||
@@ -1104,7 +1126,7 @@ const PackageDetailGrid: React.FC<PackageDetailGridProps> = ({
|
||||
</div>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{[
|
||||
{ title: t('packages.faq_free'), body: t('packages.faq_free_desc') },
|
||||
{ title: t('packages.faq_branding'), body: t('packages.faq_branding_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') },
|
||||
|
||||
@@ -118,10 +118,129 @@
|
||||
"custom_branding": "Benutzerdefiniertes Branding",
|
||||
"max_tenants": "Max. Tenants",
|
||||
"max_events": "Events im Kontingent",
|
||||
"faq_free": "Was ist das Free Package?",
|
||||
"faq_branding": "Was bedeutet „Eigenes Branding“?",
|
||||
"faq_branding_desc": "Du steuerst das Design der Gäste-App: Light/Dark/Auto-Modus, Farben, Schriften, Logo/Emoji und Button-Stil. Wenn dein Paket Branding erlaubt, kannst du es event-spezifisch überschreiben, sonst bleibt das Standard-Branding aktiv.",
|
||||
"faq_upgrade": "Kann ich upgraden?",
|
||||
"faq_upgrade_desc": "Ja, du kannst dein Paket jederzeit upgraden. Änderungen wirken sofort für das laufende Event.",
|
||||
"faq_reseller": "Was für Partner / Agenturen?",
|
||||
"faq_payment": "Zahlung sicher?"
|
||||
"faq_reseller_desc": "Partner-Pakete bündeln mehrere Events und enthalten Team-Features wie Reseller-Dashboard und Branding-Steuerung.",
|
||||
"faq_payment": "Zahlung sicher?",
|
||||
"faq_payment_desc": "Ja. Zahlungen werden über sichere Zahlungsanbieter abgewickelt und mit TLS verschlüsselt übertragen.",
|
||||
"testimonials_title": "Erfahrungen zum Paket",
|
||||
"testimonials": {
|
||||
"starter": [
|
||||
{
|
||||
"name": "Sarah K.",
|
||||
"text": "Für unsere Gartenparty mit rund 80 Gästen war Starter ideal. Foto-Limits und Aufgaben haben gut gepasst."
|
||||
},
|
||||
{
|
||||
"name": "Tom H.",
|
||||
"text": "Wir wollten eine einfache Lösung für QR-Uploads und Galerie. Starter war genau richtig."
|
||||
},
|
||||
{
|
||||
"name": "Maja L.",
|
||||
"text": "Die 6 Monate Galerie reichen uns, und die 600 Fotos waren ein guter Rahmen."
|
||||
}
|
||||
],
|
||||
"standard": [
|
||||
{
|
||||
"name": "Lena & Jonas",
|
||||
"text": "Standard fühlt sich wie das Allround-Paket an: genug Gäste und Fotos plus ein Jahr Galerie."
|
||||
},
|
||||
{
|
||||
"name": "Marco P.",
|
||||
"text": "Mit Branding und Live-Slideshow wirkte das Event richtig stimmig."
|
||||
},
|
||||
{
|
||||
"name": "Nadine R.",
|
||||
"text": "Kein Wasserzeichen und eigene Farben – für Hochzeiten einfach passend."
|
||||
}
|
||||
],
|
||||
"pro": [
|
||||
{
|
||||
"name": "Aylin B.",
|
||||
"text": "Bei großen Events ist Premium entspannt: keine Gästegrenze und viel Foto-Puffer."
|
||||
},
|
||||
{
|
||||
"name": "Robert M.",
|
||||
"text": "Die Analytics sind hilfreich für den Nachbericht, und der Support reagierte schnell."
|
||||
},
|
||||
{
|
||||
"name": "Clara F.",
|
||||
"text": "Zwei Jahre Galerie geben uns Ruhe, wenn Fotos später noch gebraucht werden."
|
||||
}
|
||||
],
|
||||
"s-small-reseller": [
|
||||
{
|
||||
"name": "Agentur Huber",
|
||||
"text": "Ein guter Einstieg für kleine Studios. Fünf Events lassen sich sauber planen."
|
||||
},
|
||||
{
|
||||
"name": "Studio Meyer",
|
||||
"text": "Das Partner-Dashboard spart Zeit, wenn mehrere Kunden parallel laufen."
|
||||
},
|
||||
{
|
||||
"name": "Kathrin T.",
|
||||
"text": "Für kleinere Feiern reicht Starter-Level völlig aus – der Ablauf ist zuverlässig."
|
||||
}
|
||||
],
|
||||
"m-medium-reseller": [
|
||||
{
|
||||
"name": "Eventbüro Lenz",
|
||||
"text": "15 Events decken unsere Saison meistens ab. Standard-Level ist für viele Kunden passend."
|
||||
},
|
||||
{
|
||||
"name": "Jasmin & Co.",
|
||||
"text": "Das Reporting unterstützt die Nachbereitung und Kunden-Recaps."
|
||||
},
|
||||
{
|
||||
"name": "Agentur Nord",
|
||||
"text": "Branding und Slideshow machen die Umsetzung professionell, ohne kompliziert zu werden."
|
||||
}
|
||||
],
|
||||
"l-large-reseller": [
|
||||
{
|
||||
"name": "Studio Westend",
|
||||
"text": "Bei hoher Auslastung ist Premium angenehm – keine Diskussionen über Limits."
|
||||
},
|
||||
{
|
||||
"name": "Eventagentur Krämer",
|
||||
"text": "Die Live-Slideshow kommt bei großen Events immer gut an."
|
||||
},
|
||||
{
|
||||
"name": "Fritz P.",
|
||||
"text": "Viele Events im Jahr lassen sich hier sauber bündeln."
|
||||
}
|
||||
],
|
||||
"partner-premium-5": [
|
||||
{
|
||||
"name": "Hochzeitsstudio Weiß",
|
||||
"text": "Perfekt für wenige, aber große Produktionen im Jahr."
|
||||
},
|
||||
{
|
||||
"name": "Agentur Rosen",
|
||||
"text": "Premium-Features wirken bei Kunden sehr hochwertig, ohne großes Kontingent."
|
||||
},
|
||||
{
|
||||
"name": "Laura S.",
|
||||
"text": "Wir nutzen es für unsere Top-Events – zuverlässig und planbar."
|
||||
}
|
||||
],
|
||||
"studio-annual": [
|
||||
{
|
||||
"name": "Studio Alster",
|
||||
"text": "Das Jahreskontingent passt gut zu wiederkehrenden Kunden."
|
||||
},
|
||||
{
|
||||
"name": "Eventservice Hahn",
|
||||
"text": "24 Events geben ausreichend Spielraum über die Saison."
|
||||
},
|
||||
{
|
||||
"name": "Agentur Süd",
|
||||
"text": "Standard-Level ist ein guter Mittelweg für verschiedene Event-Typen."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "Fotospiel - Blog",
|
||||
|
||||
@@ -118,10 +118,129 @@
|
||||
"custom_branding": "Custom Branding",
|
||||
"max_tenants": "Max. Tenants",
|
||||
"max_events": "Events in bundle",
|
||||
"faq_free": "What is the Free Package?",
|
||||
"faq_branding": "What does “Custom Branding” mean?",
|
||||
"faq_branding_desc": "You control the guest app’s look: Light/Dark/Auto mode, colors, fonts, logo/emoji, and button style. If your package allows branding, you can override per event; otherwise the default branding stays active.",
|
||||
"faq_upgrade": "Can I upgrade?",
|
||||
"faq_upgrade_desc": "Yes, you can upgrade at any time. Changes apply immediately to the current event.",
|
||||
"faq_reseller": "What for Partner / Agencies?",
|
||||
"faq_payment": "Payment secure?"
|
||||
"faq_reseller_desc": "Partner packages bundle multiple events and include team features like reseller dashboards and branding controls.",
|
||||
"faq_payment": "Payment secure?",
|
||||
"faq_payment_desc": "Yes. Payments are processed by secure providers and transmitted with TLS encryption.",
|
||||
"testimonials_title": "Package experiences",
|
||||
"testimonials": {
|
||||
"starter": [
|
||||
{
|
||||
"name": "Sarah K.",
|
||||
"text": "Starter was ideal for our garden party of about 80 guests. The photo and task limits fit well."
|
||||
},
|
||||
{
|
||||
"name": "Tom H.",
|
||||
"text": "We wanted a simple QR upload + gallery flow. Starter did exactly that."
|
||||
},
|
||||
{
|
||||
"name": "Maja L.",
|
||||
"text": "Six months of gallery access was enough for us, and 600 photos was a good frame."
|
||||
}
|
||||
],
|
||||
"standard": [
|
||||
{
|
||||
"name": "Lena & Jonas",
|
||||
"text": "Standard feels like the all-round package: plenty of guests and photos plus a full year of gallery."
|
||||
},
|
||||
{
|
||||
"name": "Marco P.",
|
||||
"text": "Branding and the live slideshow made the event feel cohesive."
|
||||
},
|
||||
{
|
||||
"name": "Nadine R.",
|
||||
"text": "No watermark and custom colors fit our wedding perfectly."
|
||||
}
|
||||
],
|
||||
"pro": [
|
||||
{
|
||||
"name": "Aylin B.",
|
||||
"text": "For large events, Premium is stress-free: no guest limit and lots of photo headroom."
|
||||
},
|
||||
{
|
||||
"name": "Robert M.",
|
||||
"text": "Analytics help with post-event reporting, and support was quick."
|
||||
},
|
||||
{
|
||||
"name": "Clara F.",
|
||||
"text": "Two years of gallery access is a relief when photos are needed later."
|
||||
}
|
||||
],
|
||||
"s-small-reseller": [
|
||||
{
|
||||
"name": "Agency Huber",
|
||||
"text": "A solid entry for small studios. Five events per year are easy to plan."
|
||||
},
|
||||
{
|
||||
"name": "Studio Meyer",
|
||||
"text": "The partner dashboard saves time when running several clients."
|
||||
},
|
||||
{
|
||||
"name": "Kathrin T.",
|
||||
"text": "Starter-level coverage is enough for smaller events, and the flow is reliable."
|
||||
}
|
||||
],
|
||||
"m-medium-reseller": [
|
||||
{
|
||||
"name": "Event Bureau Lenz",
|
||||
"text": "Fifteen events usually cover our season. Standard level fits most clients."
|
||||
},
|
||||
{
|
||||
"name": "Jasmin & Co.",
|
||||
"text": "Reporting helps with recaps and client summaries."
|
||||
},
|
||||
{
|
||||
"name": "Agency North",
|
||||
"text": "Branding and slideshow make delivery feel professional without complexity."
|
||||
}
|
||||
],
|
||||
"l-large-reseller": [
|
||||
{
|
||||
"name": "Studio Westend",
|
||||
"text": "At high volume, Premium is comfortable—no back-and-forth about limits."
|
||||
},
|
||||
{
|
||||
"name": "Agency Krämer",
|
||||
"text": "The live slideshow is a consistent hit at larger events."
|
||||
},
|
||||
{
|
||||
"name": "Fritz P.",
|
||||
"text": "Plenty of events per year bundled in one plan."
|
||||
}
|
||||
],
|
||||
"partner-premium-5": [
|
||||
{
|
||||
"name": "Wedding Studio Weiß",
|
||||
"text": "Perfect for a few large productions each year."
|
||||
},
|
||||
{
|
||||
"name": "Agency Rosen",
|
||||
"text": "Premium features feel high-end without committing to a huge bundle."
|
||||
},
|
||||
{
|
||||
"name": "Laura S.",
|
||||
"text": "We use it for our top events—reliable and predictable."
|
||||
}
|
||||
],
|
||||
"studio-annual": [
|
||||
{
|
||||
"name": "Studio Alster",
|
||||
"text": "The annual bundle fits recurring clients well."
|
||||
},
|
||||
{
|
||||
"name": "Event Service Hahn",
|
||||
"text": "24 events give enough room across the season."
|
||||
},
|
||||
{
|
||||
"name": "Agency South",
|
||||
"text": "Standard level is a solid middle ground for varied event types."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "Fotospiel - Blog",
|
||||
|
||||
@@ -13,7 +13,7 @@ class WatermarkConfigResolverTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_watermark_is_disabled_when_package_blocks_it(): void
|
||||
public function test_watermark_falls_back_to_base_when_package_blocks_it(): void
|
||||
{
|
||||
$package = Package::factory()->create([
|
||||
'watermark_allowed' => false,
|
||||
@@ -41,8 +41,9 @@ class WatermarkConfigResolverTest extends TestCase
|
||||
|
||||
$config = WatermarkConfigResolver::resolve($event);
|
||||
|
||||
$this->assertSame('none', $config['type']);
|
||||
$this->assertSame('none', $config['policy']);
|
||||
$this->assertSame('base', $config['type']);
|
||||
$this->assertSame(config('watermark.base.asset'), $config['asset']);
|
||||
$this->assertSame(config('watermark.base.position'), $config['position']);
|
||||
}
|
||||
|
||||
public function test_branding_locked_forces_base_watermark(): void
|
||||
|
||||
Reference in New Issue
Block a user