feat(events): promo codes — organizer editor and server-validated checkout discount #153

Merged
padreug merged 6 commits from feat/events-promo-codes into dev 2026-09-13 16:55:15 +00:00
4 changed files with 132 additions and 0 deletions
Showing only changes of commit a68fed916d - Show all commits

i18n(events): promo code strings for the editor and the purchase dialog

New events.create.promo.* (first key under a create namespace; the rest
of the create dialog remains hardcoded English) and events.purchase.promo.*
in en/es/fr + the types interface.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByAwHU4pRnyE58YocQvAas
Padreug 2026-09-10 12:22:13 +02:00

View file

@ -58,6 +58,27 @@ const messages: LocaleMessages = {
events: { events: {
title: 'Events', title: 'Events',
createNew: 'Create Event', createNew: 'Create Event',
create: {
promo: {
title: 'Promo codes',
hint: 'Percentage discounts buyers can enter at checkout. Codes are never shown publicly.',
empty: 'No promo codes yet.',
code: 'Code',
codePlaceholder: 'EARLYBIRD',
discount: 'Discount %',
maxUses: 'Max uses',
active: 'Active',
used: 'Used {count}',
add: 'Add code',
removeLabel: 'Remove code {code}',
errors: {
required: 'Enter a code',
duplicate: 'Duplicate code',
percentRange: 'Between 1 and 100',
maxUsesMin: 'Must be 1 or more',
},
},
},
noEvents: 'No events found', noEvents: 'No events found',
filters: { filters: {
all: 'All', all: 'All',
@ -137,6 +158,18 @@ const messages: LocaleMessages = {
fiatFailed: 'Card checkout failed', fiatFailed: 'Card checkout failed',
loginRequiredTitle: 'Login required', loginRequiredTitle: 'Login required',
loginRequiredFree: 'Free tickets need an account so seats cannot be claimed anonymously. Log in to get yours.', loginRequiredFree: 'Free tickets need an account so seats cannot be claimed anonymously. Log in to get yours.',
promo: {
label: 'Promo code',
placeholder: 'Enter code',
apply: 'Apply',
remove: 'Remove',
checking: 'Checking…',
applied: '{code} −{percent}%',
discountLine: 'Discount ({code})',
invalid: 'This code is not valid for this purchase.',
unverified: 'Could not verify the code — it will be applied at checkout if valid.',
loginRequiredDiscount: 'This code makes your ticket free. Free tickets need an account — log in to claim it.',
},
contact: { contact: {
title: 'Your details', title: 'Your details',
name: 'Name', name: 'Name',

View file

@ -58,6 +58,27 @@ const messages: LocaleMessages = {
events: { events: {
title: 'Eventos', title: 'Eventos',
createNew: 'Crear evento', createNew: 'Crear evento',
create: {
promo: {
title: 'Códigos promocionales',
hint: 'Descuentos porcentuales que los compradores pueden introducir al pagar. Los códigos nunca se muestran públicamente.',
empty: 'Aún no hay códigos promocionales.',
code: 'Código',
codePlaceholder: 'EARLYBIRD',
discount: 'Descuento %',
maxUses: 'Usos máx.',
active: 'Activo',
used: 'Usado {count}',
add: 'Añadir código',
removeLabel: 'Eliminar el código {code}',
errors: {
required: 'Introduce un código',
duplicate: 'Código duplicado',
percentRange: 'Entre 1 y 100',
maxUsesMin: 'Debe ser 1 o más',
},
},
},
noEvents: 'No se encontraron eventos', noEvents: 'No se encontraron eventos',
filters: { filters: {
all: 'Todas', all: 'Todas',
@ -137,6 +158,18 @@ const messages: LocaleMessages = {
fiatFailed: 'El pago con tarjeta falló', fiatFailed: 'El pago con tarjeta falló',
loginRequiredTitle: 'Inicio de sesión requerido', loginRequiredTitle: 'Inicio de sesión requerido',
loginRequiredFree: 'Los boletos gratuitos requieren una cuenta para evitar reservas anónimas. Inicia sesión para obtener el tuyo.', loginRequiredFree: 'Los boletos gratuitos requieren una cuenta para evitar reservas anónimas. Inicia sesión para obtener el tuyo.',
promo: {
label: 'Código promocional',
placeholder: 'Introduce el código',
apply: 'Aplicar',
remove: 'Quitar',
checking: 'Comprobando…',
applied: '{code} −{percent}%',
discountLine: 'Descuento ({code})',
invalid: 'Este código no es válido para esta compra.',
unverified: 'No se pudo verificar el código; se aplicará al pagar si es válido.',
loginRequiredDiscount: 'Este código hace que tu boleto sea gratis. Los boletos gratuitos requieren una cuenta: inicia sesión para reclamarlo.',
},
contact: { contact: {
title: 'Tus datos', title: 'Tus datos',
name: 'Nombre', name: 'Nombre',

View file

@ -58,6 +58,27 @@ const messages: LocaleMessages = {
events: { events: {
title: 'Événements', title: 'Événements',
createNew: 'Créer un événement', createNew: 'Créer un événement',
create: {
promo: {
title: 'Codes promo',
hint: 'Remises en pourcentage que les acheteurs peuvent saisir au paiement. Les codes ne sont jamais affichés publiquement.',
empty: 'Aucun code promo pour le moment.',
code: 'Code',
codePlaceholder: 'EARLYBIRD',
discount: 'Remise %',
maxUses: 'Utilisations max.',
active: 'Actif',
used: 'Utilisé {count}',
add: 'Ajouter un code',
removeLabel: 'Supprimer le code {code}',
errors: {
required: 'Saisissez un code',
duplicate: 'Code en double',
percentRange: 'Entre 1 et 100',
maxUsesMin: 'Doit être 1 ou plus',
},
},
},
noEvents: 'Aucun événement trouvé', noEvents: 'Aucun événement trouvé',
filters: { filters: {
all: 'Tout', all: 'Tout',
@ -137,6 +158,18 @@ const messages: LocaleMessages = {
fiatFailed: 'Le paiement par carte a échoué', fiatFailed: 'Le paiement par carte a échoué',
loginRequiredTitle: 'Connexion requise', loginRequiredTitle: 'Connexion requise',
loginRequiredFree: 'Les billets gratuits nécessitent un compte pour éviter les réservations anonymes. Connectez-vous pour obtenir le vôtre.', loginRequiredFree: 'Les billets gratuits nécessitent un compte pour éviter les réservations anonymes. Connectez-vous pour obtenir le vôtre.',
promo: {
label: 'Code promo',
placeholder: 'Saisir le code',
apply: 'Appliquer',
remove: 'Retirer',
checking: 'Vérification…',
applied: '{code} −{percent}%',
discountLine: 'Remise ({code})',
invalid: "Ce code n'est pas valable pour cet achat.",
unverified: "Impossible de vérifier le code — il sera appliqué au paiement s'il est valable.",
loginRequiredDiscount: 'Ce code rend votre billet gratuit. Les billets gratuits nécessitent un compte : connectez-vous pour le réclamer.',
},
contact: { contact: {
title: 'Vos coordonnées', title: 'Vos coordonnées',
name: 'Nom', name: 'Nom',

View file

@ -59,6 +59,27 @@ export interface LocaleMessages {
events?: { events?: {
title: string title: string
createNew: string createNew: string
create: {
promo: {
title: string
hint: string
empty: string
code: string
codePlaceholder: string
discount: string
maxUses: string
active: string
used: string
add: string
removeLabel: string
errors: {
required: string
duplicate: string
percentRange: string
maxUsesMin: string
}
}
}
noEvents: string noEvents: string
filters: { filters: {
all: string all: string
@ -112,6 +133,18 @@ export interface LocaleMessages {
fiatFailed: string fiatFailed: string
loginRequiredTitle: string loginRequiredTitle: string
loginRequiredFree: string loginRequiredFree: string
promo: {
label: string
placeholder: string
apply: string
remove: string
checking: string
applied: string
discountLine: string
invalid: string
unverified: string
loginRequiredDiscount: string
}
contact: { contact: {
title: string title: string
name: string name: string