Skip to content
11 changes: 1 addition & 10 deletions src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,7 @@ public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception e
},
cancellationToken);
break;
case not null:
httpContext.Response.StatusCode = 500;
await httpContext.Response.WriteAsJsonAsync(value: new {
Title = "Server Error",
Detail = "An unexpected error occurred.",
Status = StatusCodes.Status500InternalServerError,
},
cancellationToken);
break;
case null:
default:
httpContext.Response.StatusCode = 500;
await httpContext.Response.WriteAsJsonAsync(value: new {
Title = "Server Error",
Expand Down
2 changes: 1 addition & 1 deletion src/Evently.Server/Features/Bookings/BookingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task<ActionResult<Booking>> GetBooking(string bookingId) {
[HttpGet("{bookingId}/preview", Name = "PreviewBooking")]
public async Task<ActionResult<Booking>> PreviewBooking(string bookingId) {
string html = await bookingService.RenderTicket(bookingId);
return Content(html);
return Content(html, "text/html");
}

[HttpGet("", Name = "GetBookings")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public async Task<Booking> CreateBooking(BookingReqDto bookingReqDto) {
throw new ArgumentException(string.Join("\n", values: validationResult.Errors.Select(e => e.ErrorMessage)));
}

bool hasPrevBooking =
await db.Bookings.AnyAsync(b => b.AccountId == booking.AccountId && b.GatheringId == booking.GatheringId && b.CancellationDateTime == null);
if (hasPrevBooking) {
throw new ArgumentException("Account has already booked");
}

booking.BookingId = $"book_{await Nanoid.GenerateAsync(size: 10)}";
await db.Bookings.AddAsync(booking);
await db.SaveChangesAsync();
Expand Down
119 changes: 91 additions & 28 deletions src/Evently.Server/Features/Emails/Views/Ticket.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

<html lang="en" dir="ltr">
<head>
<title>ExpoConnect</title>
<title>Evently</title>

<style>
@@media print {
.print-ticket {
width: 360px;
margin-top: 10rem;
border: 1px solid #0d6efd !important;
box-shadow: none !important;
}

.print-ticket-header {
Expand All @@ -23,48 +24,114 @@
}
</style>
</head>
<body>
<Container>
<p>Your booking has been confirmed.</p>
<body
style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background-color: #f8f9fa;">
<Container
Style="max-width: 400px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 8px 32px rgba(13, 110, 253, 0.15); overflow: hidden;">
<!-- Confirmation Message -->
<div
style="padding: 20px; text-align: center; background: linear-gradient(135deg, #e3f2fd, #f0f8ff); border-bottom: 1px solid #e9ecef;">
<p style="margin: 0; color: #2c5aa0; font-weight: 600; font-size: 16px;">Your booking has been
confirmed!</p>
</div>

<!-- Ticket Header -->
<Table
Style="width: 100%; margin-top: 10px; text-align: center; border-top-left-radius: 1rem;
border-top-right-radius: 1rem; background-color: #0d6efd; padding: 1.5rem; color: white;
height: 40px; border: 1px solid #0d6efd;"
Style="width: 100%; text-align: center; border: none; background: linear-gradient(135deg, #0d6efd, #0056b3);
color: white; margin: 0; border-spacing: 0;"
class="print-ticket-header">
<tr>
<td>
<span style="font-size: 1rem; padding: 1.5rem;">Regular Ticket</span>
<td style="padding: 15px 20px;">
<div style="display: inline-flex; align-items: center; gap: 8px;">
<span style="font-size: 16px; font-weight: 600; letter-spacing: 0.5px;">REGULAR TICKET</span>
</div>
</td>
</tr>
</Table>
<Table Style="padding: 1rem; text-align: center; border: 1px solid #0d6efd; width: 100%; color: black;">

<!-- Ticket Content -->
<Table Style="width: 100%; border: none; margin: 0; border-spacing: 0; background: white;">

<!-- Event Name -->
<tr>
<td>
<a href="@_baseUri/exhibitions/@Gathering.GatheringId">@Gathering.Name</a>
<td style="padding: 20px 20px 10px; text-align: center;">
<a href="@_baseUri/gatherings/@Gathering.GatheringId"
style="color: #0d6efd; font-size: 20px; font-weight: 700;
display: block; line-height: 1.3; padding-bottom: 5px; text-decoration: underline;">
@Gathering.Name
</a>
</td>
</tr>

<!-- Date and Time -->
<tr>
<td>@Day, @StartDate | @StartTime - @EndTime</td>
<td style="padding: 10px 20px; text-align: center;">
<div
style="background: #f8f9fa; border-radius: 8px; padding: 12px; border-left: 4px solid #0d6efd;">
<div style="color: #6c757d; font-size: 12px; font-weight: 500; margin-bottom: 4px;">DATE &
TIME
</div>
<div style="color: #495057; font-size: 16px; font-weight: 600;">@StartDate - @EndDate</div>
</div>
</td>
</tr>

<!-- Location -->
<tr>
<td>@Gathering.Location</td>
</tr>
<tr style="margin-top: 1rem; font-size: 2.5rem;">
<td>@Account.Name</td>
<td style="padding: 10px 20px; text-align: center;">
<div
style="background: #f8f9fa; border-radius: 8px; padding: 12px; border-left: 4px solid #28a745;">
<div style="color: #6c757d; font-size: 12px; font-weight: 500; margin-bottom: 4px;">LOCATION
</div>
<div style="color: #495057; font-size: 14px; font-weight: 500;">@Gathering.Location</div>
</div>
</td>
</tr>
<tr style="text-align: center; font-size: 1.75rem; color: #0d6efd;">
<td>ATTENDEE</td>

<!-- Attendee Name -->
<tr>
<td style="padding: 20px 20px 10px; text-align: center;">
<div
style="background: linear-gradient(135deg, #fff3cd, #ffeaa7); border-radius: 10px; padding: 16px; border: 2px dashed #ffc107;">
<div style="color: #856404; font-size: 12px; font-weight: 600; margin-bottom: 8px;">ATTENDEE
</div>
<div
style="color: #212529; font-size: 24px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">
@Account.UserName
</div>
</div>
</td>
</tr>

<!-- QR Code -->
<tr>
<td>
<img alt="qrcode" src="@QrCodeUrl" width="200px" height="200px"/>
<td style="padding: 20px; text-align: center;">
<div
style="background: white; border: 3px solid #e9ecef; border-radius: 12px; padding: 15px; display: inline-block;">
<img alt="QR Code for ticket verification" src="@QrCodeUrl"
style="width: 180px; height: 180px; display: block;"/>
</div>
</td>
</tr>

<!-- Booking ID -->
<tr>
<td>@Booking.BookingId</td>
<td style="padding: 0 20px 20px; text-align: center;">
<div style="background: #f8f9fa; border-radius: 6px; padding: 8px 12px; display: inline-block;">
<span style="color: #6c757d; font-size: 11px; font-weight: 500;">BOOKING ID: </span>
<span
style="color: #495057; font-size: 12px; font-weight: 600; font-family: 'Courier New', monospace;">@Booking.BookingId</span>
</div>
</td>
</tr>
</Table>

<!-- Footer -->
<div style="background: #f8f9fa; padding: 15px 20px; text-align: center; border-top: 1px solid #e9ecef;">
<p style="margin: 0; color: #6c757d; font-size: 12px;">
Please present this ticket at the event entrance
</p>
</div>
</Container>

</body>
Expand All @@ -81,13 +148,9 @@
}
}

private string Day => Gathering.Start.DayOfWeek.ToString();
private string StartTime => Gathering.Start.ToString("HH:mm");
private string EndTime => Gathering.End.ToString("HH:mm");
private string StartDate => Gathering.Start.ToString("d MMM yyyy");
private string StartDate => Gathering.Start.ToString("ddd, d MMM yyyy, HH:mm");
private string EndDate => Gathering.End.ToString("d MMM yyyy, HH:mm");
private string _baseUri = string.Empty;
private Gathering Gathering => Booking.Gathering ?? new Gathering();
private Account Account => Booking.Account ?? new Account();


}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ public async Task<ActionResult> GetHealthcheck() {
statuses["Server"] = "Healthy";
return Ok(statuses);
}

[HttpGet("middlewares/error-middleware", Name = "TestErrorMiddleware")]
public Task<ActionResult> TestErrorMiddleware() {
throw new ArgumentException("Test Error Middleware");
}
}
3 changes: 3 additions & 0 deletions src/Evently.Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
await dbContext.Database.MigrateAsync();
}

// Use the global exception handler
app.UseExceptionHandler((_) => {});

// To serve the Svelte SPA files
app.UseFileServer();

Expand Down
17 changes: 10 additions & 7 deletions src/evently.client/src/lib/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Icon } from "@iconify/react";
export function Navbar(): JSX.Element {
const location = useLocation();
const router = useRouter();

const identityUserId: string | undefined = useRouteContext({
from: "__root__",
select: (context) => context.account?.id
Expand All @@ -32,25 +32,28 @@ export function Navbar(): JSX.Element {
return (
<div className="bg-base-100 navbar fixed top-0 right-0 left-0 z-50 shadow-sm">
<div className="navbar-start">
{/* Mobile: Hamburger menu and back button */}
<div className="flex items-center gap-2 lg:hidden">
{/* Back button (only show when not on home page) */}
{!isHomePage && (
<button onClick={handleBack} className="btn btn-ghost btn-circle">
<Icon icon="material-symbols:arrow-circle-left-outline-rounded" width="24" height="24" />
<button onClick={handleBack} className="btn btn-ghost btn-circle btn-xs">
<Icon
icon="material-symbols:arrow-circle-left-outline-rounded"
width="24"
height="24"
/>
</button>
)}
</div>

{/* Brand/Logo */}
<Link className="btn btn-ghost hidden text-xl sm:inline" to="/">
Evently
<Link className="btn btn-ghost hidden sm:inline btn-lg" to="/">
<span className="hidden sm:inline">Evently</span>
</Link>
</div>

{/* Desktop menu */}
<div className="navbar-center">
<ul className="menu menu-horizontal px-1 text-xs sm:text-sm">
<ul className="menu menu-horizontal text-xs sm:text-sm">
<li>
<Link to="/gatherings" activeProps={{ className: "underline" }}>
<span>Gatherings</span>
Expand Down
2 changes: 1 addition & 1 deletion src/evently.client/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function App(): JSX.Element {
return (
<div className="h-screen">
<Navbar />
<div className="pt-16 h-full">
<div className="h-full pt-16">
<Outlet />
</div>
<TanStackRouterDevtools />
Expand Down
18 changes: 2 additions & 16 deletions src/evently.client/src/routes/gatherings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Gathering } from "~/lib/domains/entities";
import { getGatherings, type GetGatheringsParams } from "~/lib/services";
import { Card } from "~/lib/components";
import type { PageResult } from "~/lib/domains/models";
import { Icon } from "@iconify/react";

export const Route = createFileRoute("/gatherings/")({
component: GatheringsPage,
Expand Down Expand Up @@ -62,22 +63,7 @@ export function GatheringsPage(): JSX.Element {
<div className="h-full">
<div className="mt-1 flex flex-row justify-center">
<label className="input [w-200px]">
<svg
className="h-[1em] opacity-50"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<g
strokeLinejoin="round"
strokeLinecap="round"
strokeWidth="2.5"
fill="none"
stroke="currentColor"
>
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</g>
</svg>
<Icon icon="material-symbols:search" width="24" height="24" />
<input
type="search"
className="w-full"
Expand Down
Loading