diff --git a/VERSION b/VERSION index 482f3d312c..5bc7cd46d4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0-beta +1.10.0-beta diff --git a/backend/VERSION b/backend/VERSION new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/app/DomainObjects/Enums/TicketDateDisplayMode.php b/backend/app/DomainObjects/Enums/TicketDateDisplayMode.php new file mode 100644 index 0000000000..1a777de691 --- /dev/null +++ b/backend/app/DomainObjects/Enums/TicketDateDisplayMode.php @@ -0,0 +1,12 @@ + ['nullable', 'integer'], 'ticket_design_settings.footer_text' => ['nullable', 'string', 'max:500'], 'ticket_design_settings.layout_type' => ['nullable', 'string', Rule::in(['default', 'modern'])], + 'ticket_design_settings.date_display_mode' => ['nullable', 'string', Rule::in(TicketDateDisplayMode::valuesArray())], 'ticket_design_settings.enabled' => ['boolean'], // Marketing settings diff --git a/backend/app/Services/Application/Handlers/EventSettings/DTO/UpdateEventSettingsDTO.php b/backend/app/Services/Application/Handlers/EventSettings/DTO/UpdateEventSettingsDTO.php index 6d3c3864f1..ca73dec5a3 100644 --- a/backend/app/Services/Application/Handlers/EventSettings/DTO/UpdateEventSettingsDTO.php +++ b/backend/app/Services/Application/Handlers/EventSettings/DTO/UpdateEventSettingsDTO.php @@ -8,6 +8,7 @@ use HiEvents\DomainObjects\Enums\HomepageBackgroundType; use HiEvents\DomainObjects\Enums\PaymentProviders; use HiEvents\DomainObjects\Enums\PriceDisplayMode; +use HiEvents\DomainObjects\Enums\TicketDateDisplayMode; use HiEvents\DomainObjects\OrganizerDomainObject; class UpdateEventSettingsDTO extends BaseDTO @@ -150,6 +151,7 @@ public static function createWithDefaults( 'logo_image_id' => null, 'footer_text' => null, 'layout_type' => 'classic', + 'date_display_mode' => TicketDateDisplayMode::START_DATE_TIME->value, 'enabled' => true, ], diff --git a/backend/composer.json b/backend/composer.json index e9768d531c..c12f67d49a 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -4,7 +4,7 @@ "description": "hi.events - Ticket selling and event management.", "keywords": ["ticketing", "events"], "license": "AGPL-3.0", - "version": "1.8.0-beta", + "version": "1.10.0-beta", "require": { "php": "^8.2", "ext-intl": "*", diff --git a/backend/lang/es.json b/backend/lang/es.json index 450cb3be08..a3338aefb7 100644 --- a/backend/lang/es.json +++ b/backend/lang/es.json @@ -491,176 +491,184 @@ "Reply to :name": "Responder a :name", "This message was sent via your organizer contact form.": "Este mensaje fue enviado a través de tu formulario de contacto de organizador.", "Your email confirmation code is:": "Tu código de confirmación de email es:", - "Ticket Name A-Z": "", - "Ticket Name Z-A": "", - "Liquid": "", - "Blade": "", - "Order Confirmation": "", - "Attendee Ticket": "", - "Sent to the customer after placing an order": "", - "Sent to each attendee with their ticket": "", - "Abandoned": "", - "Check Ins": "", - "Unknown": "", - "Opted In To Marketing": "", - "Unauthorized": "", - "Configuration assigned successfully.": "", - "Impersonation started": "", - "Not currently impersonating": "", - "Impersonation ended": "", - "No user role found in token": "", - "You cannot modify email templates until your account is verified.": "", - "Due to issues with spam, you must connect a Stripe account before you can modify email templates.": "", - "Invalid template type": "", - "Superadmin user is viewing non-live event with ID :eventId": "", - "Invalid report type.": "", - "Date range must be less than 370 days.": "", - "Attendee updated successfully": "", - "Order updated successfully": "", - "Ticket resent successfully": "", - "Order confirmation resent successfully": "", - "If you have tickets associated with this email, we will send you an email with the details.": "", - "The footer text may not be greater than 500 characters.": "", - "The layout type must be default or modern.": "", - "The mode must be light or dark.": "", - "The background type must be COLOR or MIRROR_COVER_IMAGE.": "", - "First name must be a string": "", - "First name must not exceed 255 characters": "", - "Last name must be a string": "", - "Last name must not exceed 255 characters": "", - "Email must not exceed 255 characters": "", - "Validation failed after multiple attempts: :error": "", - "Your Ticket Details Have Been Changed": "", - "Your Order Details Have Been Changed": "", - "Response from :organizerName": "", - "Your Tickets": "", - "Invalid VAT number format": "", - "The system default configuration cannot be deleted.": "", - "User does not belong to this account": "", - "Impersonation not allowed": "", - "Email template not found": "", - "Unable to verify Stripe signature with any platform": "", - "Order :id not found for event :eventId": "", - "Order is not in a valid status to be abandoned": "", - "Order has already expired": "", - "Attendee not found": "", - "Event not found": "", - "Page must be a positive integer": "", - "Page not found": "", - "Invalid or expired link. Please request a new one.": "", - "This link has expired. Please request a new one.": "", - "SUPERADMIN users cannot be created through the application": "", - "Cannot associate a user with SUPERADMIN role to an account": "", - "View Order & Tickets": "", - "Summer Music Festival 2024": "", - "3 Arena, North Wall Quay, Dublin 1, Ireland": "", - "Join us for an unforgettable evening of live music featuring top artists from around the world.": "", - "Please transfer the total amount to the following bank account within 5 business days.": "", - "Thank you for your purchase! We look forward to seeing you at the event.": "", - "Platform Fee": "", - "Processing charge event": "", - "Charge not in succeeded status, skipping": "", - "Stripe payment not found for charge": "", - "Order not found for charge": "", - "Extracting platform fee for order": "", - "Retrieving balance transaction from Stripe": "", - "No balance transaction found for charge": "", - "Platform fee already stored for this transaction": "", - "Platform fee stored successfully": "", - "Failed to store platform fee": "", - "Ticket Reference": "", - "Order Reference": "", - "The name of the event": "", - "The event start date": "", - "The event start time": "", - "The event end date": "", - "The event end time": "", - "The full event address": "", - "The event venue name": "", - "The venue address line 1": "", - "The venue address line 2": "", - "The venue city": "", - "The venue state or region": "", - "The venue ZIP or postal code": "", - "The venue country code": "", - "The event description": "", - "The organizer\\'s name": "", - "The organizer\\'s email": "", - "The support email address": "", - "Instructions for offline payment": "", - "Message shown after checkout": "", - "Link to view the order summary": "", - "The order reference number": "", - "The total order amount": "", - "The order date": "", - "The first name of the person who placed the order": "", - "The last name of the person who placed the order": "", - "The email of the person who placed the order": "", - "The attendee\\'s full name": "", - "The attendee\\'s email": "", - "The ticket type name": "", - "The ticket price": "", - "Link to view/download the ticket": "", - "Stripe secret key not configured for platform: :platform": "", - "VIES service returned HTTP :status": "", - "VAT number is not valid according to VIES": "", - "Connection error: :error": "", - "Validation error: :error": "", - "VIES service is temporarily busy. Validation will be retried.": "", - "Member State service is temporarily unavailable. Validation will be retried.": "", - "VIES service timed out. Validation will be retried.": "", - "VIES server is busy. Validation will be retried.": "", - "VIES service is unavailable. Validation will be retried.": "", - "VIES service has reached maximum requests. Validation will be retried.": "", - "Invalid VAT number format.": "", - "Invalid requester information.": "", - "VIES validation error: :code": "", - "Please confirm your email address": "", - "Email addresses do not match": "", - "Please confirm the email address": "", - "Ticket Details Changed": "", - "The details on your ticket for **:eventName** have been updated.": "", - "What Changed": "", - "Security Information": "", - "This change was made from IP address: :ipAddress": "", - "If you did not make this change, please contact the event organizer immediately.": "", - "Event Organizer: :organizerName": "", - "Contact: :email": "", - "Order Details Changed": "", - "The details on your order for **:eventName** have been updated.": "", - "We found :count order(s) associated with :email.": "", - "Click the button below to view your tickets and order details.": "", - "View My Tickets": "", - "This link will expire in 24 hours.": "", - "If you did not request this, please ignore this email.": "", - "You have reached your daily message limit. Please try again later or contact support to increase your limits.": "", - "The number of recipients exceeds your account limit. Please contact support to increase your limits.": "", - "Your account tier does not allow links in messages. Please contact support to enable this feature.": "", - "Deleted :count failed jobs": "", - "Failed job not found": "", - "Queued :count jobs for retry": "", - "Job queued for retry": "", - "Message approved and queued for sending": "", - "Promo code not found": "", - "Question not found": "", - "[Action Required] Message Pending Review - :subject": "", - "Message not found": "", - "Message must be in pending review status to be approved": "", - "The :attribute must be a valid URL.": "", - "The :attribute must use http or https protocol.": "", - "The :attribute cannot point to localhost or internal addresses.": "", - "The :attribute cannot use reserved domain names.": "", - "The :attribute cannot point to cloud metadata endpoints.": "", - "The :attribute cannot point to private or internal IP addresses.": "", - "A message has been flagged for review due to eligibility check failures.": "", - "Message Details": "", - "Subject": "", - "Account": "", - "Event": "", - "Message ID": "", - "Eligibility Failures": "", - "Stripe payment account not connected": "", - "No completed paid orders on this account": "", - "Event was created less than 24 hours ago": "", - "Review Message": "" -} \ No newline at end of file + "Ticket Name A-Z": "Nombre del billete A-Z", + "Ticket Name Z-A": "Nombre del billete Z-A", + "Liquid": "Liquid", + "Blade": "Blade", + "Order Confirmation": "Confirmación de pedido", + "Attendee Ticket": "Entrada del asistente", + "Sent to the customer after placing an order": "Enviado al cliente después de realizar un pedido", + "Sent to each attendee with their ticket": "Enviado a cada asistente con su entrada", + "Abandoned": "Abandonado", + "Check Ins": "Registros de entrada", + "Unknown": "Desconocido", + "Opted In To Marketing": "Aceptó recibir marketing", + "Unauthorized": "No autorizado", + "Configuration assigned successfully.": "Configuración asignada correctamente.", + "Impersonation started": "Suplantación iniciada", + "Not currently impersonating": "No se está suplantando actualmente", + "Impersonation ended": "Suplantación finalizada", + "No user role found in token": "No se encontró rol de usuario en el token", + "You cannot modify email templates until your account is verified.": "No puedes modificar plantillas de correo electrónico hasta que tu cuenta esté verificada.", + "Due to issues with spam, you must connect a Stripe account before you can modify email templates.": "Debido a problemas de spam, debes conectar una cuenta de Stripe antes de poder modificar plantillas de correo electrónico.", + "Invalid template type": "Tipo de plantilla no válido", + "Superadmin user is viewing non-live event with ID :eventId": "El superadministrador está viendo un evento no activo con ID :eventId", + "Invalid report type.": "Tipo de informe no válido.", + "Date range must be less than 370 days.": "El rango de fechas debe ser inferior a 370 días.", + "Attendee updated successfully": "Asistente actualizado correctamente", + "Order updated successfully": "Pedido actualizado correctamente", + "Ticket resent successfully": "Entrada reenviada correctamente", + "Order confirmation resent successfully": "Confirmación de pedido reenviada correctamente", + "If you have tickets associated with this email, we will send you an email with the details.": "Si tienes entradas asociadas a este correo electrónico, te enviaremos un correo con los detalles.", + "The footer text may not be greater than 500 characters.": "El texto del pie de página no puede superar los 500 caracteres.", + "The layout type must be default or modern.": "El tipo de diseño debe ser predeterminado o moderno.", + "The mode must be light or dark.": "El modo debe ser claro u oscuro.", + "The background type must be COLOR or MIRROR_COVER_IMAGE.": "El tipo de fondo debe ser COLOR o MIRROR_COVER_IMAGE.", + "First name must be a string": "El nombre debe ser una cadena de texto", + "First name must not exceed 255 characters": "El nombre no debe superar los 255 caracteres", + "Last name must be a string": "El apellido debe ser una cadena de texto", + "Last name must not exceed 255 characters": "El apellido no debe superar los 255 caracteres", + "Email must not exceed 255 characters": "El correo electrónico no debe superar los 255 caracteres", + "Validation failed after multiple attempts: :error": "La validación falló después de varios intentos: :error", + "Your Ticket Details Have Been Changed": "Los detalles de tu entrada han sido modificados", + "Your Order Details Have Been Changed": "Los detalles de tu pedido han sido modificados", + "Response from :organizerName": "Respuesta de :organizerName", + "Your Tickets": "Tus entradas", + "Invalid VAT number format": "Formato de número de IVA no válido", + "The system default configuration cannot be deleted.": "La configuración predeterminada del sistema no se puede eliminar.", + "User does not belong to this account": "El usuario no pertenece a esta cuenta", + "Impersonation not allowed": "Suplantación no permitida", + "Email template not found": "Plantilla de correo electrónico no encontrada", + "Unable to verify Stripe signature with any platform": "No se pudo verificar la firma de Stripe con ninguna plataforma", + "Order :id not found for event :eventId": "Pedido :id no encontrado para el evento :eventId", + "Order is not in a valid status to be abandoned": "El pedido no está en un estado válido para ser abandonado", + "Order has already expired": "El pedido ya ha expirado", + "Attendee not found": "Asistente no encontrado", + "Event not found": "Evento no encontrado", + "Page must be a positive integer": "La página debe ser un número entero positivo", + "Page not found": "Página no encontrada", + "Invalid or expired link. Please request a new one.": "Enlace no válido o expirado. Por favor, solicita uno nuevo.", + "This link has expired. Please request a new one.": "Este enlace ha expirado. Por favor, solicita uno nuevo.", + "SUPERADMIN users cannot be created through the application": "Los usuarios SUPERADMIN no se pueden crear a través de la aplicación", + "Cannot associate a user with SUPERADMIN role to an account": "No se puede asociar un usuario con rol SUPERADMIN a una cuenta", + "View Order & Tickets": "Ver pedido y entradas", + "Summer Music Festival 2024": "Festival de Música de Verano 2024", + "3 Arena, North Wall Quay, Dublin 1, Ireland": "3 Arena, North Wall Quay, Dublín 1, Irlanda", + "Join us for an unforgettable evening of live music featuring top artists from around the world.": "Únete a nosotros para una noche inolvidable de música en vivo con los mejores artistas de todo el mundo.", + "Please transfer the total amount to the following bank account within 5 business days.": "Por favor, transfiere el importe total a la siguiente cuenta bancaria en un plazo de 5 días hábiles.", + "Thank you for your purchase! We look forward to seeing you at the event.": "¡Gracias por tu compra! Esperamos verte en el evento.", + "Platform Fee": "Tarifa de la plataforma", + "Processing charge event": "Procesando evento de cargo", + "Charge not in succeeded status, skipping": "El cargo no tiene estado exitoso, omitiendo", + "Stripe payment not found for charge": "Pago de Stripe no encontrado para el cargo", + "Order not found for charge": "Pedido no encontrado para el cargo", + "Extracting platform fee for order": "Extrayendo tarifa de la plataforma para el pedido", + "Retrieving balance transaction from Stripe": "Recuperando transacción de saldo de Stripe", + "No balance transaction found for charge": "No se encontró transacción de saldo para el cargo", + "Platform fee already stored for this transaction": "La tarifa de la plataforma ya está almacenada para esta transacción", + "Platform fee stored successfully": "Tarifa de la plataforma almacenada correctamente", + "Failed to store platform fee": "Error al almacenar la tarifa de la plataforma", + "Ticket Reference": "Referencia de la entrada", + "Order Reference": "Referencia del pedido", + "The name of the event": "El nombre del evento", + "The event start date": "La fecha de inicio del evento", + "The event start time": "La hora de inicio del evento", + "The event end date": "La fecha de finalización del evento", + "The event end time": "La hora de finalización del evento", + "The full event address": "La dirección completa del evento", + "The event venue name": "El nombre del lugar del evento", + "The venue address line 1": "Dirección del lugar, línea 1", + "The venue address line 2": "Dirección del lugar, línea 2", + "The venue city": "La ciudad del lugar", + "The venue state or region": "El estado o región del lugar", + "The venue ZIP or postal code": "El código postal del lugar", + "The venue country code": "El código de país del lugar", + "The event description": "La descripción del evento", + "The organizer\\'s name": "El nombre del organizador", + "The organizer\\'s email": "El correo electrónico del organizador", + "The support email address": "La dirección de correo de soporte", + "Instructions for offline payment": "Instrucciones para el pago offline", + "Message shown after checkout": "Mensaje mostrado después del pago", + "Link to view the order summary": "Enlace para ver el resumen del pedido", + "The order reference number": "El número de referencia del pedido", + "The total order amount": "El importe total del pedido", + "The order date": "La fecha del pedido", + "The first name of the person who placed the order": "El nombre de la persona que realizó el pedido", + "The last name of the person who placed the order": "El apellido de la persona que realizó el pedido", + "The email of the person who placed the order": "El correo electrónico de la persona que realizó el pedido", + "The attendee\\'s full name": "El nombre completo del asistente", + "The attendee\\'s email": "El correo electrónico del asistente", + "The ticket type name": "El nombre del tipo de entrada", + "The ticket price": "El precio de la entrada", + "Link to view/download the ticket": "Enlace para ver/descargar la entrada", + "Stripe secret key not configured for platform: :platform": "Clave secreta de Stripe no configurada para la plataforma: :platform", + "VIES service returned HTTP :status": "El servicio VIES devolvió HTTP :status", + "VAT number is not valid according to VIES": "El número de IVA no es válido según VIES", + "Connection error: :error": "Error de conexión: :error", + "Validation error: :error": "Error de validación: :error", + "VIES service is temporarily busy. Validation will be retried.": "El servicio VIES está temporalmente ocupado. Se reintentará la validación.", + "Member State service is temporarily unavailable. Validation will be retried.": "El servicio del Estado miembro no está disponible temporalmente. Se reintentará la validación.", + "VIES service timed out. Validation will be retried.": "El servicio VIES ha agotado el tiempo de espera. Se reintentará la validación.", + "VIES server is busy. Validation will be retried.": "El servidor VIES está ocupado. Se reintentará la validación.", + "VIES service is unavailable. Validation will be retried.": "El servicio VIES no está disponible. Se reintentará la validación.", + "VIES service has reached maximum requests. Validation will be retried.": "El servicio VIES ha alcanzado el máximo de solicitudes. Se reintentará la validación.", + "Invalid VAT number format.": "Formato de número de IVA no válido.", + "Invalid requester information.": "Información del solicitante no válida.", + "VIES validation error: :code": "Error de validación VIES: :code", + "Please confirm your email address": "Por favor, confirma tu dirección de correo electrónico", + "Email addresses do not match": "Las direcciones de correo electrónico no coinciden", + "Please confirm the email address": "Por favor, confirma la dirección de correo electrónico", + "Ticket Details Changed": "Detalles de la entrada modificados", + "The details on your ticket for **:eventName** have been updated.": "Los detalles de tu entrada para **:eventName** han sido actualizados.", + "What Changed": "Qué ha cambiado", + "Security Information": "Información de seguridad", + "This change was made from IP address: :ipAddress": "Este cambio se realizó desde la dirección IP: :ipAddress", + "If you did not make this change, please contact the event organizer immediately.": "Si no realizaste este cambio, por favor contacta al organizador del evento inmediatamente.", + "Event Organizer: :organizerName": "Organizador del evento: :organizerName", + "Contact: :email": "Contacto: :email", + "Order Details Changed": "Detalles del pedido modificados", + "The details on your order for **:eventName** have been updated.": "Los detalles de tu pedido para **:eventName** han sido actualizados.", + "We found :count order(s) associated with :email.": "Encontramos :count pedido(s) asociado(s) a :email.", + "Click the button below to view your tickets and order details.": "Haz clic en el botón de abajo para ver tus entradas y detalles del pedido.", + "View My Tickets": "Ver mis entradas", + "This link will expire in 24 hours.": "Este enlace expirará en 24 horas.", + "If you did not request this, please ignore this email.": "Si no solicitaste esto, por favor ignora este correo electrónico.", + "You have reached your daily message limit. Please try again later or contact support to increase your limits.": "Has alcanzado tu límite diario de mensajes. Por favor, inténtalo más tarde o contacta con soporte para aumentar tus límites.", + "The number of recipients exceeds your account limit. Please contact support to increase your limits.": "El número de destinatarios supera el límite de tu cuenta. Por favor, contacta con soporte para aumentar tus límites.", + "Your account tier does not allow links in messages. Please contact support to enable this feature.": "El nivel de tu cuenta no permite enlaces en los mensajes. Por favor, contacta con soporte para habilitar esta función.", + "Deleted :count failed jobs": "Eliminados :count trabajos fallidos", + "Failed job not found": "Trabajo fallido no encontrado", + "Queued :count jobs for retry": ":count trabajos puestos en cola para reintento", + "Job queued for retry": "Trabajo puesto en cola para reintento", + "Message approved and queued for sending": "Mensaje aprobado y puesto en cola para envío", + "Promo code not found": "Código promocional no encontrado", + "Question not found": "Pregunta no encontrada", + "[Action Required] Message Pending Review - :subject": "[Acción requerida] Mensaje pendiente de revisión - :subject", + "Message not found": "Mensaje no encontrado", + "Message must be in pending review status to be approved": "El mensaje debe estar en estado de revisión pendiente para ser aprobado", + "The :attribute must be a valid URL.": "El campo :attribute debe ser una URL válida.", + "The :attribute must use http or https protocol.": "El campo :attribute debe usar protocolo http o https.", + "The :attribute cannot point to localhost or internal addresses.": "El campo :attribute no puede apuntar a localhost o direcciones internas.", + "The :attribute cannot use reserved domain names.": "El campo :attribute no puede usar nombres de dominio reservados.", + "The :attribute cannot point to cloud metadata endpoints.": "El campo :attribute no puede apuntar a endpoints de metadatos en la nube.", + "The :attribute cannot point to private or internal IP addresses.": "El campo :attribute no puede apuntar a direcciones IP privadas o internas.", + "A message has been flagged for review due to eligibility check failures.": "Un mensaje ha sido marcado para revisión debido a fallos en la verificación de elegibilidad.", + "Message Details": "Detalles del mensaje", + "Subject": "Asunto", + "Account": "Cuenta", + "Event": "Evento", + "Message ID": "ID del mensaje", + "Eligibility Failures": "Fallos de elegibilidad", + "Stripe payment account not connected": "Cuenta de pago de Stripe no conectada", + "No completed paid orders on this account": "No hay pedidos pagados completados en esta cuenta", + "Event was created less than 24 hours ago": "El evento fue creado hace menos de 24 horas", + "Review Message": "Revisar mensaje", + "A spot has opened up!": "¡Se ha liberado una plaza!", + "An order has been reserved for you. Click the button below to complete your purchase.": "Se ha reservado un pedido para ti. Haz clic en el botón de abajo para completar tu compra.", + "Complete Your Order": "Completar tu pedido", + "If you are still interested, you may rejoin the waitlist from the event page.": "Si todavía estás interesado, puedes volver a unirte a la lista de espera desde la página del evento.", + "View Event": "Ver evento", + "Your waitlist offer has expired": "Tu oferta de la lista de espera ha expirado", + "We'll notify you as soon as a spot becomes available.": "Te notificaremos en cuanto haya una plaza disponible.", + "You're on the waitlist!": "¡Estás en la lista de espera!" +} diff --git a/backend/tests/Feature/Http/Actions/EmailTemplates/EmailTemplateTokenTest.php b/backend/tests/Feature/Http/Actions/EmailTemplates/EmailTemplateTokenTest.php index 030a2f06f5..4689b16594 100644 --- a/backend/tests/Feature/Http/Actions/EmailTemplates/EmailTemplateTokenTest.php +++ b/backend/tests/Feature/Http/Actions/EmailTemplates/EmailTemplateTokenTest.php @@ -68,11 +68,11 @@ public function test_can_get_order_confirmation_tokens(): void $tokens = $response->json('tokens'); $this->assertNotEmpty($tokens); - $firstNameToken = collect($tokens)->firstWhere('token', '{{ order_first_name }}'); + $firstNameToken = collect($tokens)->firstWhere('token', '{{ order.first_name }}'); $this->assertNotNull($firstNameToken); $this->assertEquals('The first name of the person who placed the order', $firstNameToken['description']); - $lastNameToken = collect($tokens)->firstWhere('token', '{{ order_last_name }}'); + $lastNameToken = collect($tokens)->firstWhere('token', '{{ order.last_name }}'); $this->assertNotNull($lastNameToken); $this->assertEquals('The last name of the person who placed the order', $lastNameToken['description']); } @@ -97,7 +97,7 @@ public function test_can_get_attendee_ticket_tokens(): void $tokens = $response->json('tokens'); $this->assertNotEmpty($tokens); - $attendeeNameToken = collect($tokens)->firstWhere('token', '{{ attendee_name }}'); + $attendeeNameToken = collect($tokens)->firstWhere('token', '{{ attendee.name }}'); $this->assertNotNull($attendeeNameToken); } @@ -128,10 +128,10 @@ public function test_tokens_include_order_specific_tokens(): void $tokens = $response->json('tokens'); $tokenNames = collect($tokens)->pluck('token')->toArray(); - $this->assertContains('{{ event_title }}', $tokenNames); - $this->assertContains('{{ order_number }}', $tokenNames); - $this->assertContains('{{ order_total }}', $tokenNames); - $this->assertContains('{{ organizer_name }}', $tokenNames); + $this->assertContains('{{ event.title }}', $tokenNames); + $this->assertContains('{{ order.number }}', $tokenNames); + $this->assertContains('{{ order.total }}', $tokenNames); + $this->assertContains('{{ organizer.name }}', $tokenNames); } public function test_tokens_have_proper_structure(): void diff --git a/backend/tests/Unit/Http/Request/EventSettings/UpdateEventSettingsRequestTest.php b/backend/tests/Unit/Http/Request/EventSettings/UpdateEventSettingsRequestTest.php new file mode 100644 index 0000000000..0e66005956 --- /dev/null +++ b/backend/tests/Unit/Http/Request/EventSettings/UpdateEventSettingsRequestTest.php @@ -0,0 +1,46 @@ + ['date_display_mode' => $mode]], + (new UpdateEventSettingsRequest)->rules() + ); + + $this->assertFalse( + $validator->errors()->has('ticket_design_settings.date_display_mode'), + "Expected '{$mode}' to be a valid date display mode" + ); + } + } + + public function test_invalid_date_display_mode_is_rejected(): void + { + $validator = Validator::make( + ['ticket_design_settings' => ['date_display_mode' => 'NOT_A_MODE']], + (new UpdateEventSettingsRequest)->rules() + ); + + $this->assertTrue($validator->errors()->has('ticket_design_settings.date_display_mode')); + } + + public function test_date_display_mode_is_optional(): void + { + $validator = Validator::make( + ['ticket_design_settings' => ['accent_color' => '#333333']], + (new UpdateEventSettingsRequest)->rules() + ); + + $this->assertFalse($validator->errors()->has('ticket_design_settings.date_display_mode')); + } +} diff --git a/frontend/package.json b/frontend/package.json index 09e15f7f65..3e0a2de45d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "hievents-frontend", "private": true, - "version": "1.8.0-beta", + "version": "1.10.0-beta", "type": "module", "scripts": { "dev:csr": "vite --port 5678 --host 0.0.0.0", diff --git a/frontend/src/components/common/AttendeeTicket/index.tsx b/frontend/src/components/common/AttendeeTicket/index.tsx index a30dedcc27..e67ceebe04 100644 --- a/frontend/src/components/common/AttendeeTicket/index.tsx +++ b/frontend/src/components/common/AttendeeTicket/index.tsx @@ -3,6 +3,7 @@ import {Button, CopyButton} from "@mantine/core"; import {formatCurrency} from "../../../utilites/currency.ts"; import {t} from "@lingui/macro"; import {prettyDate} from "../../../utilites/dates.ts"; +import {EventDateRange} from "../EventDateRange"; import QRCode from "react-qr-code"; import {IconCopy, IconPrinter, IconLock, IconX} from "@tabler/icons-react"; import {Address, Attendee, Event, Product} from "../../../types.ts"; @@ -32,6 +33,7 @@ export const AttendeeTicket = ({ const ticketDesignSettings = event?.settings?.ticket_design_settings; const accentColor = ticketDesignSettings?.accent_color || '#6B46C1'; const footerText = ticketDesignSettings?.footer_text; + const dateDisplayMode = ticketDesignSettings?.date_display_mode || 'START_DATE_TIME'; const logoUrl = imageUrl('TICKET_LOGO', event?.images); const ticketStyle = { @@ -71,12 +73,16 @@ export const AttendeeTicket = ({
{/* Event Details */}
-
-
{t`Date & Time`}
-
- {prettyDate(event.start_date, event.timezone, true)} + {dateDisplayMode !== 'HIDDEN' && ( +
+
{t`Date & Time`}
+
+ {dateDisplayMode === 'DATE_RANGE' + ? + : prettyDate(event.start_date, event.timezone, true)} +
-
+ )} {event?.organizer?.name && (
{t`Organizer`}
diff --git a/frontend/src/components/routes/event/TicketDesigner/TicketPreview.tsx b/frontend/src/components/routes/event/TicketDesigner/TicketPreview.tsx index 26da7b28b0..e88adebb5a 100644 --- a/frontend/src/components/routes/event/TicketDesigner/TicketPreview.tsx +++ b/frontend/src/components/routes/event/TicketDesigner/TicketPreview.tsx @@ -10,6 +10,7 @@ interface TicketDesignSettings { accent_color: string; logo_image_id: IdParam | null; footer_text: string | null; + date_display_mode: 'START_DATE_TIME' | 'DATE_RANGE' | 'HIDDEN'; enabled: boolean; } @@ -82,6 +83,7 @@ export const TicketPreview = ({settings, eventId, logoUrl}: TicketPreviewProps) accent_color: settings.accent_color, logo_image_id: settings.logo_image_id, footer_text: settings.footer_text, + date_display_mode: settings.date_display_mode, enabled: settings.enabled }, location_details: eventSettings?.location_details || { diff --git a/frontend/src/components/routes/event/TicketDesigner/index.tsx b/frontend/src/components/routes/event/TicketDesigner/index.tsx index 807feb2c13..250d9439a0 100644 --- a/frontend/src/components/routes/event/TicketDesigner/index.tsx +++ b/frontend/src/components/routes/event/TicketDesigner/index.tsx @@ -8,7 +8,7 @@ import {IdParam} from "../../../../types.ts"; import {showSuccess} from "../../../../utilites/notifications.tsx"; import {t} from "@lingui/macro"; import {useForm} from "@mantine/form"; -import {Button, ColorInput, Textarea, Accordion, Stack, Text, Group} from "@mantine/core"; +import {Button, ColorInput, Textarea, Accordion, Stack, Text, Group, Select} from "@mantine/core"; import {IconColorSwatch, IconHelp, IconPrinter} from "@tabler/icons-react"; import {Tooltip} from "../../../common/Tooltip"; import {ImageUploadDropzone} from "../../../common/ImageUploadDropzone"; @@ -21,6 +21,7 @@ interface TicketDesignSettings { accent_color: string; logo_image_id: IdParam; footer_text: string | null; + date_display_mode: 'START_DATE_TIME' | 'DATE_RANGE' | 'HIDDEN'; enabled: boolean; } @@ -39,6 +40,7 @@ const TicketDesigner = () => { accent_color: '#333333', logo_image_id: undefined, footer_text: '', + date_display_mode: 'START_DATE_TIME', enabled: true, } }); @@ -52,6 +54,7 @@ const TicketDesigner = () => { accent_color: settings.accent_color || '#333333', logo_image_id: settings.logo_image_id || undefined, footer_text: settings.footer_text || '', + date_display_mode: settings.date_display_mode || 'START_DATE_TIME', enabled: settings.enabled !== false, }); } @@ -73,6 +76,7 @@ const TicketDesigner = () => { accent_color: values.accent_color, logo_image_id: values.logo_image_id, footer_text: values.footer_text || undefined, + date_display_mode: values.date_display_mode, enabled: values.enabled } }, @@ -177,6 +181,21 @@ const TicketDesigner = () => { {form.values.footer_text?.length || 0} / 500
+ +
+