Skip to content
This repository was archived by the owner on May 19, 2026. It is now read-only.

feat(ui): add customer homepage - #60

Closed
NguyenVanThanhTung wants to merge 5 commits into
mainfrom
feat/12-customer-homepage-ui
Closed

feat(ui): add customer homepage#60
NguyenVanThanhTung wants to merge 5 commits into
mainfrom
feat/12-customer-homepage-ui

Conversation

@NguyenVanThanhTung

@NguyenVanThanhTung NguyenVanThanhTung commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Mô tả

Thực hiện xây dựng cấu trúc layout cho trang chủ khách hàng (customer homepage) và tách các giao diện thành các component tái sử dụng. Đồng thời tối ưu hóa cấu trúc component của trang admin và cập nhật service liên quan.

Closes #12

Loại thay đổi

  • ✨ Feature mới
  • 🐛 Bug fix
  • ♻️ Refactor
  • 💄 UI / Style
  • 📝 Documentation
  • 🔧 Chore (config, dependencies,...)

Screenshots / Demo

Screenshot (12) Screenshot (13) Screenshot (14) Screenshot 2026-04-11 233015

Checklist

  • Code chạy không lỗi (bun run dev)
  • TypeScript check pass (bun run check)
  • Lint pass (bun run lint)
  • Đã format code (bun run format)
  • Đã test thủ công chức năng
  • Commit message đúng convention (feat:, fix:, chore:,...)

Ghi chú cho reviewer

  • Lưu ý: Layout của phần customer đã được tách biệt hoàn toàn qua file src/routes/(customer)/+layout.svelte.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added event browsing page with search functionality, category filtering, and pagination support.
    • New customer navigation bar and footer with social media links and app download options.
  • Improvements

    • Simplified admin panel header and sidebar design.
    • Enhanced empty state messaging for search results.
    • Updated authentication layout and styling.
  • Dependencies

    • Added TypeScript definitions for Bun runtime.

@coderabbitai

coderabbitai Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces a new customer-facing event discovery page with search and pagination capabilities, refactors authentication UI layouts, simplifies the admin panel interface, and updates event visibility filtering for admin users. Multiple new customer components are added for event browsing, navigation, and footer display.

Changes

Cohort / File(s) Summary
Customer Event Pages
src/routes/(customer)/+page.svelte, src/routes/(customer)/+page.server.ts, src/routes/(customer)/+layout.svelte
New customer-facing event listing with server-side search query parsing, pagination (limit=8 with page offset), database filtering (published events), and frontend event card grid with category filtering and pagination controls.
Customer Navigation & Layout Components
src/lib/components/customer/layout/CustomerNavbar.svelte, src/lib/components/customer/layout/CustomerFooter.svelte, src/lib/components/customer/layout/CustomerSearchBar.svelte, src/lib/components/customer/layout/AuthNav.svelte, src/lib/components/customer/layout/Logo.svelte
New navbar with logo, search bar, and conditional auth links (login/register vs. logout). Multi-column footer with social links, navigation, payment methods, and app download buttons. Auth-aware navigation bar component with form-based logout.
Customer Event Display Components
src/lib/components/customer/event/EventCard.svelte, src/lib/components/customer/event/CategoryBar.svelte, src/lib/components/customer/event/EmptyState.svelte, src/lib/components/customer/event/Pagination.svelte, src/lib/components/customer/event/Hero.svelte
Event card rendering with image placeholders, badges, and price display. Category filter bar with bindable state. Empty-state fallback UI. Pagination navigation with prev/next links and page number buttons. Hero section with CTAs.
Admin Panel UI Simplification
src/lib/components/admin/layout/AdminHeader.svelte, src/lib/components/admin/layout/AdminSidebar.svelte, src/routes/(admin)/admin/+layout.svelte
Removed dynamic route-based title computation from header (now fixed "Admin Panel" label). Simplified sidebar with emoji icon, reduced navigation styling, removed footer dividers and version badge. Updated responsive padding values.
Admin Event Management Pages
src/routes/(admin)/admin/events/+page.svelte, src/routes/(admin)/admin/events/new/+page.svelte
Collapsed events list page to static empty-state container; removed grid, publish dialogs, and pagination UI. Refactored event creation form: consolidated sections, removed unnecessary icons from labels, moved date/time under dedicated section, updated validation/tooltip handling for submit button, changed reset button variant to destructive.
Admin Section/Seat Components
src/lib/components/admin/event/SectionBuilder.svelte, src/lib/components/admin/event/SectionItem.svelte
Updated row label generation from numbers to strings; changed preview key binding and display logic. Removed auto-sync tracking of layout position changes in SectionItem; reworked remove event dispatching; simplified remove button UI.
Auth Layout & Forms Refactoring
src/routes/(auth)/+layout.svelte, src/routes/(auth)/login/+page.svelte, src/routes/(auth)/register/+page.svelte
Removed bento-style outer layout from auth routes (replaced with direct children render). Wrapped login/register forms with Card component structure. Added invalidateAll() call on successful login. Reduced view-transition animation duration from 0.4s to 0.35s.
Root Layout & Page
src/routes/+layout.server.ts, src/routes/+page.server.ts, src/routes/+page.svelte
Added new root layout server load exporting user from locals. Removed commented-out connectivity tests from old root page load. Deleted old root welcome page (now customer page serves as home).
Event Service & Configuration
src/lib/server/services/event.service.ts, src/lib/server/auth/jwt.ts, package.json, tsconfig.json, .gitignore
Restricted admin event visibility to published + user's own draft events (removed blanket admin inclusion rule). Removed exception variable in JWT catch clause. Added bun-types devDependency. Updated tsconfig types array. Added /.skill to gitignore.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #19 — Modifies authentication UI and pages (login/register routes, auth layout structure, form integration), directly overlapping with auth refactoring in this PR.
  • PR #55 — Modifies admin UI files (AdminHeader.svelte, AdminSidebar.svelte) and event service logic, sharing code-level changes with admin panel simplification and visibility filtering updates.
  • PR #53 — Modifies the same admin event section components (SectionBuilder.svelte, SectionItem.svelte) and package.json, indicating overlapping changes to seat/section management UI.

Suggested reviewers

  • DungxND
  • HungND-flocus

Poem

🐰 Hop hop, the customer page now shines with cards and search so fine,
Events glow in grids of three, with pagination—quite divine!
Admin panel trimmed and neat, no clutter in between,
Auth forms wrapped in cards so sweet, the cleanest ever seen! ✨🎫

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Beyond issue #12's scope, the PR includes admin component refactoring (AdminHeader, AdminSidebar, SectionBuilder, SectionItem), service modifications (event.service.ts visibility logic), auth layout simplification, and various configuration updates (tsconfig.json, package.json, .gitignore). While some refactoring complements the customer UI work, several changes appear tangential to the stated objective. Consider separating admin refactoring and service changes into distinct PRs to maintain focus on the customer homepage feature. If included intentionally, update the PR description to explicitly document these refactoring objectives and rationale.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description follows the template with all major sections completed: description, change type checkboxes, screenshots/demo, and a comprehensive checklist with all items marked as done. Provides adequate context about the implementation.
Linked Issues check ✅ Passed The PR successfully implements all objectives from issue #12: created customer layout with navbar and footer, implemented homepage with search and event cards grid, added server-side pagination, filtering for published events, responsive design, and empty state handling.
Title check ✅ Passed The PR title 'feat(ui): add customer homepage' directly reflects the main objective—implementing the customer-facing homepage UI with layout structure, components, and event listing functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/12-customer-homepage-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/routes/(admin)/admin/events/new/+page.svelte (1)

154-157: ⚠️ Potential issue | 🟠 Major

Enforce the seat-map validation inside handleSubmit(), not only in the button state.

Lines 551-582 prevent the normal click path, but handleSubmit() itself never checks validationIssues, sectionHasOverlap, or sectionDuplicatePrefixes. That means an invalid seat map can still be posted if submission reaches Line 294 through another path.

🛡️ Minimal fix
   async function handleSubmit() {
+    if (validationIssues.length > 0) {
+      toast.error(validationIssues[0]);
+      return;
+    }
+
     const payload = buildPayload();

     // Client-side validation
     const result = createEventSchema.safeParse(payload);
     if (!result.success) {

Also applies to: 294-303, 551-582

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(admin)/admin/events/new/+page.svelte around lines 154 - 157, The
submission handler handleSubmit must enforce seat-map validation rather than
relying only on the button state; modify handleSubmit to check validationIssues,
sectionHasOverlap, and sectionDuplicatePrefixes at the start (or before any
network/post call) and abort/return early if any are present, setting
appropriate user-facing error state (e.g., reuse existing error variables or
call the same validation report used for the button). Locate
handleSectionValidation and the handleSubmit function and add the guard that
mirrors the button-disabled logic (check validationIssues.length > 0 or
sectionHasOverlap === true or sectionDuplicatePrefixes.length > 0) so no
alternate code path can submit an invalid seat map. Ensure the function does not
proceed to the existing submit/post logic when validation fails.
🧹 Nitpick comments (1)
src/lib/components/admin/event/SectionItem.svelte (1)

24-30: Unify remove signaling to avoid accidental double-handling.

Line 25–Line 29 currently notify via both bubbling event and callback. If consumers subscribe to both, remove logic can run twice. Prefer a single contract (event-only or callback-only) for this component.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/components/admin/event/SectionItem.svelte` around lines 24 - 30, The
component currently signals removal twice in handleRemove by both dispatching a
bubbling CustomEvent('remove') via rootEl and calling the onremove callback
prop; unify to a single contract to avoid double-handling. Pick one approach
(preferred: event-only): remove the onremove invocation and any onremove prop
handling so handleRemove only dispatches new CustomEvent('remove', { bubbles:
true, detail: { index }}); alternatively, if you choose callback-only, remove
the rootEl?.dispatchEvent call and keep onremove?.(); update the component
API/docs and any consumers to match the chosen contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/components/admin/layout/AdminHeader.svelte`:
- Around line 33-47: The icon-only buttons lack accessible names; add explicit
accessible labels to the mobile menu and logout buttons so screen readers can
announce them: update the Button using onclick={ontogglesidebar} to include an
aria-label like "Open menu" (or add a visually-hidden <span> label) and update
the Button using onclick={handleLogout} (and disabled={loggingOut}) to include
an aria-label like "Log out" (or a hidden label), ensuring the labels are
present even when the visible text is hidden and that they reference the
existing Button elements and the ontogglesidebar and handleLogout handlers.

In `@src/lib/components/customer/event/CategoryBar.svelte`:
- Around line 18-23: The category toggle buttons lack pressed-state semantics;
update the Button component usage in CategoryBar.svelte (the <Button ...
onclick={() => (activeCategory = cat.value)}> instances) to include
aria-pressed={activeCategory === cat.value} so screen readers receive the
button's selected state; ensure the attribute uses the same activeCategory and
cat.value expressions as the variant logic.

In `@src/lib/components/customer/event/EmptyState.svelte`:
- Line 8: The decorative emoji in the EmptyState.svelte component is announced
by screen readers; update the element with class "empty-icon" inside the
EmptyState component to mark it as purely decorative (e.g., add
aria-hidden="true" and ensure it's not focusable) so assistive tech ignores it;
locate the <div class="empty-icon"> in EmptyState.svelte and add the decorative
attributes accordingly.

In `@src/lib/components/customer/event/EventCard.svelte`:
- Around line 45-49: Parse event.eventDate once into a single Date object and
reuse it for both the day and month badge (e.g., create a parsedDate variable
inside the EventCard component) to avoid double parsing and
timezone/invalid-date drift; also check parsedDate for validity
(isNaN(parsedDate.getTime())) and handle invalid dates gracefully before calling
parsedDate.getDate() and parsedDate.toLocaleString('vi-VN', { month: 'short' }).

In `@src/lib/components/customer/event/Hero.svelte`:
- Around line 23-30: The decorative arrow SVGs in the Hero.svelte component are
currently semantic to assistive tech; update each arrow <svg> element (the CTA
arrow icons) to be non-semantic by adding aria-hidden="true" and optionally
focusable="false" so screen readers ignore them; apply the same change to the
other arrow SVG instance noted (the one around lines 50-52) to keep
accessibility consistent.

In `@src/lib/components/customer/event/Pagination.svelte`:
- Around line 31-38: The active pagination Button needs an aria-current
attribute so screen readers announce the current page: update the Button
instantiation in Pagination.svelte (the block that uses buildPageUrl(num),
currentPage and id "pagination-page-{num}") to include aria-current set to
'page' when num === currentPage (omit or set undefined otherwise) so only the
active page button has aria-current="page".

In `@src/lib/components/customer/layout/AuthNav.svelte`:
- Around line 23-24: The current form uses SvelteKit's use:enhance with
handleLogout but the /api/auth/logout endpoint is a +server.ts JSON endpoint, so
change to an explicit fetch-based submit: remove use:enhance from the <form> in
AuthNav.svelte and instead wire the form's on:submit to handleLogout (or update
handleLogout) so it preventDefault(), performs a fetch POST to
"/api/auth/logout" (including credentials/CSRF as needed), then runs the
existing invalidateAll()/goto flow on success; alternatively, if you want to
keep progressive enhancement, convert the endpoint to a form action in a
+page.server.ts and keep use:enhance, but do not use use:enhance with +server.ts
JSON endpoints.

In `@src/lib/components/customer/layout/CustomerFooter.svelte`:
- Around line 109-142: The App Store and Google Play CTA Buttons (Button with
id="footer-appstore" and Button with id="footer-googleplay") currently use
resolve('/') which incorrectly navigates to the homepage; update those Button
instances to either (a) point href to the real external store URLs (use full
https:// links and add target="_blank" rel="noopener noreferrer") or (b) if
listings are not ready, remove/omit the href and render a disabled state (e.g.,
add disabled or aria-disabled="true" and change the inner text to "Sắp ra mắt")
so they don't behave as links; ensure accessibility attributes reflect the
chosen state and keep the SVG and text blocks intact.

In `@src/lib/server/services/event.service.ts`:
- Line 129: The admin draft visibility check uses a truthy test "if (role ===
'admin' && userId)" which wrongly treats 0 as absent; update the condition to an
explicit nullish check such as "if (role === 'admin' && userId != null)" (or
"userId !== null && userId !== undefined") so that numeric userId values like 0
are handled correctly; locate the check in the admin visibility logic (the
condition referencing role === 'admin' and userId) in event.service.ts and
replace the truthy check with the nullish comparison.

In `@src/routes/`(admin)/admin/events/+page.svelte:
- Around line 18-20: The empty-state div is always rendered; restore the
data-driven branch so admins see their events: in +page.svelte stop rendering
the static placeholder unconditionally and wrap that div in a Svelte conditional
that checks the loaded events collection (use the page load output, e.g. export
let data and data.events or the local events variable returned by your load
function) so the empty message only appears when the collection is empty, and
ensure the existing events management UI (the {`#each` ...} list or component that
renders draft/published events) is rendered when data.events has items.

In `@src/routes/`(auth)/login/+page.svelte:
- Around line 114-119: The password toggle button currently has tabindex={-1}
which removes it from the keyboard tab order and makes the show/hide control
inaccessible; update the button (the element with onclick={() => (showPassword =
!showPassword)} and aria-label bound to showPassword) to be keyboard-focusable
by removing the tabindex attribute or setting tabindex={0} so it remains
reachable to keyboard users and retains the existing aria-label and click
handler.

In `@src/routes/`(auth)/register/+page.svelte:
- Around line 82-83: The outer wrapper div currently uses h-screen which
prevents scrolling on short viewports; update that wrapper (the div containing
Card.Root) to use min-h-screen instead of h-screen so the page can grow and
become scrollable when the form or virtual keyboard exceeds the viewport height,
keeping the Card.Root registration area reachable on phones.
- Around line 138-143: The password toggle button currently has tabindex={-1},
removing it from keyboard focus; remove the tabindex attribute (or change it to
tabindex={0}) on the button so it remains focusable, and ensure the toggle
wiring that uses showPassword (the button with onclick={() => (showPassword =
!showPassword)}) can be activated via keyboard (native button activation is fine
if you keep it a <button>), and keep the existing aria-label updating to reflect
the showPassword state.

In `@src/routes/`(customer)/+layout.svelte:
- Line 25: Remove the stray markup note "🧭 2️⃣ Navbar." from the layout
template in +layout.svelte so it no longer renders on every customer page;
locate the literal text "🧭 2️⃣ Navbar." in the file and delete it (or move it
to a code comment) to keep the note internal rather than part of the rendered
layout.

In `@src/routes/`(customer)/+page.server.ts:
- Around line 24-49: Compute totalEvents and totalPages before querying
eventList, then clamp the requested page (page) into the valid range [1,
totalPages] and recompute offset before running the eventList query;
specifically, move the COUNT query using db.select({ count: count()
}).from(events).where(and(...conditions)) ahead of the eventList query,
calculate totalPages = Math.ceil(totalEvents / limit), set page = Math.max(1,
Math.min(page, totalPages || 1)), recalc offset = (page - 1) * limit, and then
run the db.select()
.from(events).where(and(...conditions)).orderBy(desc(events.createdAt)).limit(limit).offset(offset)
to avoid rendering an empty state for out‑of‑range page values.

In `@src/routes/`(customer)/+page.svelte:
- Around line 10-17: The Event type and rendering need to support category
filtering: add a category (e.g., category: string) to the Event interface and
ensure incoming event objects populate it; create a derived variable (e.g.,
filteredEvents) that filters the existing events array by activeCategory (treat
a special "All" or empty value as no filter) and use filteredEvents in the
template where events are mapped/rendered (instead of raw events) and in the
empty-state check so switching activeCategory updates the displayed cards and
empty state accordingly.
- Around line 171-180: The .view-all-link rule uses an undefined CSS variable
--color-primary-dim; either change that usage to a theme token that actually
exists (e.g., replace --color-primary-dim with the existing primary token used
in your layout, such as --color-primary) in the .view-all-link selector, or add
a definition for --color-primary-dim in the layout’s :root/theme variables so
the value is defined globally; update the .view-all-link class (and/or the
layout variables) accordingly to ensure the link uses a defined color token.

In `@tsconfig.json`:
- Line 14: Remove "bun-types" from the global "types" array in tsconfig.json so
Bun APIs are not exposed to client/universal code, and instead create a
server-only ambient declaration file that adds Bun typings via a triple-slash
directive (e.g., a new .d.ts containing /// <reference types="bun-types" />) and
ensure only server/test compilation picks up that file (so tests like
seat-label.test.ts get Bun types but client code does not); update any
server/test-specific tsconfig or include patterns to reference that new .d.ts as
needed.

---

Outside diff comments:
In `@src/routes/`(admin)/admin/events/new/+page.svelte:
- Around line 154-157: The submission handler handleSubmit must enforce seat-map
validation rather than relying only on the button state; modify handleSubmit to
check validationIssues, sectionHasOverlap, and sectionDuplicatePrefixes at the
start (or before any network/post call) and abort/return early if any are
present, setting appropriate user-facing error state (e.g., reuse existing error
variables or call the same validation report used for the button). Locate
handleSectionValidation and the handleSubmit function and add the guard that
mirrors the button-disabled logic (check validationIssues.length > 0 or
sectionHasOverlap === true or sectionDuplicatePrefixes.length > 0) so no
alternate code path can submit an invalid seat map. Ensure the function does not
proceed to the existing submit/post logic when validation fails.

---

Nitpick comments:
In `@src/lib/components/admin/event/SectionItem.svelte`:
- Around line 24-30: The component currently signals removal twice in
handleRemove by both dispatching a bubbling CustomEvent('remove') via rootEl and
calling the onremove callback prop; unify to a single contract to avoid
double-handling. Pick one approach (preferred: event-only): remove the onremove
invocation and any onremove prop handling so handleRemove only dispatches new
CustomEvent('remove', { bubbles: true, detail: { index }}); alternatively, if
you choose callback-only, remove the rootEl?.dispatchEvent call and keep
onremove?.(); update the component API/docs and any consumers to match the
chosen contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 686a6495-9a5f-4967-8279-bde72baa651b

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd66d4 and c5913b4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • .gitignore
  • package.json
  • src/lib/components/admin/event/SectionBuilder.svelte
  • src/lib/components/admin/event/SectionItem.svelte
  • src/lib/components/admin/layout/AdminHeader.svelte
  • src/lib/components/admin/layout/AdminSidebar.svelte
  • src/lib/components/customer/event/CategoryBar.svelte
  • src/lib/components/customer/event/EmptyState.svelte
  • src/lib/components/customer/event/EventCard.svelte
  • src/lib/components/customer/event/Hero.svelte
  • src/lib/components/customer/event/Pagination.svelte
  • src/lib/components/customer/layout/AuthNav.svelte
  • src/lib/components/customer/layout/CustomerFooter.svelte
  • src/lib/components/customer/layout/CustomerNavbar.svelte
  • src/lib/components/customer/layout/CustomerSearchBar.svelte
  • src/lib/components/customer/layout/Logo.svelte
  • src/lib/server/auth/jwt.ts
  • src/lib/server/services/event.service.ts
  • src/routes/(admin)/admin/+layout.svelte
  • src/routes/(admin)/admin/events/+page.svelte
  • src/routes/(admin)/admin/events/new/+page.svelte
  • src/routes/(auth)/+layout.svelte
  • src/routes/(auth)/login/+page.svelte
  • src/routes/(auth)/register/+page.svelte
  • src/routes/(customer)/+layout.svelte
  • src/routes/(customer)/+page.server.ts
  • src/routes/(customer)/+page.svelte
  • src/routes/+layout.server.ts
  • src/routes/+page.server.ts
  • src/routes/+page.svelte
  • tsconfig.json
💤 Files with no reviewable changes (2)
  • src/routes/+page.svelte
  • src/routes/+page.server.ts

Comment on lines +33 to +47
<Button variant="ghost" size="icon" class="md:hidden" onclick={ontogglesidebar}>
<Menu class="h-5 w-5" />
</Button>

<div class="flex flex-col">
<h2 class="ml-2 font-heading text-lg font-bold tracking-tight text-foreground">
{pageTitle}
</h2>
</div>
<h2 class="text-sm font-semibold text-foreground">Admin Panel</h2>
</div>

<div class="flex items-center gap-2">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<button
{...props}
class="flex items-center gap-2.5 rounded-xl border border-border/50 bg-card px-3 py-1.5 hover:bg-accent/60"
style="transition: all 0.2s var(--ease-bento);"
aria-label="Tài khoản Admin"
>
<Avatar.Root class="h-7 w-7">
<Avatar.Fallback class="bg-primary/10 text-xs font-semibold text-primary">
A
</Avatar.Fallback>
</Avatar.Root>
<span class="hidden text-sm font-medium text-foreground md:inline">Admin</span>
</button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-48 rounded-xl p-1.5">
<DropdownMenu.Group>
<DropdownMenu.Item class="gap-2 rounded-lg" disabled>
<User class="h-4 w-4 text-muted-foreground" />
<span>Hồ sơ</span>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
class="gap-2 rounded-lg text-destructive focus:text-destructive"
onclick={handleLogout}
disabled={loggingOut}
>
{#if loggingOut}
<Loader class="h-4 w-4 animate-spin" />
{:else}
<LogOut class="h-4 w-4" />
{/if}
<span>Đăng xuất</span>
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
<div class="flex items-center gap-2 md:gap-3">
<span class="hidden text-sm text-muted-foreground md:inline">👤 Admin</span>
<Button variant="ghost" size="sm" onclick={handleLogout} disabled={loggingOut}>
{#if loggingOut}
<Loader class="mr-1.5 h-3.5 w-3.5 animate-spin" />
{:else}
<LogOut class="mr-1.5 h-3.5 w-3.5" />
{/if}
<span class="hidden md:inline">Đăng xuất</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add accessible names to the mobile icon buttons.

On small screens both controls are icon-only: the menu button has no label, and the logout text is hidden. Screen readers won't announce a meaningful name for either button.

Suggested tweak
-    <Button variant="ghost" size="icon" class="md:hidden" onclick={ontogglesidebar}>
+    <Button
+      variant="ghost"
+      size="icon"
+      class="md:hidden"
+      onclick={ontogglesidebar}
+      aria-label="Mở menu điều hướng"
+    >
       <Menu class="h-5 w-5" />
     </Button>
@@
-    <Button variant="ghost" size="sm" onclick={handleLogout} disabled={loggingOut}>
+    <Button
+      variant="ghost"
+      size="sm"
+      onclick={handleLogout}
+      disabled={loggingOut}
+      aria-label="Đăng xuất"
+    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button variant="ghost" size="icon" class="md:hidden" onclick={ontogglesidebar}>
<Menu class="h-5 w-5" />
</Button>
<div class="flex flex-col">
<h2 class="ml-2 font-heading text-lg font-bold tracking-tight text-foreground">
{pageTitle}
</h2>
</div>
<h2 class="text-sm font-semibold text-foreground">Admin Panel</h2>
</div>
<div class="flex items-center gap-2">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<button
{...props}
class="flex items-center gap-2.5 rounded-xl border border-border/50 bg-card px-3 py-1.5 hover:bg-accent/60"
style="transition: all 0.2s var(--ease-bento);"
aria-label="Tài khoản Admin"
>
<Avatar.Root class="h-7 w-7">
<Avatar.Fallback class="bg-primary/10 text-xs font-semibold text-primary">
A
</Avatar.Fallback>
</Avatar.Root>
<span class="hidden text-sm font-medium text-foreground md:inline">Admin</span>
</button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-48 rounded-xl p-1.5">
<DropdownMenu.Group>
<DropdownMenu.Item class="gap-2 rounded-lg" disabled>
<User class="h-4 w-4 text-muted-foreground" />
<span>Hồ sơ</span>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
class="gap-2 rounded-lg text-destructive focus:text-destructive"
onclick={handleLogout}
disabled={loggingOut}
>
{#if loggingOut}
<Loader class="h-4 w-4 animate-spin" />
{:else}
<LogOut class="h-4 w-4" />
{/if}
<span>Đăng xuất</span>
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
<div class="flex items-center gap-2 md:gap-3">
<span class="hidden text-sm text-muted-foreground md:inline">👤 Admin</span>
<Button variant="ghost" size="sm" onclick={handleLogout} disabled={loggingOut}>
{#if loggingOut}
<Loader class="mr-1.5 h-3.5 w-3.5 animate-spin" />
{:else}
<LogOut class="mr-1.5 h-3.5 w-3.5" />
{/if}
<span class="hidden md:inline">Đăng xuất</span>
<Button
variant="ghost"
size="icon"
class="md:hidden"
onclick={ontogglesidebar}
aria-label="Mở menu điều hướng"
>
<Menu class="h-5 w-5" />
</Button>
<h2 class="text-sm font-semibold text-foreground">Admin Panel</h2>
</div>
<div class="flex items-center gap-2 md:gap-3">
<span class="hidden text-sm text-muted-foreground md:inline">👤 Admin</span>
<Button
variant="ghost"
size="sm"
onclick={handleLogout}
disabled={loggingOut}
aria-label="Đăng xuất"
>
{`#if` loggingOut}
<Loader class="mr-1.5 h-3.5 w-3.5 animate-spin" />
{:else}
<LogOut class="mr-1.5 h-3.5 w-3.5" />
{/if}
<span class="hidden md:inline">Đăng xuất</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/components/admin/layout/AdminHeader.svelte` around lines 33 - 47, The
icon-only buttons lack accessible names; add explicit accessible labels to the
mobile menu and logout buttons so screen readers can announce them: update the
Button using onclick={ontogglesidebar} to include an aria-label like "Open menu"
(or add a visually-hidden <span> label) and update the Button using
onclick={handleLogout} (and disabled={loggingOut}) to include an aria-label like
"Log out" (or a hidden label), ensuring the labels are present even when the
visible text is hidden and that they reference the existing Button elements and
the ontogglesidebar and handleLogout handlers.

Comment on lines +18 to +23
<Button
id="cat-{cat.value || 'all'}"
variant={activeCategory === cat.value ? 'default' : 'outline'}
size="sm"
onclick={() => (activeCategory = cat.value)}
class="rounded-full whitespace-nowrap"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add pressed-state semantics to category toggles.

These controls behave like a selectable toggle set. Add aria-pressed={activeCategory === cat.value} so state is announced accessibly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/components/customer/event/CategoryBar.svelte` around lines 18 - 23,
The category toggle buttons lack pressed-state semantics; update the Button
component usage in CategoryBar.svelte (the <Button ... onclick={() =>
(activeCategory = cat.value)}> instances) to include
aria-pressed={activeCategory === cat.value} so screen readers receive the
button's selected state; ensure the attribute uses the same activeCategory and
cat.value expressions as the variant logic.

</script>

<div class="empty-state">
<div class="empty-icon">🎭</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hide decorative emoji from assistive tech.

Line 8 should mark the icon as decorative to avoid noisy announcements by screen readers.

Suggested fix
-  <div class="empty-icon">🎭</div>
+  <div class="empty-icon" aria-hidden="true">🎭</div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="empty-icon">🎭</div>
<div class="empty-icon" aria-hidden="true">🎭</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/components/customer/event/EmptyState.svelte` at line 8, The
decorative emoji in the EmptyState.svelte component is announced by screen
readers; update the element with class "empty-icon" inside the EmptyState
component to mark it as purely decorative (e.g., add aria-hidden="true" and
ensure it's not focusable) so assistive tech ignores it; locate the <div
class="empty-icon"> in EmptyState.svelte and add the decorative attributes
accordingly.

Comment on lines +45 to +49
<div class="card-date-badge">
<span class="date-day">{new Date(event.eventDate).getDate()}</span>
<span class="date-month">
{new Date(event.eventDate).toLocaleString('vi-VN', { month: 'short' })}
</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a single parsed date source for the badge to avoid timezone/invalid-date drift.

Line 46 and Line 48 parse event.eventDate independently, which can diverge from the formatted date shown elsewhere and makes invalid values harder to handle consistently.

Suggested fix
 <script lang="ts">
   import { resolve } from '$app/paths';
   import { Badge } from '$lib/components/ui/badge';
@@
   let { event, i, formatDate, formatPrice, getGradient } = $props<{
@@
   }>();
+
+  const badgeDate = $derived(new Date(event.eventDate));
+  const isBadgeDateValid = $derived(!Number.isNaN(badgeDate.getTime()));
 </script>
@@
     <!-- Date badge -->
     <div class="card-date-badge">
-      <span class="date-day">{new Date(event.eventDate).getDate()}</span>
-      <span class="date-month">
-        {new Date(event.eventDate).toLocaleString('vi-VN', { month: 'short' })}
-      </span>
+      {`#if` isBadgeDateValid}
+        <span class="date-day">{badgeDate.getDate()}</span>
+        <span class="date-month">{badgeDate.toLocaleString('vi-VN', { month: 'short' })}</span>
+      {:else}
+        <span class="date-day">--</span>
+        <span class="date-month">---</span>
+      {/if}
     </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="card-date-badge">
<span class="date-day">{new Date(event.eventDate).getDate()}</span>
<span class="date-month">
{new Date(event.eventDate).toLocaleString('vi-VN', { month: 'short' })}
</span>
<div class="card-date-badge">
{`#if` isBadgeDateValid}
<span class="date-day">{badgeDate.getDate()}</span>
<span class="date-month">{badgeDate.toLocaleString('vi-VN', { month: 'short' })}</span>
{:else}
<span class="date-day">--</span>
<span class="date-month">---</span>
{/if}
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/components/customer/event/EventCard.svelte` around lines 45 - 49,
Parse event.eventDate once into a single Date object and reuse it for both the
day and month badge (e.g., create a parsedDate variable inside the EventCard
component) to avoid double parsing and timezone/invalid-date drift; also check
parsedDate for validity (isNaN(parsedDate.getTime())) and handle invalid dates
gracefully before calling parsedDate.getDate() and
parsedDate.toLocaleString('vi-VN', { month: 'short' }).

Comment on lines +23 to +30
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 7l5 5m0 0l-5 5m5-5H6"
/>
</svg>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mark decorative CTA icons as non-semantic.

The arrow icons are visual-only. Add aria-hidden="true" (and optionally focusable="false") so assistive tech doesn’t announce extra noise.

Also applies to: 50-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/components/customer/event/Hero.svelte` around lines 23 - 30, The
decorative arrow SVGs in the Hero.svelte component are currently semantic to
assistive tech; update each arrow <svg> element (the CTA arrow icons) to be
non-semantic by adding aria-hidden="true" and optionally focusable="false" so
screen readers ignore them; apply the same change to the other arrow SVG
instance noted (the one around lines 50-52) to keep accessibility consistent.


<CustomerFooter />
</div>
🧭 2️⃣ Navbar.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove the stray note from the layout body.

Line 25 is plain markup text, so it will render 🧭 2️⃣ Navbar. on every customer page instead of staying as an internal note.

✂️ Minimal fix
-🧭 2️⃣ Navbar.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
🧭 2️⃣ Navbar.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(customer)/+layout.svelte at line 25, Remove the stray markup
note "🧭 2️⃣ Navbar." from the layout template in +layout.svelte so it no longer
renders on every customer page; locate the literal text "🧭 2️⃣ Navbar." in the
file and delete it (or move it to a code comment) to keep the note internal
rather than part of the rendered layout.

Comment on lines +24 to +49
const eventList = await db
.select()
.from(events)
.where(and(...conditions))
.orderBy(desc(events.createdAt)) // Mới nhất lên đầu
.limit(limit)
.offset(offset);

// 4. Đếm tổng số sự kiện để làm phân trang (Pagination)
// Tạm thời lấy length để làm demo nhanh, dự án thật sẽ dùng hàm COUNT() của DB
const totalEvents = await db
.select({ count: count() })
.from(events)
.where(and(...conditions))
.then((result) => result[0]?.count || 0);

const totalPages = Math.ceil(totalEvents / limit);
// 5. Trả dữ liệu về cho Frontend (+page.svelte)
return {
events: eventList,
pagination: {
currentPage: page,
totalPages,
searchQuery,
},
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clamp out-of-range page values before rendering the empty state.

Lines 24-30 fetch with the raw requested page before the total is known. If ?page is larger than the last page, this returns [], and the customer page will show "no events" even though matching published events still exist.

📄 One way to fix it
-  const limit = 8; // Hiển thị 8 sự kiện 1 trang
-  const offset = (page - 1) * limit;
+  const limit = 8; // Hiển thị 8 sự kiện 1 trang

   // 2. Viết điều kiện lọc (Chỉ lấy sự kiện 'published')
   const conditions = [eq(events.status, 'published')];

   // Nếu user có gõ tìm kiếm, thêm điều kiện tìm theo tiêu đề (ilike: không phân biệt hoa thường)
   if (searchQuery) {
     conditions.push(ilike(events.title, `%${escapedQuery}%`));
   }

-  // 3. Query Database
-  const eventList = await db
-    .select()
-    .from(events)
-    .where(and(...conditions))
-    .orderBy(desc(events.createdAt)) // Mới nhất lên đầu
-    .limit(limit)
-    .offset(offset);
-
-  // 4. Đếm tổng số sự kiện để làm phân trang (Pagination)
-  // Tạm thời lấy length để làm demo nhanh, dự án thật sẽ dùng hàm COUNT() của DB
-  const totalEvents = await db
+  // 3. Đếm tổng số sự kiện để làm phân trang (Pagination)
+  const totalEvents = Number(
+    (
+      await db
+        .select({ count: count() })
+        .from(events)
+        .where(and(...conditions))
+    )[0]?.count ?? 0,
+  );
+
+  const totalPages = Math.ceil(totalEvents / limit);
+  const currentPage = totalPages === 0 ? 1 : Math.min(page, totalPages);
+  const offset = (currentPage - 1) * limit;
+
+  // 4. Query Database
+  const eventList = await db
     .select({ count: count() })
     .from(events)
     .where(and(...conditions))
-    .then((result) => result[0]?.count || 0);
-
-  const totalPages = Math.ceil(totalEvents / limit);
+    .orderBy(desc(events.createdAt))
+    .limit(limit)
+    .offset(offset);
+
   // 5. Trả dữ liệu về cho Frontend (+page.svelte)
   return {
     events: eventList,
     pagination: {
-      currentPage: page,
+      currentPage,
       totalPages,
       searchQuery,
     },
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(customer)/+page.server.ts around lines 24 - 49, Compute
totalEvents and totalPages before querying eventList, then clamp the requested
page (page) into the valid range [1, totalPages] and recompute offset before
running the eventList query; specifically, move the COUNT query using
db.select({ count: count() }).from(events).where(and(...conditions)) ahead of
the eventList query, calculate totalPages = Math.ceil(totalEvents / limit), set
page = Math.max(1, Math.min(page, totalPages || 1)), recalc offset = (page - 1)
* limit, and then run the db.select()
.from(events).where(and(...conditions)).orderBy(desc(events.createdAt)).limit(limit).offset(offset)
to avoid rendering an empty state for out‑of‑range page values.

Comment on lines +10 to +17
interface Event {
id: number;
title: string;
eventDate: string | Date;
venue: string;
bannerImageUrl?: string;
min_price: number | string;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The category bar is wired as state only, not as a filter.

Lines 46-47 track activeCategory, but Lines 120-126 still render the raw events array, and the Event type in Lines 10-17 does not expose any category field to filter on. Right now users can switch categories with no change to the cards or empty state.

Also applies to: 46-47, 101-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(customer)/+page.svelte around lines 10 - 17, The Event type and
rendering need to support category filtering: add a category (e.g., category:
string) to the Event interface and ensure incoming event objects populate it;
create a derived variable (e.g., filteredEvents) that filters the existing
events array by activeCategory (treat a special "All" or empty value as no
filter) and use filteredEvents in the template where events are mapped/rendered
(instead of raw events) and in the empty-state check so switching activeCategory
updates the displayed cards and empty state accordingly.

Comment on lines +171 to +180
.view-all-link {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.875rem;
font-weight: 600;
color: var(--color-primary-dim);
transition: gap 0.2s;
text-decoration: none;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a defined color token for the "Xem tất cả" link.

Line 177 references --color-primary-dim, but src/routes/(customer)/+layout.svelte Lines 28-39 never define that variable. The link color will fall back unpredictably instead of using the intended theme color.

🎨 Minimal fix
-    color: var(--color-primary-dim);
+    color: var(--color-primary);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.view-all-link {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.875rem;
font-weight: 600;
color: var(--color-primary-dim);
transition: gap 0.2s;
text-decoration: none;
}
.view-all-link {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.875rem;
font-weight: 600;
color: var(--color-primary);
transition: gap 0.2s;
text-decoration: none;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(customer)/+page.svelte around lines 171 - 180, The
.view-all-link rule uses an undefined CSS variable --color-primary-dim; either
change that usage to a theme token that actually exists (e.g., replace
--color-primary-dim with the existing primary token used in your layout, such as
--color-primary) in the .view-all-link selector, or add a definition for
--color-primary-dim in the layout’s :root/theme variables so the value is
defined globally; update the .view-all-link class (and/or the layout variables)
accordingly to ensure the link uses a defined color token.

Comment thread tsconfig.json
"strict": true,
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"types": ["node", "bun-types"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if Bun globals are currently used in browser/universal code.
# Expected: no Bun globals in component and universal route code.
rg -nP --type=ts --type=svelte -C2 '\bBun\b|\bbun:\w+|\bBunFile\b|\bBun\.'

Repository: Tixtac-Project/tixtac

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Check current tsconfig.json
cat -n tsconfig.json

# Search for Bun globals in source files (using file patterns instead of types)
echo "=== Searching for Bun usage ==="
rg -nP '\bBun\b|\bbun:' --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' --glob='*.svelte' -C1

# Check repository structure
echo "=== Repository structure ==="
ls -la | head -20

Repository: Tixtac-Project/tixtac

Length of output: 2419


🏁 Script executed:

# Verify SvelteKit structure and universal/client code
echo "=== Checking src structure ==="
fd -t f '\.(ts|tsx|js|jsx|svelte)$' src/ | head -20

echo "=== Checking routes structure ==="
fd -t d 'routes' | head -10

echo "=== Checking for client-only markers ==="
rg -l 'browser\s*:?\s*true|ssr\s*:\s*false|@sveltejs/adapter' --glob='*.ts' --glob='*.js' --glob='*.svelte' | head -10

Repository: Tixtac-Project/tixtac

Length of output: 1174


Remove bun-types from the global TypeScript scope to prevent accidentally using Bun APIs in client code.

Currently, bun-types is included in the global types configuration alongside node, which exposes Bun APIs to all files—including client and universal components—during type-checking. While Bun is only used in test files (src/lib/utils/seat-label.test.ts), exposing these types globally could mask type-checking mistakes if Bun APIs are accidentally imported in browser code.

Scope Bun typings to server-only contexts instead:

Suggested fix
-    "types": ["node", "bun-types"]
+    "types": ["node"]

Then add a server-only ambient declaration (example):

/// <reference types="bun-types" />

in a server-only .d.ts file (e.g., near server runtime entrypoints or in a src/lib/types/bun.d.ts file specifically for test/server code).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"types": ["node", "bun-types"]
"types": ["node"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tsconfig.json` at line 14, Remove "bun-types" from the global "types" array
in tsconfig.json so Bun APIs are not exposed to client/universal code, and
instead create a server-only ambient declaration file that adds Bun typings via
a triple-slash directive (e.g., a new .d.ts containing /// <reference
types="bun-types" />) and ensure only server/test compilation picks up that file
(so tests like seat-label.test.ts get Bun types but client code does not);
update any server/test-specific tsconfig or include patterns to reference that
new .d.ts as needed.

@HungND-flocus HungND-flocus changed the title Feat(ui) add customer homepage feat(ui) add customer homepage Apr 11, 2026
@HungND-flocus HungND-flocus changed the title feat(ui) add customer homepage feat(ui): add customer homepage Apr 11, 2026
@NguyenVanThanhTung
NguyenVanThanhTung deleted the feat/12-customer-homepage-ui branch April 11, 2026 16:44
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FE] UI Customer: Trang chủ danh sách sự kiện

1 participant