diff --git a/apps/backend/src/emails/booking-confirmation.ts b/apps/backend/src/emails/booking-confirmation.ts index 0aa7e42..fdaa07e 100644 --- a/apps/backend/src/emails/booking-confirmation.ts +++ b/apps/backend/src/emails/booking-confirmation.ts @@ -10,6 +10,20 @@ type BookingEmailData = { price?: number; }; +const APP_BASE_URL = process.env.APP_BASE_URL ?? 'http://localhost:5173'; + +function formatBookingPrice(price: number): string { + if (price === 0) { + return 'Sin cargo'; + } + + return new Intl.NumberFormat('es-AR', { + style: 'currency', + currency: 'ARS', + maximumFractionDigits: Number.isInteger(price) ? 0 : 2, + }).format(price); +} + function formatFriendlyDate(isoDate: string): string { const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(isoDate); if (!match) return isoDate; @@ -27,7 +41,7 @@ const BASE_STYLES = ` body { margin: 0; padding: 0; - background-color: #f4f4f5; + background-color: #edf7f4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } * { @@ -44,11 +58,11 @@ function wrapLayout(content: string) { Playzer - - + +
@@ -103,63 +117,110 @@ function dateTimeBlock(date: string, startTime: string, endTime: string): string } export function bookingConfirmationHtml(data: BookingEmailData): string { - const formattedPrice = - data.price !== undefined && data.price > 0 - ? new Intl.NumberFormat('es-AR', { - style: 'currency', - currency: 'ARS', - maximumFractionDigits: Number.isInteger(data.price) ? 0 : 2, - }).format(data.price) - : undefined; + const formattedPrice = formatBookingPrice(data.price ?? 0); + const friendlyDate = formatFriendlyDate(data.date); const content = ` - ${headerSection()} - - - - - - - - ${divider()} - - - - ${divider()} - - - + + + + `; diff --git a/apps/backend/test/complex/invite-complex-user.test.ts b/apps/backend/test/complex/invite-complex-user.test.ts index 9d13302..839db55 100644 --- a/apps/backend/test/complex/invite-complex-user.test.ts +++ b/apps/backend/test/complex/invite-complex-user.test.ts @@ -80,7 +80,7 @@ test('rejects when the invite target already belongs to the complex', async () = } expect(caught).toBeInstanceOf(ComplexMembersError); - expect((caught as ComplexMembersError).status).toBe(409); + expect((caught as InstanceType).status).toBe(409); expect(transactionMock).not.toHaveBeenCalled(); expect(sendMailMock).not.toHaveBeenCalled(); }); diff --git a/apps/frontend/public/playzer-favicon-512-transparent.png b/apps/frontend/public/playzer-favicon-512-transparent.png new file mode 100644 index 0000000..c4d6d05 Binary files /dev/null and b/apps/frontend/public/playzer-favicon-512-transparent.png differ
- +
${content}
-

- Reserva confirmada -

-

- ${data.complexName} -

-
- ${dateTimeBlock(data.date, data.startTime, data.endTime)} -
+ - ${detailRow('Código', `${data.bookingCode}`)} - ${detailRow('Cancha', `${data.courtName} — ${data.sportName}`)} - ${detailRow('Cliente', data.customerName)} - ${formattedPrice ? detailRow('Precio', formattedPrice) : ''} + + + +
+ + + + +
+ ✓ Reserva confirmada +
+

+ ${data.complexName} +

+
+ + + + + +
+ Playzer + + Playzer +
+
-

- Presentá el código de reserva al llegar al complejo. -
- Si necesitás cancelar o modificar, contactate directamente con el complejo. -

+
+ + + + +
+ + + + + +
+

+ Tu turno +

+

+ ${friendlyDate} +

+

+ ${data.startTime} — ${data.endTime} +

+
+ + + + +
+

Precio del turno

+

${formattedPrice}

+
+
+
-

- Playzer — Reserva de canchas online +

+ + + + + +
+

+ Cancha +

+

+ ${data.courtName} — ${data.sportName} +

+
+

+ Código de reserva +

+

+ ${data.bookingCode} +

+
+
+

+ Presentá el código de reserva al llegar al complejo. +
+ Si necesitás cancelar o modificar, contactate directamente con el complejo.