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()}
-
-
- Reserva confirmada
-
-
- ${data.complexName}
-
- |
-
-
-
- |
- ${dateTimeBlock(data.date, data.startTime, data.endTime)}
- |
-
-
- ${divider()}
-
-
- |
+ |
- ${detailRow('Código', `${data.bookingCode}`)}
- ${detailRow('Cancha', `${data.courtName} — ${data.sportName}`)}
- ${detailRow('Cliente', data.customerName)}
- ${formattedPrice ? detailRow('Precio', formattedPrice) : ''}
+
+
+
+
+ |
+ ✓ Reserva confirmada
+ |
+
+
+
+ ${data.complexName}
+
+ |
+
+
+
+
+
+ |
+
+ Playzer
+ |
+
+
+ |
+
|
- ${divider()}
-
- |
-
- 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.
|
`;
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
|