Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ 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.

- **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**
Expand Down Expand Up @@ -4118,3 +4127,90 @@ 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`

---

# 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`
74 changes: 74 additions & 0 deletions src/app/dashboard/entries/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Skeleton } from '@/components/ui/skeleton'

export default function EntriesLoading() {
return (
<div className="space-y-8 animate-in fade-in duration-500">
{/* Header section */}
<div className="space-y-3">
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
<div>
<div className="flex flex-wrap items-center gap-2">
<Skeleton className="h-9 w-[250px] sm:w-[350px]" />
</div>
<Skeleton className="h-5 w-[200px] mt-2" />
</div>
<Skeleton className="h-10 w-[140px] rounded-md" />
</div>
</div>

{/* Stats Overview */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="dashboard-surface p-6">
<Skeleton className="h-4 w-[100px] mb-2" />
<Skeleton className="h-8 w-[60px]" />
</div>
))}
</div>

{/* Entries List Area */}
<div className="space-y-4">
<div className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
<div>
<Skeleton className="h-7 w-[80px]" />
<Skeleton className="h-4 w-[220px] mt-1" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="h-10 w-[60px]" />
<Skeleton className="h-10 w-[70px]" />
<Skeleton className="h-10 w-[90px]" />
<Skeleton className="h-10 w-[90px]" />
</div>
</div>

{/* Table Skeleton */}
<div className="dashboard-surface overflow-hidden">
<div className="border-b border-black/5 px-4 py-3 dark:border-white/10 flex items-center justify-between">
<Skeleton className="h-4 w-[120px]" />
<Skeleton className="h-4 w-[80px]" />
</div>
<div className="flex flex-col">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-center justify-between gap-4 p-4 border-b border-border/20 last:border-0">
<div className="flex items-center gap-4 w-full">
<Skeleton className="h-10 w-10 rounded-full shrink-0" />
<div className="space-y-2 w-full max-w-[400px]">
<Skeleton className="h-5 w-[160px]" />
<div className="flex gap-2">
<Skeleton className="h-4 w-[100px]" />
<Skeleton className="h-4 w-[80px]" />
</div>
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<Skeleton className="h-8 w-[80px] rounded-full hidden sm:block" />
<Skeleton className="h-8 w-8 rounded-md" />
</div>
</div>
))}
</div>
</div>
</div>
</div>
)
}
69 changes: 69 additions & 0 deletions src/app/dashboard/events-browser/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Skeleton } from '@/components/ui/skeleton'

export default function EventBrowserLoading() {
return (
<div className="space-y-4 animate-in fade-in duration-500">
<div className="space-y-2 mb-4">
<Skeleton className="h-8 w-[150px] sm:w-[200px]" />
<Skeleton className="h-4 w-[250px] sm:w-[350px]" />
</div>

<div className="space-y-4">
{/* Approved Events */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4 rounded-full" />
<Skeleton className="h-4 w-[120px]" />
</div>
<div className="dashboard-surface overflow-hidden">
<div className="flex flex-col">
{Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="group flex items-center justify-between gap-4 p-3 border-b border-border/20 last:border-0">
<div className="flex items-center gap-3 w-full">
<Skeleton className="h-8 w-8 rounded-md shrink-0" />
<div className="space-y-2 w-full max-w-[400px]">
<div className="flex gap-2">
<Skeleton className="h-4 w-[160px]" />
<Skeleton className="h-3 w-[60px]" />
</div>
<Skeleton className="h-3 w-[220px]" />
</div>
</div>
<Skeleton className="h-8 w-16 rounded-md shrink-0 hidden sm:block" />
</div>
))}
</div>
</div>
</div>

{/* Active Events */}
<div className="space-y-2">
<div className="flex items-center gap-2 pt-2">
<Skeleton className="h-4 w-4 rounded-md" />
<Skeleton className="h-4 w-[100px]" />
</div>
<div className="dashboard-surface overflow-hidden">
<div className="flex flex-col">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="group flex items-center justify-between gap-4 p-3 border-b border-border/20 last:border-0">
<div className="flex items-center gap-3 w-full">
<Skeleton className="h-8 w-8 rounded-md shrink-0" />
<div className="space-y-2 w-full max-w-[400px]">
<div className="flex gap-2">
<Skeleton className="h-4 w-[160px]" />
<Skeleton className="h-3 w-[60px]" />
</div>
<Skeleton className="h-3 w-[220px]" />
</div>
</div>
<Skeleton className="h-8 w-20 rounded-md shrink-0 hidden sm:block" />
</div>
))}
</div>
</div>
</div>

</div>
</div>
)
}
53 changes: 53 additions & 0 deletions src/app/dashboard/events/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Skeleton } from '@/components/ui/skeleton'

export default function EventsLoading() {
return (
<div className="space-y-5 animate-in fade-in duration-500">
<div className="flex items-center justify-between gap-4 mb-4">
<div className="space-y-2 flex-1">
<Skeleton className="h-8 w-[150px] sm:w-[200px]" />
<Skeleton className="h-4 w-full max-w-[350px]" />
</div>
<Skeleton className="h-9 w-[120px] rounded-md hidden sm:block shrink-0" />
</div>

<div className="dashboard-surface p-2 sm:p-3 space-y-2">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-center justify-between gap-4 rounded-xl border border-black/10 p-3.5 dark:border-white/10">
<div className="flex items-center gap-3 w-full">
<Skeleton className="h-9 w-9 rounded-lg shrink-0" />
<div className="space-y-2 w-full max-w-[400px]">
<div className="flex gap-2">
<Skeleton className="h-4 w-[140px]" />
<Skeleton className="h-4 w-[60px] rounded-full" />
</div>
<Skeleton className="h-3 w-[200px] sm:w-[250px]" />
</div>
</div>
<Skeleton className="h-8 w-20 rounded-full shrink-0 hidden sm:block" />
</div>
))}
</div>

<div className="pt-2">
<div className="flex items-center gap-2 mb-3">
<Skeleton className="h-4 w-4 rounded" />
<Skeleton className="h-5 w-[100px]" />
</div>
<div className="dashboard-surface p-2 sm:p-3 space-y-2">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="flex items-center justify-between gap-4 rounded-xl border border-black/10 p-3.5 dark:border-white/10 opacity-70">
<div className="flex items-center gap-3 w-full">
<Skeleton className="h-9 w-9 rounded-lg shrink-0" />
<div className="space-y-2 w-full max-w-[400px]">
<Skeleton className="h-4 w-[160px]" />
<Skeleton className="h-3 w-[220px]" />
</div>
</div>
</div>
))}
</div>
</div>
</div>
)
}
48 changes: 42 additions & 6 deletions src/app/dashboard/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,45 @@
import { DashboardLoading } from '@/components/dashboard/dashboard-loading'
import { Skeleton } from '@/components/ui/skeleton'

export default function Loading() {
return (
<div className="min-h-screen bg-gradient-to-b from-background to-secondary/40">
<DashboardLoading />
export default function DashboardLoading() {
return (
<div className="space-y-8 animate-in fade-in duration-500">
<div className="flex flex-col gap-2 mb-8">
<Skeleton className="h-8 w-[250px] sm:w-[350px]" />
<Skeleton className="h-4 w-[200px] sm:w-[300px]" />
</div>

{/* Bento Grid Skeleton */}
<div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 lg:items-start">
<Skeleton className="h-[140px] w-full rounded-2xl" />
<Skeleton className="h-[140px] w-full rounded-2xl" />
<Skeleton className="h-[140px] w-full rounded-2xl lg:col-span-1 sm:col-span-2" />
<Skeleton className="h-[140px] w-full rounded-2xl" />
</div>

<div className="space-y-4 pt-4">
<div className="flex items-center gap-2 mb-4">
<Skeleton className="h-9 w-9 rounded-lg" />
<Skeleton className="h-6 w-[180px]" />
</div>

<div className="dashboard-surface p-2 sm:p-4 space-y-2 sm:space-y-3">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="flex items-center justify-between gap-4 p-3 rounded-xl border border-border/20">
<div className="flex items-center gap-3 w-full">
<Skeleton className="h-9 w-9 rounded-lg shrink-0" />
<div className="space-y-2 w-full max-w-[400px]">
<div className="flex gap-2">
<Skeleton className="h-4 w-[140px]" />
<Skeleton className="h-4 w-[60px] rounded-full" />
</div>
<Skeleton className="h-3 w-[200px] sm:w-[250px]" />
</div>
</div>
<Skeleton className="h-8 w-20 rounded-full shrink-0 hidden sm:block" />
</div>
))}
</div>
)
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ export default async function DashboardPage() {
<span className="text-xs font-semibold uppercase tracking-wider">Students</span>
</div>
<div className="text-3xl font-bold tabular-nums text-foreground">{studentsCount ?? 0}</div>
<div className="mt-1 text-xs text-muted-foreground font-medium">Total registered</div>
<div className="mt-1 text-xs text-muted-foreground font-medium">Total Athletes</div>
</div>
</Link>

Expand Down
Loading