Added opaque join-token support across backend and frontend: new migration/model/service/endpoints, guest controllers now resolve tokens, and the demo seeder seeds a token. Tenant event details list/manage tokens with copy/revoke actions, and the guest PWA uses tokens end-to-end (routing, storage, uploads, achievements, etc.). Docs TODO updated to reflect completed steps.

This commit is contained in:
Codex Agent
2025-10-12 10:32:37 +02:00
parent d04e234ca0
commit 9394c3171e
73 changed files with 3277 additions and 911 deletions

View File

@@ -0,0 +1,21 @@
{
"app": {
"brand": "Fotospiel Tenant Admin",
"languageSwitch": "Language"
},
"navigation": {
"dashboard": "Dashboard",
"events": "Events",
"tasks": "Tasks",
"billing": "Billing",
"settings": "Settings"
},
"language": {
"de": "German",
"en": "English"
},
"actions": {
"open": "Open",
"viewAll": "View all"
}
}

View File

@@ -0,0 +1,72 @@
{
"actions": {
"newEvent": "New Event",
"allEvents": "All events",
"guidedSetup": "Guided setup"
},
"welcome": {
"fallbackName": "Tenant Admin",
"greeting": "Welcome, {{name}}!",
"subtitle": "Keep your events, credits, and tasks on track."
},
"errors": {
"loadFailed": "Dashboard could not be loaded."
},
"alerts": {
"errorTitle": "Error"
},
"welcomeCard": {
"title": "Start with the welcome journey",
"summary": "Discover the storytelling elements, choose your package, and create your first event with guided steps.",
"body1": "We guide you through packages, tasks, and gallery setup so your event shines.",
"body2": "You can return to the welcome journey at any time, even once events are live.",
"cta": "Start now"
},
"overview": {
"title": "At a glance",
"description": "Key tenant metrics at a glance.",
"noPackage": "No active package",
"stats": {
"activeEvents": "Active events",
"publishedHint": "{{count}} published",
"newPhotos": "New photos (7 days)",
"taskProgress": "Task progress",
"credits": "Credits",
"lowCredits": "Top up recommended"
}
},
"quickActions": {
"title": "Quick actions",
"description": "Jump straight to the most important actions.",
"createEvent": {
"label": "Create event",
"description": "Plan your next highlight."
},
"moderatePhotos": {
"label": "Moderate photos",
"description": "Review new uploads."
},
"organiseTasks": {
"label": "Organise tasks",
"description": "Assign clear responsibilities."
},
"manageCredits": {
"label": "Manage credits",
"description": "Review balance and ledger."
}
},
"upcoming": {
"title": "Upcoming events",
"description": "The next dates including status and quick access.",
"settings": "Open settings",
"empty": {
"message": "No events scheduled yet. Create your first one!",
"cta": "Plan event"
},
"status": {
"live": "Live",
"planning": "In planning",
"noDate": "No date"
}
}
}

View File

@@ -0,0 +1,149 @@
{
"billing": {
"title": "Billing & credits",
"subtitle": "Manage balances, packages, and invoicing.",
"actions": {
"refresh": "Refresh",
"exportCsv": "Export CSV"
},
"errors": {
"load": "Unable to load billing data.",
"more": "Unable to load more ledger entries."
},
"sections": {
"overview": {
"title": "Credits & status",
"description": "Your current balance and active reseller package.",
"cards": {
"balance": {
"label": "Available credits"
},
"used": {
"label": "Events used",
"helper": "Remaining: {{count}}"
},
"price": {
"label": "Price (net)"
},
"expires": {
"label": "Expires",
"helper": "Auto-renews when active"
}
}
},
"packages": {
"title": "Package history",
"description": "Overview of active and past reseller packages.",
"empty": "No packages purchased yet.",
"card": {
"statusActive": "Active",
"statusInactive": "Inactive",
"used": "Events used",
"available": "Remaining",
"expires": "Expires"
}
},
"ledger": {
"title": "Credit ledger",
"description": "All credit additions and deductions.",
"empty": "No ledger entries recorded yet.",
"loadMore": "Load more",
"reasons": {
"purchase": "Credit purchase",
"usage": "Usage",
"manual": "Manual adjustment"
}
}
}
},
"members": {
"title": "Event members",
"subtitle": "Manage moderators, admins, and helpers for this event.",
"actions": {
"back": "Back to overview"
},
"errors": {
"missingSlug": "No event slug provided.",
"load": "Could not load members.",
"emailRequired": "Please provide an email address.",
"invite": "Invitation could not be sent.",
"remove": "Member could not be removed."
},
"alerts": {
"notFoundTitle": "Event not found",
"notFoundDescription": "Please return to the event list.",
"lockedTitle": "Feature not enabled",
"lockedDescription": "Member management isnt available for this event yet. Contact support to activate the feature."
},
"sections": {
"list": {
"title": "Members",
"empty": "No members invited yet."
},
"invite": {
"title": "Invite new member"
}
},
"labels": {
"status": "Status: {{status}}",
"joined": "Joined: {{date}}"
},
"form": {
"emailLabel": "Email",
"emailPlaceholder": "person@example.com",
"nameLabel": "Name (optional)",
"namePlaceholder": "Name",
"roleLabel": "Role",
"rolePlaceholder": "Select role",
"submit": "Send invitation"
},
"roles": {
"tenantAdmin": "Tenant admin",
"member": "Member",
"guest": "Guest"
},
"statuses": {
"published": "Published",
"draft": "Draft",
"active": "Active"
},
"eventStatus": "Status: {{status}}",
"events": {
"untitled": "Untitled event"
}
},
"tasks": {
"title": "Event tasks",
"subtitle": "Manage tasks associated with this event.",
"actions": {
"back": "Back to overview",
"assign": "Assign selected tasks"
},
"errors": {
"missingSlug": "No event slug provided.",
"load": "Event tasks could not be loaded.",
"assign": "Tasks could not be assigned."
},
"alerts": {
"notFoundTitle": "Event not found",
"notFoundDescription": "Please return to the event list."
},
"eventStatus": "Status: {{status}}",
"sections": {
"assigned": {
"title": "Assigned tasks",
"empty": "No tasks assigned yet."
},
"library": {
"title": "Add tasks from library",
"empty": "No tasks found in the library."
}
},
"priorities": {
"low": "Low",
"medium": "Medium",
"high": "High",
"urgent": "Urgent"
}
}
}

View File

@@ -0,0 +1,264 @@
{
"layout": {
"eyebrow": "Fotospiel Tenant Admin",
"title": "Welcome to your event studio",
"subtitle": "Begin with an inspired introduction, secure your package, and craft the perfect guest gallery all optimised for mobile hosts.",
"alreadyFamiliar": "Already familiar with Fotospiel?",
"jumpToDashboard": "Jump to dashboard"
},
"hero": {
"eyebrow": "Your event, your stage",
"title": "Design the next Fotospiel experience",
"scriptTitle": "Memorable for guests, effortless for you.",
"description": "In just a few steps you guide guests through a magical photo journey complete with storytelling, tasks, and a moderated gallery.",
"primary": {
"label": "Explore packages",
"button": "Explore packages"
},
"secondary": {
"label": "View events",
"button": "View existing events"
}
},
"highlights": {
"gallery": {
"title": "Premium guest gallery",
"description": "Curate photos in real time, highlight favourites, and share QR codes in a tap.",
"badge": "New"
},
"team": {
"title": "Flexible team onboarding",
"description": "Invite co-hosts, assign roles, and stay on top of moderation and tasks."
},
"story": {
"title": "Storytelling in chapters",
"description": "Guided tasks and emotion cards turn every event into a memorable journey."
}
},
"ctaList": {
"choosePackage": {
"label": "Choose your package",
"description": "Reserve credits or subscriptions to activate events instantly. Flexible options for any event size.",
"button": "Continue to packages"
},
"createEvent": {
"label": "Prepare event",
"description": "Collect event details, plan tasks, and ensure a smooth flow before the big day.",
"button": "Go to event manager"
}
},
"packages": {
"layout": {
"eyebrow": "Step 2",
"title": "Choose your package",
"subtitle": "Fotospiel supports flexible pricing: single-use credits or subscriptions covering multiple events."
},
"step": {
"title": "Activate the right credits",
"description": "Secure capacity for your next event. Upgrade at any time only pay for what you need."
},
"state": {
"loading": "Loading packages …",
"errorTitle": "Failed to load",
"errorDescription": "Please try again or contact support.",
"emptyTitle": "Catalogue is empty",
"emptyDescription": "No packages are currently available. Reach out to support to enable new offers."
},
"card": {
"subscription": "Subscription",
"creditPack": "Credit pack",
"description": "Ready for your next event right away.",
"descriptionWithPhotos": "Up to {{count}} photos included perfect for vibrant storytelling.",
"active": "Active package",
"select": "Select package",
"onRequest": "On request",
"purchased": "Purchased on {{date}}",
"purchasedUnknown": "unknown date",
"badges": {
"guests": "{{count}} guests",
"days": "{{count}} gallery days",
"photos": "{{count}} photos"
}
},
"features": {
"subscription": "Subscription",
"priority_support": "Priority support",
"custom_domain": "Custom domain",
"analytics": "Analytics",
"team_management": "Team management",
"moderation_tools": "Moderation tools",
"prints": "Print uploads"
},
"cta": {
"billing": {
"label": "Go to billing",
"description": "Already know what you need? Jump straight to the billing area you know.",
"button": "Open billing"
},
"summary": {
"label": "View order summary",
"description": "Review package details and decide whether to pay now or later.",
"button": "Continue to summary"
}
}
},
"summary": {
"layout": {
"eyebrow": "Step 3",
"title": "Order summary",
"subtitle": "Review package, price, and payment before proceeding to the event setup."
},
"footer": {
"back": "Back to package selection"
},
"step": {
"title": "Your selection at a glance",
"description": "Hand off to billing now or continue setup and pay later."
},
"state": {
"loading": "Checking available packages …",
"errorTitle": "Package data temporarily unavailable",
"errorDescription": "Please try again or contact support.",
"missingTitle": "No package selected",
"missingDescription": "Select a package first or refresh if data changed."
},
"details": {
"subscription": "Subscription",
"creditPack": "Credit pack",
"photos": "Up to {{count}} photos",
"galleryDays": "{{count}} gallery days",
"guests": "{{count}} guests",
"infinity": "∞",
"features": {
"subscription": "Subscription",
"priority_support": "Priority support",
"custom_domain": "Custom domain",
"analytics": "Analytics",
"team_management": "Team management",
"moderation_tools": "Moderation tools",
"prints": "Print uploads"
},
"section": {
"photosTitle": "Photos & gallery",
"photosValue": "Up to {{count}} photos, gallery {{days}} days",
"photosUnlimited": "Unlimited photos, flexible gallery",
"guestsTitle": "Guests & team",
"guestsValue": "{{count}} guests included, co-hosts flexible",
"guestsUnlimited": "Unlimited guest list",
"featuresTitle": "Highlights",
"featuresNone": "Standard",
"statusTitle": "Status",
"statusActive": "Already purchased",
"statusInactive": "Not purchased yet"
}
},
"status": {
"pendingTitle": "Payment still pending",
"pendingDescription": "You can start preparing the event. An active package is required before going live."
},
"free": {
"description": "This package is free. Assign it to your tenant and continue immediately.",
"activate": "Activate free package",
"progress": "Activating …",
"successTitle": "Free package activated",
"successDescription": "Credits added. Continue with the setup.",
"failureTitle": "Activation failed",
"errorMessage": "The free package could not be activated."
},
"stripe": {
"sectionTitle": "Card payment (Stripe)",
"heading": "Card payment",
"notReady": "Payment module not ready yet. Please refresh.",
"genericError": "Payment failed. Please try again.",
"missingPaymentId": "Could not confirm payment (missing payment ID).",
"completionFailed": "Purchase not recorded yet. Contact support with your payment confirmation.",
"errorTitle": "Payment failed",
"submitting": "Confirming payment …",
"submit": "Pay now",
"hint": "Secure checkout via Stripe. You'll receive confirmation once recorded.",
"loading": "Loading payment details …",
"unavailableTitle": "Stripe unavailable",
"unavailableDescription": "Stripe could not be initialised.",
"missingKey": "Stripe publishable key missing. Configure VITE_STRIPE_PUBLISHABLE_KEY.",
"intentFailed": "Stripe could not prepare the payment."
},
"paypal": {
"sectionTitle": "PayPal",
"heading": "PayPal",
"createFailed": "PayPal order could not be created. Please try again.",
"captureFailed": "PayPal payment could not be captured. Contact support if funds were withdrawn.",
"errorTitle": "PayPal error",
"genericError": "PayPal reported a problem. Please try again later.",
"missingOrderId": "PayPal did not return an order ID.",
"cancelled": "PayPal payment was cancelled.",
"hint": "PayPal may redirect you briefly to confirm. You'll return automatically afterwards.",
"notConfiguredTitle": "PayPal not configured",
"notConfiguredDescription": "Provide VITE_PAYPAL_CLIENT_ID so hosts can pay with PayPal."
},
"nextStepsTitle": "Next steps",
"nextSteps": [
"Optional: finish billing (Stripe/PayPal) inside the billing area.",
"Complete the event setup and configure tasks, team, and gallery.",
"Check credits before go-live and share your guest link."
],
"cta": {
"billing": {
"label": "Start billing",
"description": "Opens the billing area with Stripe, PayPal, and credit options.",
"button": "Go to billing"
},
"setup": {
"label": "Continue to event setup",
"description": "You can return to billing any time.",
"button": "Continue to setup"
}
}
},
"eventSetup": {
"layout": {
"eyebrow": "Step 4",
"title": "Prepare your first event",
"subtitle": "Fill in a few details, invite co-hosts, and open your guest gallery for the big day."
},
"step": {
"title": "Event setup in minutes",
"description": "We guide you through name, date, mood, and tasks. Afterwards you can moderate photos and support guests live."
},
"tiles": {
"story": {
"title": "Story & mood",
"copy": "Pick imagery, colours, and emotion cards for your event."
},
"team": {
"title": "Organise your team",
"copy": "Invite moderators or photographers and assign roles."
},
"launch": {
"title": "Prepare go-live",
"copy": "Create QR codes, test the gallery, and align the run of show."
}
},
"cta": {
"heading": "Ready for your first event?",
"description": "You're switching to the event manager. Assign tasks, invite members, and test the gallery. You can always return to the welcome journey.",
"button": "Create event"
},
"actions": {
"back": {
"label": "Review packages again",
"description": "Compare pricing or update your current package.",
"button": "Back to packages"
},
"dashboard": {
"label": "Go to dashboard",
"description": "Jump into management to edit existing events.",
"button": "Open dashboard"
},
"events": {
"label": "Event overview",
"description": "Keep track of active and archived events.",
"button": "Open event list"
}
}
}
}