- Wired the checkout wizard for Google “comfort login”: added Socialite controller + dependency, new Google env

hooks in config/services.php/.env.example, and updated wizard steps/controllers to store session payloads,
attach packages, and surface localized success/error states.
- Retooled payment handling for both Stripe and PayPal, adding richer status management in CheckoutController/
PayPalController, fallback flows in the wizard’s PaymentStep.tsx, and fresh feature tests for intent
creation, webhooks, and the wizard CTA.
- Introduced a consent-aware Matomo analytics stack: new consent context, cookie-banner UI, useAnalytics/
useCtaExperiment hooks, and MatomoTracker component, then instrumented marketing pages (Home, Packages,
Checkout) with localized copy and experiment tracking.
- Polished package presentation across marketing UIs by centralizing formatting in PresentsPackages, surfacing
localized description tables/placeholders, tuning badges/layouts, and syncing guest/marketing translations.
- Expanded docs & reference material (docs/prp/*, TODOs, public gallery overview) and added a Playwright smoke
test for the hero CTA while reconciling outstanding checklist items.
This commit is contained in:
Codex Agent
2025-10-19 11:41:03 +02:00
parent ae9b9160ac
commit a949c8d3af
113 changed files with 5169 additions and 712 deletions

View File

@@ -16,8 +16,8 @@ interface EmotionPickerProps {
}
export default function EmotionPicker({ onSelect }: EmotionPickerProps) {
const { token: slug } = useParams<{ token: string }>();
const eventKey = slug ?? '';
const { token } = useParams<{ token: string }>();
const eventKey = token ?? '';
const navigate = useNavigate();
const [emotions, setEmotions] = useState<Emotion[]>([]);
const [loading, setLoading] = useState(true);

View File

@@ -1,14 +1,13 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { getDeviceId } from '../lib/device';
import { usePollGalleryDelta } from '../polling/usePollGalleryDelta';
type Props = { slug: string };
type Props = { token: string };
export default function GalleryPreview({ slug }: Props) {
const { photos, loading } = usePollGalleryDelta(slug);
export default function GalleryPreview({ token }: Props) {
const { photos, loading } = usePollGalleryDelta(token);
const [mode, setMode] = React.useState<'latest' | 'popular' | 'myphotos'>('latest');
const items = React.useMemo(() => {
@@ -82,7 +81,7 @@ export default function GalleryPreview({ slug }: Props) {
My Photos
</button>
</div>
<Link to={`/e/${encodeURIComponent(slug)}/gallery?mode=${mode}`} className="text-sm text-pink-600 hover:text-pink-700 font-medium">
<Link to={`/e/${encodeURIComponent(token)}/gallery?mode=${mode}`} className="text-sm text-pink-600 hover:text-pink-700 font-medium">
Alle ansehen
</Link>
</div>
@@ -97,7 +96,7 @@ export default function GalleryPreview({ slug }: Props) {
)}
<div className="grid grid-cols-2 gap-3">
{items.map((p: any) => (
<Link key={p.id} to={`/e/${encodeURIComponent(slug)}/gallery?photoId=${p.id}`} className="block">
<Link key={p.id} to={`/e/${encodeURIComponent(token)}/gallery?photoId=${p.id}`} className="block">
<div className="relative">
<img
src={p.thumbnail_path || p.file_path}

View File

@@ -68,12 +68,12 @@ function renderEventAvatar(name: string, icon: unknown) {
);
}
export default function Header({ slug, title = '' }: { slug?: string; title?: string }) {
export default function Header({ eventToken, title = '' }: { eventToken?: string; title?: string }) {
const statsContext = useOptionalEventStats();
const identity = useOptionalGuestIdentity();
const { t } = useTranslation();
if (!slug) {
if (!eventToken) {
const guestName = identity?.name && identity?.hydrated ? identity.name : null;
return (
<div className="sticky top-0 z-20 flex items-center justify-between border-b bg-white/70 px-4 py-2 backdrop-blur dark:bg-black/40">
@@ -95,7 +95,7 @@ export default function Header({ slug, title = '' }: { slug?: string; title?: st
const { event, status } = useEventData();
const guestName =
identity && identity.eventKey === slug && identity.hydrated && identity.name ? identity.name : null;
identity && identity.eventKey === eventToken && identity.hydrated && identity.name ? identity.name : null;
if (status === 'loading') {
return (
@@ -114,7 +114,7 @@ export default function Header({ slug, title = '' }: { slug?: string; title?: st
}
const stats =
statsContext && statsContext.eventKey === slug ? statsContext : undefined;
statsContext && statsContext.eventKey === eventToken ? statsContext : undefined;
return (
<div className="sticky top-0 z-20 flex items-center justify-between border-b bg-white/70 px-4 py-2 backdrop-blur dark:bg-black/40">