From fdde0614b34dd05b4578b6b36831e55c49a84ec4 Mon Sep 17 00:00:00 2001 From: Ullas M Date: Thu, 26 Feb 2026 00:59:47 +0530 Subject: [PATCH 1/3] feat: Add skeleton loading states for dashboard pages with global styling and shimmer effect. --- CHANGELOG.md | 43 +++++++++ src/app/dashboard/entries/loading.tsx | 58 ++++++++++++ src/app/dashboard/events-browser/loading.tsx | 69 ++++++++++++++ src/app/dashboard/events/loading.tsx | 53 +++++++++++ src/app/dashboard/loading.tsx | 48 ++++++++-- src/app/dashboard/students/loading.tsx | 52 +++++++++++ src/app/globals.css | 97 +++++++++++++------- src/components/ui/skeleton.tsx | 15 +++ 8 files changed, 397 insertions(+), 38 deletions(-) create mode 100644 src/app/dashboard/entries/loading.tsx create mode 100644 src/app/dashboard/events-browser/loading.tsx create mode 100644 src/app/dashboard/events/loading.tsx create mode 100644 src/app/dashboard/students/loading.tsx create mode 100644 src/components/ui/skeleton.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc28a8..85f80c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,10 @@ This doc captures the main issues encountered while setting up/running the app l - **20th session:** Synced Students dojo filter to URL and reset pagination on filter change (with global navigation loader). - **20th session:** Centralized profile display-name derivation for OAuth and improved profile auto-create/update behavior (role + full_name). +- **21st session:** Added shimmer-based `Skeleton` UI primitive and global CSS animation for consistent skeleton loading states. +- **21st session:** Replaced the dashboard root loading screen with a skeleton layout matching the home bento + list surfaces. +- **21st session:** Added route-level skeleton `loading.tsx` for Entries, Events, Events Browser, and Students. + ## 1) Supabase migration error: `must be owner of table users` **Symptom** @@ -4118,3 +4122,42 @@ This session focused on making student management “dojo-aware” end-to-end (d - `src/lib/auth/profile.ts` - `src/lib/auth/require-role.ts` - `src/app/dashboard/events-browser/actions/index.ts` + +--- + +# Session 21 — Skeleton Loading States + Shimmer UI (2026-02-26) + +This session focused on improving perceived performance by using skeleton placeholders (instead of blank/spinners) across key dashboard routes. + +## 1) Added a `Skeleton` primitive + shimmer animation + +**Change** +- Added a reusable `Skeleton` component backed by a `.shimmer` utility class. +- Added `.shimmer` styles + keyframes in global CSS for both light and dark theme. + +**Where** +- `src/components/ui/skeleton.tsx` +- `src/app/globals.css` + +--- + +## 2) Dashboard root loading now matches dashboard layout + +**Change** +- Replaced the previous dashboard loading component with a structured skeleton: page header, bento-style cards, and list rows. + +**Where** +- `src/app/dashboard/loading.tsx` + +--- + +## 3) Added route-level loading skeletons for major dashboard pages + +**Change** +- Added dedicated route-level `loading.tsx` skeletons so each section shows an immediate, layout-accurate placeholder while server components stream. + +**Where** +- `src/app/dashboard/entries/loading.tsx` +- `src/app/dashboard/events/loading.tsx` +- `src/app/dashboard/events-browser/loading.tsx` +- `src/app/dashboard/students/loading.tsx` diff --git a/src/app/dashboard/entries/loading.tsx b/src/app/dashboard/entries/loading.tsx new file mode 100644 index 0000000..4b64253 --- /dev/null +++ b/src/app/dashboard/entries/loading.tsx @@ -0,0 +1,58 @@ +import { Skeleton } from '@/components/ui/skeleton' + +export default function EntriesLoading() { + return ( +
+
+ + +
+ +
+
+
+ +
+
+ {Array.from({ length: 4 }).map((_, i) => ( +
+
+ +
+
+ + +
+ +
+
+ +
+ ))} +
+
+ +
+
+ +
+
+ {Array.from({ length: 2 }).map((_, i) => ( +
+
+ +
+
+ +
+ +
+
+
+ ))} +
+
+
+
+ ) +} diff --git a/src/app/dashboard/events-browser/loading.tsx b/src/app/dashboard/events-browser/loading.tsx new file mode 100644 index 0000000..9571c98 --- /dev/null +++ b/src/app/dashboard/events-browser/loading.tsx @@ -0,0 +1,69 @@ +import { Skeleton } from '@/components/ui/skeleton' + +export default function EventBrowserLoading() { + return ( +
+
+ + +
+ +
+ {/* Approved Events */} +
+
+ + +
+
+
+ {Array.from({ length: 2 }).map((_, i) => ( +
+
+ +
+
+ + +
+ +
+
+ +
+ ))} +
+
+
+ + {/* Active Events */} +
+
+ + +
+
+
+ {Array.from({ length: 4 }).map((_, i) => ( +
+
+ +
+
+ + +
+ +
+
+ +
+ ))} +
+
+
+ +
+
+ ) +} diff --git a/src/app/dashboard/events/loading.tsx b/src/app/dashboard/events/loading.tsx new file mode 100644 index 0000000..e9411a4 --- /dev/null +++ b/src/app/dashboard/events/loading.tsx @@ -0,0 +1,53 @@ +import { Skeleton } from '@/components/ui/skeleton' + +export default function EventsLoading() { + return ( +
+
+
+ + +
+ +
+ +
+ {Array.from({ length: 5 }).map((_, i) => ( +
+
+ +
+
+ + +
+ +
+
+ +
+ ))} +
+ +
+
+ + +
+
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+ +
+ + +
+
+
+ ))} +
+
+
+ ) +} diff --git a/src/app/dashboard/loading.tsx b/src/app/dashboard/loading.tsx index c464a22..48444e6 100644 --- a/src/app/dashboard/loading.tsx +++ b/src/app/dashboard/loading.tsx @@ -1,9 +1,45 @@ -import { DashboardLoading } from '@/components/dashboard/dashboard-loading' +import { Skeleton } from '@/components/ui/skeleton' -export default function Loading() { - return ( -
- +export default function DashboardLoading() { + return ( +
+
+ + +
+ + {/* Bento Grid Skeleton */} +
+ + + + +
+ +
+
+ + +
+ +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+
+ +
+
+ + +
+ +
+
+ +
+ ))}
- ) +
+
+ ) } diff --git a/src/app/dashboard/students/loading.tsx b/src/app/dashboard/students/loading.tsx new file mode 100644 index 0000000..d43b1eb --- /dev/null +++ b/src/app/dashboard/students/loading.tsx @@ -0,0 +1,52 @@ +import { Skeleton } from '@/components/ui/skeleton' + +export default function StudentsLoading() { + return ( +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+ +
+ {/* Header row */} +
+ + + + +
+ {/* Rows */} + {Array.from({ length: 8 }).map((_, i) => ( +
+ + + + +
+ ))} +
+
+ +
+ + + +
+
+
+
+ ) +} diff --git a/src/app/globals.css b/src/app/globals.css index 6eb9479..f96c6dc 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -11,42 +11,36 @@ html { :root { /* Green / White / Black Theme - Light Mode */ - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --card: hsl(0, 0%, 100%); + --card-foreground: hsl(222.2, 84%, 4.9%); - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover: hsl(0, 0%, 100%); + --popover-foreground: hsl(222.2, 84%, 4.9%); /* EntryDesk Emerald Primary */ --primary: oklch(0.508 0.118 165.612); /* emerald-700 */ --primary-foreground: oklch(1 0 0); - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; + --secondary: hsl(210, 40%, 96.1%); + --secondary-foreground: hsl(222.2, 47.4%, 11.2%); - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted: hsl(210, 40%, 96.1%); + --muted-foreground: hsl(215.4, 16.3%, 46.9%); - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: hsl(210, 40%, 96.1%); + --accent-foreground: hsl(222.2, 47.4%, 11.2%); - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; + --destructive: hsl(0, 84.2%, 60.2%); + --destructive-foreground: hsl(210, 40%, 98%); - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --border: hsl(214.3, 31.8%, 91.4%); + --input: hsl(214.3, 31.8%, 91.4%); + --ring: hsl(222.2, 84%, 4.9%); --radius: 1rem; /* Light Mode Background: Slate 50 (#f8fafc) */ - --background: oklch(0.985 0.002 247.839); - /* Using OKLCH approximation of #f8fafc or just white-ish */ - /* Actually, let's use the Hex directly in the theme mapping, but variables here usually take HSL or OKLCH in shadcn */ - /* However, Tailwind v4 can handle raw values. I'll stick to a clean separate mapping if I can. */ - /* Let's use HSL for these standard ones to be safe or oklch if preferred. */ - /* Slate 50 is approx 210 40% 98% in HSL */ --background: oklch(0.984 0.003 247.858); --foreground: oklch(0.129 0.042 264.695); @@ -88,21 +82,21 @@ html { /* emerald-500 */ --primary-foreground: oklch(0 0 0); - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; + --secondary: hsl(217.2, 32.6%, 17.5%); + --secondary-foreground: hsl(210, 40%, 98%); - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; + --muted: hsl(217.2, 32.6%, 17.5%); + --muted-foreground: hsl(215, 20.2%, 65.1%); - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; + --accent: hsl(217.2, 32.6%, 17.5%); + --accent-foreground: hsl(210, 40%, 98%); - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; + --destructive: hsl(0, 62.8%, 30.6%); + --destructive-foreground: hsl(210, 40%, 98%); - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; + --border: hsl(217.2, 32.6%, 17.5%); + --input: hsl(217.2, 32.6%, 17.5%); + --ring: hsl(212.7, 26.8%, 83.9%); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); @@ -301,6 +295,45 @@ body { box-shadow: 0 14px 32px -24px rgb(0 0 0 / 0.55); } + .shimmer { + position: relative; + overflow: hidden; + background-color: rgb(0 0 0 / 0.06); + } + .dark .shimmer { + background-color: rgb(255 255 255 / 0.05); + } + .shimmer::after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + transform: translateX(-100%); + background-image: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.6) 50%, + transparent 100% + ); + animation: shimmer 1.5s infinite; + z-index: 10; + } + .dark .shimmer::after { + background-image: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.05) 50%, + transparent 100% + ); + } + @keyframes shimmer { + 100% { + transform: translateX(100%); + } + } + .dashboard-shell .dashboard-list>*+* { border-top: 1px solid color-mix(in oklab, var(--color-border) 70%, transparent); } diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..1ee0684 --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } From ace035e5bb56367e71d0da929c89d20b203e6dbb Mon Sep 17 00:00:00 2001 From: Ullas M Date: Thu, 26 Feb 2026 07:30:41 +0530 Subject: [PATCH 2/3] feat: Add a new dashboard page with role-based metrics and navigation for organizers and coaches. --- CHANGELOG.md | 53 +++++++++++ src/app/dashboard/page.tsx | 2 +- src/components/coach/coach-dashboard.tsx | 2 +- .../coach/coach-student-register.tsx | 91 ++++++++++--------- src/components/dashboard/mobile-nav.tsx | 2 +- .../dashboard/responsive-dashboard-frame.tsx | 2 +- 6 files changed, 105 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f80c5..8c0f973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,11 @@ This doc captures the main issues encountered while setting up/running the app l - **21st session:** Replaced the dashboard root loading screen with a skeleton layout matching the home bento + list surfaces. - **21st session:** Added route-level skeleton `loading.tsx` for Entries, Events, Events Browser, and Students. +- **22nd session:** Improved coach “Register athletes” dialog sizing/scrolling to work better on mobile and small viewports. +- **22nd session:** Made coach register filters/action bar responsive (full-width controls, horizontal scroll where needed) and allowed table horizontal scroll. +- **22nd session:** Tuned dashboard mobile header stacking (z-index/borders) and reduced mobile drawer width for better content context. +- **22nd session:** Minor copy polish on dashboard Students metric (“Total Athletes”). + ## 1) Supabase migration error: `must be owner of table users` **Symptom** @@ -4161,3 +4166,51 @@ This session focused on improving perceived performance by using skeleton placeh - `src/app/dashboard/events/loading.tsx` - `src/app/dashboard/events-browser/loading.tsx` - `src/app/dashboard/students/loading.tsx` + +--- + +# Session 22 — Coach Register UX + Mobile Nav Polish (2026-02-26) + +This session focused on tightening the coach registration UX on small screens and polishing the dashboard’s mobile navigation/header layering. + +## 1) Coach “Register athletes” dialog is viewport-friendly + +**Change** +- Updated the register dialog to use viewport-aware width and max-height, with scrolling enabled so the form stays usable on smaller devices. + +**Where** +- `src/components/coach/coach-dashboard.tsx` + +--- + +## 2) Coach register filters/action bar/table are responsive + +**Change** +- Filters bar now stacks nicely on mobile and uses full-width inputs/selects. +- Action controls (event day + participation + add) handle narrow screens via horizontal scrolling and tighter button copy. +- Table container now supports horizontal scrolling with a sensible minimum table width. + +**Where** +- `src/components/coach/coach-student-register.tsx` + +--- + +## 3) Mobile nav + header layering polish + +**Change** +- Reduced mobile drawer width so it doesn’t fully cover the screen on small devices. +- Increased mobile header z-index and refined border/background styles to prevent overlay/clipping issues. + +**Where** +- `src/components/dashboard/mobile-nav.tsx` +- `src/components/dashboard/responsive-dashboard-frame.tsx` + +--- + +## 4) Small dashboard copy improvement + +**Change** +- Renamed Students metric helper text from “Total registered” → “Total Athletes”. + +**Where** +- `src/app/dashboard/page.tsx` diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 1640e3f..b2cbae2 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -293,7 +293,7 @@ export default async function DashboardPage() { Students
{studentsCount ?? 0}
-
Total registered
+
Total Athletes
diff --git a/src/components/coach/coach-dashboard.tsx b/src/components/coach/coach-dashboard.tsx index 85fce33..617451f 100644 --- a/src/components/coach/coach-dashboard.tsx +++ b/src/components/coach/coach-dashboard.tsx @@ -85,7 +85,7 @@ export function CoachDashboard({ event, stats, entries, students, eventDays, doj {!isPastEvent && ( - + Register athletes diff --git a/src/components/coach/coach-student-register.tsx b/src/components/coach/coach-student-register.tsx index d490283..1c8ac88 100644 --- a/src/components/coach/coach-student-register.tsx +++ b/src/components/coach/coach-student-register.tsx @@ -141,18 +141,19 @@ export function CoachStudentRegister({ students, existingStudentIds, eventId, ev /> {/* Filters Bar */} -
- - - setSearchQuery(e.target.value)} - /> +
+
+ + setSearchQuery(e.target.value)} + /> +
- - +
+
+ {/* Event Day Selector (Conditional) */} + {eventDays.length > 0 && ( + + )} + + - )} - - - + +
-
- +
+
diff --git a/src/components/dashboard/mobile-nav.tsx b/src/components/dashboard/mobile-nav.tsx index b88d1dd..1b6d0b4 100644 --- a/src/components/dashboard/mobile-nav.tsx +++ b/src/components/dashboard/mobile-nav.tsx @@ -50,7 +50,7 @@ export function MobileNav({ role, profile, userEmail }: MobileNavProps) { {/* ✅ Required for Radix accessibility */} diff --git a/src/components/dashboard/responsive-dashboard-frame.tsx b/src/components/dashboard/responsive-dashboard-frame.tsx index 9773a8f..a8f6a5b 100644 --- a/src/components/dashboard/responsive-dashboard-frame.tsx +++ b/src/components/dashboard/responsive-dashboard-frame.tsx @@ -152,7 +152,7 @@ export function ResponsiveDashboardFrame({
-
+
From be434887a188ebdd8da2a9d7ab0a385072760ac7 Mon Sep 17 00:00:00 2001 From: Ullas M Date: Thu, 26 Feb 2026 07:34:51 +0530 Subject: [PATCH 3/3] feat: Add loading skeleton UI for the entries dashboard. --- src/app/dashboard/entries/loading.tsx | 82 ++++++++++++++++----------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/app/dashboard/entries/loading.tsx b/src/app/dashboard/entries/loading.tsx index 4b64253..8215584 100644 --- a/src/app/dashboard/entries/loading.tsx +++ b/src/app/dashboard/entries/loading.tsx @@ -2,52 +2,68 @@ import { Skeleton } from '@/components/ui/skeleton' export default function EntriesLoading() { return ( -
-
- - +
+ {/* Header section */} +
+
+
+
+ +
+ +
+ +
-
-
-
- -
-
- {Array.from({ length: 4 }).map((_, i) => ( -
-
- -
-
- - -
- -
-
- -
- ))} + {/* Stats Overview */} +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ +
+ ))} +
+ + {/* Entries List Area */} +
+
+
+ + +
+
+ + + + +
+ {/* Table Skeleton */}
-
- +
+ +
- {Array.from({ length: 2 }).map((_, i) => ( -
-
- + {Array.from({ length: 5 }).map((_, i) => ( +
+
+
+
- + +
-
+
+ + +
))}