Conversation
- Generate lib/supabase/database.types.ts from all 19 migrations covering 13 tables, 8 enums (user_role, request_status, payment_status, match_status, session_status, etc.) and 5 RPC functions - Add Database generic to createBrowserClient<Database>() in client.ts - Add Database generic to createServerClient<Database>() in server.ts; also add try/catch around setAll() so Server Components that read cookies do not throw on cookie-write attempts - Add Database generic to createClient<Database>() in admin.ts - Update tsconfig.json with @/ path alias for clean imports This eliminates all manual inline type assertions (as SomeShape | null) and provides compile-time detection of column name typos across the entire codebase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rary Design system (app/globals.css + app/layout.tsx): - Import Outfit font via next/font/google as primary typeface - Define CSS custom properties for full Bauhaus palette: background #F0F0F0, foreground #121212, primary-red #D02020, primary-blue #1040C0, primary-yellow #F0C020 - Add .bauhaus-card, .bauhaus-card-sm, .bauhaus-shadow, .bauhaus-shadow-lg utility classes (thick borders + hard offset shadows) - Add accessible :focus-visible ring, subtle scrollbar, and prefers-reduced-motion support - Wrap root layout in TooltipProvider; mount <Toaster> (Sonner) globally UI component library (components/ui/): - Install and configure shadcn/ui components: alert-dialog, badge, button, card, dialog, dropdown-menu, form, input, label, pagination, select, separator, skeleton, sonner, table, tabs, tooltip - Add custom bauhaus.tsx with BauhausCard, BauhausButton, BauhausBadge variants (hard shadow, thick borders, no border-radius) - components.json added for shadcn/ui CLI configuration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…afe sign-out (C1+C2+C5) next.config.ts — security headers (C1): - X-Frame-Options: DENY (clickjacking protection) - X-Content-Type-Options: nosniff (MIME-sniffing protection) - Referrer-Policy: strict-origin-when-cross-origin - Permissions-Policy: camera=(), microphone=(), geolocation=() - Strict-Transport-Security: max-age=63072000; includeSubDomains; preload - X-DNS-Prefetch-Control: on middleware.ts — role-based enforcement + email verification (C2): - After session refresh, check email_confirmed_at for email/password users; redirect unverified users to /auth/verify - Fetch primary_role from user_profiles on protected routes: /admin/* requires primary_role = 'admin' /tutor/* requires primary_role = 'tutor' | 'admin' - Redirects to /dashboard on role mismatch (no layout-only guard) app/auth/actions.ts — CSRF-safe sign-out (C5): - New Server Action signOut() replaces plain POST to /auth/sign-out - Next.js Server Actions have built-in CSRF protection via the Origin header check; no explicit token needed - app/auth/sign-out/route.ts retained but simplified Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(F1+F3+F4+F5) vercel.json (F1): - Cron job: /api/cron/expire-packages runs daily at 02:00 UTC to automatically expire packages past their end_date .env.example (F4): - Documents all required environment variables with descriptions: Supabase (URL, anon key, service role), WhatsApp number, bank details (NEXT_PUBLIC_BANK_*), CRON_SECRET, SENTRY_DSN placeholder - Developers copy this to .env.local before running the project package.json (F3+F5): - Move @types/luxon to devDependencies (type-only package, not runtime) - Add "typecheck": "tsc --noEmit" script for fast CI type checking - Add "test": "vitest run" and "test:watch": "vitest" for unit tests - Add vitest, @vitest/ui, and related packages vitest.config.ts: - Configure Vitest with @/ path alias and node test environment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ers, and RLS 20260225000004 — create_payment_proofs_bucket: - Private storage bucket 'payment-proofs' with per-user path scoping - RLS policies: owner can insert/select own proofs; service_role has full access; no public reads (signed URLs required) 20260225000005 — add_admin_notes_to_matches: - Add admin_notes text column to matches table for internal annotations 20260226000001 — update_handle_new_user: - Update handle_new_user() trigger to correctly populate user_profiles on sign-up, including for_student_name and timezone defaults 20260226000002 — subjects_grant_select: - Grant SELECT on subjects table to authenticated role so students can load the subject dropdown in the request form 20260226000003 — requests_package_tier: - Add package_tier column to requests table (8 | 12 | 20 sessions) so the package selection is recorded on the request itself 20260226000004 — tutor_profile_fields: - Add experience_years, education, teaching_approach columns to tutor_profiles for richer profile display on student dashboard 20260228000001 — subjects_rls_policy: - Add explicit RLS policy for subjects: all authenticated users can read active subjects; only service_role can mutate seed.sql: - Update seed data to match new schema columns Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The original tutor_update_session() RPC incremented sessions_used
whenever p_status IN ('done', 'no_show_student') without checking the
session's previous status. This meant changing a session from 'done'
to 'no_show_student' would double-increment, silently burning credits.
Migration 20260303000001 replaces the RPC with a guarded version:
- SELECT current status before any mutation
- Only increment sessions_used when transitioning FROM a non-consuming
status TO a consuming status ('done' | 'no_show_student')
- Only decrement (via new decrement_sessions_used helper) when
transitioning FROM a consuming status TO a non-consuming status
- No increment/decrement for no_show_tutor or rescheduled regardless
of previous status
- Existing ceiling guard (sessions_used < sessions_total) is preserved
Also adds decrement_sessions_used(p_request_id) RPC as a safety
valve for admin corrections without direct SQL access.
The TypeScript path in lib/services/sessions.ts already had this guard
via isConsuming() check; the SQL RPC now has parity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…imiting (C3+C4+A4)
lib/services/sessions.ts:
- Add tutorUpdateSessionStatus() with isConsuming() guard
- Add adminOverrideSessionStatus() for admin corrections
- Add rescheduleSession() for admin-triggered reschedules
- All mutations write to audit_logs
lib/services/payments.ts:
- Add expirePackages() — marks packages past end_date as expired and
updates associated request status to 'ended'
- Add getExpiringPackages(days) — returns packages expiring within N
days with student/subject info for admin renewal outreach
- markPaymentPaid() has full rollback logic for cascading status
changes (payment → package → request)
lib/services/requests.ts:
- Add updateRequestStatus() with audit log
- Add createRequest() with Zod validation at the service boundary
lib/services/whatsapp.ts:
- Add buildWhatsAppLink() and message template helpers
lib/utils/request.ts, lib/utils/session.ts:
- Extend STATUS_LABELS, STATUS_COLOURS, SESSION_STATUS_LABELS
- Add SESSION_STATUS_COLOURS, LEVEL_LABELS, getLevelLabel helpers
lib/validators/payment.ts (C3):
- Add markPaidSchema (paymentId, packageId, requestId as z.string().uuid())
- Add rejectPaymentSchema (paymentId UUID, rejectionNote min/max length)
- Schemas used at the top of admin payment server actions
lib/config/pricing.ts (A2):
- Replace CONFIGURE_BEFORE_LAUNCH literals with NEXT_PUBLIC_BANK_*
environment variable reads; falls back gracefully if unset
lib/rate-limit.ts (C4):
- In-memory sliding-window rate limiter with periodic cleanup
- checkRateLimit(key, limit, windowMs) → { success, remaining }
- For production scale, replace with @upstash/ratelimit + Redis
lib/auth/requireAdmin.ts:
- Reusable requireAdmin() guard for all admin server actions
- Throws if user is not authenticated or does not have admin role
lib/config/timezones.ts:
- IANA timezone list for the profile setup and request forms
app/api/leads/route.ts (C4):
- Apply rate limiting: 10 requests per minute per IP
- Return 429 with Retry-After header when limit exceeded
app/api/cron/expire-packages/route.ts (A4):
- Vercel Cron endpoint called daily at 02:00 UTC
- Validates CRON_SECRET header before executing
- Calls expirePackages() and logs count of expired packages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…us treatment (D3) app/admin/page.tsx: - Now an async Server Component fetching live counts in parallel: pending payments, pending tutor approvals, new requests, upcoming sessions, active subjects - Cards show live badge counts with color-coded Bauhaus styling - Subjects management card added app/admin/analytics/page.tsx (D3): - New "Renewals Due (Next 5 Days)" section calling getExpiringPackages(5) - Lists student name, subject, sessions remaining, expiry date - Pending payments, tutor approvals, unmarked sessions widgets app/admin/layout.tsx: - Convert sign-out to use signOut() Server Action (C5 fix) - Mobile nav: add CSS gradient fade indicators on both edges to signal horizontal scrollability (10 nav items on small screens) - Navigation links updated to include subjects app/admin/requests/: - Redesigned with Bauhaus card layout and status badges - RequestFilters.tsx: status, level, subject filter dropdowns - [id]/AssignTutorForm.tsx: tutor selector with subject/level match - [id]/AdminRequestActions.tsx: new component for inline actions - actions.ts: createMatch(), updateRequestStatus() with audit logs app/admin/payments/: - PaymentActions.tsx: mark paid / reject with rejection note modal - PaymentQuickActions.tsx: new quick action bar for payment list - ViewProofButton.tsx: triggers signed URL for proof preview - actions.ts: Zod validation on markPaymentPaid/markPaymentRejected app/admin/tutors/: - Tutor detail shows full profile, subjects, availability - TutorActions.tsx: approve/reject with audit log - TutorFilters.tsx: filter by approved status app/admin/sessions/: - SessionActions.tsx: admin status override with reason - Sessions list shows overdue sessions prominently app/admin/matches/: - Match detail: EditMatchForm (schedule pattern + Meet link), GenerateSessionsForm, WhatsApp template copy buttons - MatchActions.tsx: reassign tutor, edit match app/admin/subjects/: - New subjects management CRUD UI for admin (add/activate/deactivate subjects in the request form) app/admin/users/page.tsx: - User list with role badges and primary role display app/admin/error.tsx, app/admin/loading.tsx: - Error boundary and skeleton loading state for admin routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssword, tutor onboarding app/auth/sign-up/page.tsx: - Full Bauhaus-styled sign-up form with client-side Zod validation - Show/hide password toggle; clear error messages - Redirect to /auth/verify after successful sign-up app/auth/sign-up/tutor/: - Dedicated tutor sign-up flow that sets primary_role = 'tutor' and pre-populates the profile-setup step with tutor fields app/auth/sign-in/page.tsx + SignInForm.tsx: - Redesigned sign-in with Google OAuth button and email/password form - Inline error display for invalid credentials - 'Forgot password?' link app/auth/forgot-password/: - New forgot-password page: sends Supabase password reset email - Success state with resend option app/auth/reset-password/: - New reset-password page: handles magic link callback, prompts for new password with confirmation app/auth/verify/page.tsx: - Enhanced: shows email address, resend instructions, links to Gmail/Outlook - ResendButton.tsx: client component to resend verification email with 60s cooldown to prevent spam app/auth/profile-setup/page.tsx: - Collects display_name, whatsapp_number (normalises +92 prefix), timezone (IANA selector), and for_student_name (parent accounts) - Validates WhatsApp format before saving; redirects to /dashboard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… proof URLs (A1+A3+D4+D5+D8) app/dashboard/page.tsx: - OnboardingChecklist: progress bar + step list tracking profile → request → payment → match → first session; auto-hides when all steps are complete - StatusBanner: request-status-aware banner explaining the current state and next action (payment_pending, ready_to_match, matched, active, paused, ended) - Redirects admin→/admin, tutor→/tutor based on primary_role app/dashboard/packages/[id]/page.tsx (A1+A3+D5): - Replace getPublicUrl() (broken on private buckets) with getPaymentProofSignedUrl() server action (300s signed URL, verifies user owns the payment record) - Rejected payments: show upload form again with rejection reason from payment.rejection_note; student can re-upload proof and enter a corrected reference number - resubmitRejectedPayment() server action resets status → pending app/dashboard/requests/: - RequestForm.tsx: multi-step request form with subject selector, level/exam-board pickers, goals textarea, availability windows, timezone selector - CancelRequestButton.tsx: cancel pending requests with confirmation - actions.ts: createRequest(), cancelRequest() server actions app/dashboard/sessions/page.tsx: - Sessions list sorted by scheduled_start_utc; past sessions shown with status badge; RescheduleButton on upcoming sessions app/dashboard/tutor/[id]/page.tsx (D4): - Read-only tutor profile page accessible to matched students - Shows: display name, bio, subjects by level, experience, education, teaching approach; hides WhatsApp and availability windows - Verifies match relationship before granting access app/dashboard/layout.tsx: - Dashboard layout with nav links and sign-out Server Action app/dashboard/error.tsx, app/dashboard/loading.tsx: - Error boundary and skeleton loading state components/dashboards/: - OnboardingChecklist.tsx: progress bar + step checklist component - StatusBanner.tsx: request status → friendly message + colour variant - NextSessionCard.tsx: tutor name, subject, time in user's timezone, Meet link button; empty state when no upcoming session - PackageSummary.tsx: sessions remaining, renewal alert at ≤3 left or ≤5 days - RescheduleButton.tsx: disabled with tooltip when < 24h to start (B3) - SessionCompleteForm.tsx: toast.success()/toast.error() on submit (G1) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ror boundaries app/tutor/page.tsx: - Next session hero card: time in tutor's timezone (luxon), student name, subject, level, one-tap Meet link, Google Calendar add button - Upcoming session count, completed session count quick stats - Quick links to sessions list, profile edit app/tutor/sessions/page.tsx: - Sessions sorted chronologically; past sessions show status badge - SessionCompleteForm inline: mark done / student no-show / tutor no-show with optional notes; toasts on success/error (G1) app/tutor/profile/TutorProfileForm.tsx: - WeeklyAvailabilityPicker integration for setting availability - Subjects multi-select by level (O Level, A Level, both) - Bio, experience_years, education, teaching_approach fields - Saves to tutor_profiles via updateTutorProfile() server action app/tutor/profile/actions.ts: - updateTutorProfile() validates with Zod and writes audit log app/tutor/layout.tsx: - Nav with sign-out Server Action; mobile-friendly horizontal scroll with fade indicators app/tutor/conduct/page.tsx: - Tutor code of conduct: attendance, punctuality, communication, no-show consequences, Meet link policy app/tutor/error.tsx, app/tutor/loading.tsx: - Error boundary and skeleton loading state for tutor routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app/page.tsx: - Bold uppercase hero headline with geometric color block accent - Thick black borders and 8px hard-shadow (bauhaus-shadow-lg) - How It Works section: 4-step process with numbered geometric squares - Subjects & Levels section with badge grid - Pricing cards for 8/12/20 session tiers with Bauhaus button style - LeadForm and WhatsApp CTA integrated at bottom - Fully responsive: stacks vertically on mobile, side-by-side on lg components/LeadForm.tsx: - Redesigned with Bauhaus input/button style - Client-side Zod validation (name, email, phone, message) - Toast feedback on success/error - Rate-limited by /api/leads (10 req/min) components/WhatsAppCTA.tsx, WhatsAppLink.tsx: - WhatsApp number sourced from NEXT_PUBLIC_WHATSAPP_NUMBER env var - WhatsAppLink builds wa.me URL with optional pre-filled message components/CopyMessageButton.tsx: - Copy-to-clipboard + open WhatsApp in new tab - Clipboard icon → check icon animation on copy components/AdminPagination.tsx: - Reusable pagination component for all admin list pages - Uses shadcn Pagination component; handles page=1 edge case components/WeeklyAvailabilityPicker.tsx: - Interactive day/time slot grid for tutor availability and student availability windows on request form - 7 days × time slot columns; toggleable cells with shift+click range app/error.tsx, app/not-found.tsx: - Global error boundary and 404 page with Bauhaus styling - 404 shows numeric big text block with return-home link Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app/privacy/page.tsx: - Full Privacy Policy covering: data collected (name, email, WhatsApp, payment receipts), how data is used, storage via Supabase (EU/US), data retention, user rights (access, deletion, correction), contact information for data requests - Important for legal compliance when handling student/parent data and payment proof uploads app/terms/page.tsx: - Terms of Service covering: service description, user eligibility, payment terms (bank transfer, 48h verification), refund policy, no-show policy per docs/MVP.md, intellectual property, account termination conditions - Pakistan-jurisdiction clause; links to privacy policy app/help/page.tsx: - FAQ section with 10 common questions across: Getting Started, Sessions, Payments, Technical categories - WhatsApp contact button (pre-filled "I need help" message) - Email support address - Quick links to /policies, /privacy, /terms All three pages use Bauhaus styling and are accessible from the landing page footer and the student dashboard help icon. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tests vitest.config.ts (already committed) configures Vitest with node environment and @/ path alias. lib/services/__tests__/scheduling.test.ts (11 tests): - Generates correct session count for each tier (8/12/20) - Respects tier cap (no extra sessions beyond tierSessions) - Respects start/end date range (no sessions outside the month) - Handles 31-day months correctly - Converts session times to UTC correctly for Asia/Karachi (PKT, +5:00) - Handles DST boundary correctly for America/New_York (EDT/EST) - Returns empty array for unrecognised IANA timezone - Returns empty array for malformed time string (HH:MM expected) - Returns empty array when no days match in date range - Calculates duration_mins correctly in scheduled_end_utc - All 11 tests pass lib/__tests__/rate-limit.test.ts (4 tests): - Allows requests within the configured limit (remaining decrements) - Blocks requests once the limit is exceeded - Resets counter after the window expires (tested with vi.useFakeTimers) - Tracks different IP keys independently - All 4 tests pass playwright.config.ts: - Playwright E2E configuration targeting http://localhost:3000 - Single browser (Chromium) for CI speed; full browser matrix optional - webServer block to auto-start dev server during test runs e2e/: - Initial E2E test stubs for: sign-up flow, student request creation, admin payment approval, tutor session completion Run: npm test (unit) | npm run test:e2e (E2E) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pleted sprint CLAUDE.md: - Add unit test commands: npm test, npm run test:watch - Add typecheck command: npm run typecheck - Update key file locations table with new infrastructure: database.types.ts, rate-limit.ts, OnboardingChecklist, StatusBanner, unit tests, cron jobs, vercel.json/.env.example - Fix Atomic RPCs section: p_package_id → p_request_id (B2) docs/GAP_ANALYSIS.md: - Update summary table with status column (24/34 items done) - Mark all resolved items with ✅ DONE and resolution details: A1-A4, B1-B3, C1-C5, D3-D8, E1-E2, F1+F3-F5, G1-G2, H1 - Mark deferred items: B4 (reminders), D1 (parent accounts), D2 (email), F2 (Sentry), G3 (skeletons), H3 (tables) - Update Recommended Priority Order: all critical/important items struck through; only Sentry and nice-to-haves remain docs/plan-CorvEd.md: - Sprint 4 marked COMPLETE - Section 3 (MVP v0.1 blockers): mark addressed items done - Section 4 (MVP v0.2): mark completed items; testing section done - Section 6 (refinement plan): update step statuses - Section 9 (new): full inventory of files created and modified in this sprint with description of each change advisor_update.md, plan-corvEd.prompt.md: - Planning artifacts for this sprint Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scripts/bauhaus-replace.ps1:
- PowerShell utility script used during the Bauhaus design sprint to
batch-remove dark mode Tailwind classes (dark:bg-*, dark:text-*,
dark:border-*, dark:hover:*) from all .tsx files in app/ and
components/; CorvEd uses a single light-mode Bauhaus palette so
dark mode variants are intentionally removed
- Also standardises specific Bauhaus button and badge class strings
across the codebase for consistency
scripts/fix3.ps1:
- Targeted follow-up script to correct residual class mismatches
after bauhaus-replace.ps1 (e.g. hover:border-indigo-400 →
hover:border-[#1040C0], animate-spin colour fix, button variant
string standardisation)
- Both scripts are idempotent: re-running them on already-updated
files produces no changes
supabase/.branches/_current_branch:
- Supabase CLI internal file tracking the active local branch ("main")
- Committed so the local Supabase environment matches the repo state
when a new developer clones and runs `npx supabase start`
.gitignore additions:
- .claude/ — Claude Code local settings (machine-specific)
- .playwright-mcp/ — Playwright MCP server runtime state
- .serena/ — Serena semantic code nav tool state
- .vscode/ — VS Code MCP config (machine-specific, not project config)
- playwright-report/ — generated Playwright HTML reports
- test-results/ — generated Playwright test artifacts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR overhauls the MVP admin + user dashboards by adding renewal/health visibility, tightening auth/route protection, and standardizing the UI component system (Bauhaus styling + shadcn-style primitives), alongside new backend utilities (rate limiting, cron expiry, typed Supabase clients) and expanded E2E/unit tests.
Changes:
- Adds platform health/admin tooling: package expiry cron + renewal lookups, new admin subjects management, pagination, and richer admin dashboard counts.
- Strengthens auth/security: shared
requireAdmin, role-based middleware + email verification redirect, API route rate limiting, and security headers. - Refactors UI: introduces reusable UI primitives (button/card/table/etc.), consistent styling, toasts/tooltips, plus loading/error/not-found pages and E2E coverage.
Reviewed changes
Copilot reviewed 143 out of 163 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| next.config.ts | Adds global security headers for all routes |
| middleware.ts | Adds role enforcement + email verification checks in middleware |
| lib/validators/request.ts | Updates request validation for structured availability windows + package tier |
| lib/validators/payment.ts | Adds zod schemas for payment-related server actions |
| lib/utils/session.ts | Updates session status labels/colors + formatting utilities |
| lib/utils/request.ts | Adds availability formatting helper and refines status styling constants |
| lib/utils.ts | Adds cn() Tailwind utility (clsx + tailwind-merge) |
| lib/supabase/server.ts | Adds typed server Supabase client + safer cookie writes |
| lib/supabase/client.ts | Adds typed browser Supabase client |
| lib/supabase/admin.ts | Adds typed service-role Supabase admin client |
| lib/services/whatsapp.ts | Adds WhatsApp helpers + PK phone normalization |
| lib/services/requests.ts | Adds request status transition helper + admin request fetch |
| lib/services/payments.ts | Adds package expiry cron logic + expiring package query |
| lib/services/tests/scheduling.test.ts | Adds vitest coverage for session generation logic |
| lib/rate-limit.ts | Adds in-memory rate limiter utility |
| lib/config/timezones.ts | Adds centralized IANA timezone options |
| lib/config/pricing.ts | Pulls payment instruction bank details from env |
| lib/auth/requireAdmin.ts | Introduces shared admin guard for server actions |
| lib/tests/rate-limit.test.ts | Adds vitest coverage for rate limiter |
| e2e/utils.spec.ts | Adds Playwright tests for utility formatting functions |
| e2e/responsive.spec.ts | Adds basic mobile responsiveness checks |
| e2e/request-form.spec.ts | Adds redirect checks for protected request/admin routes |
| e2e/protected-routes.spec.ts | Adds protected-route redirect coverage |
| e2e/policies.spec.ts | Adds policies page rendering assertions |
| e2e/not-found.spec.ts | Adds 404 page assertions |
| e2e/landing.spec.ts | Adds landing page smoke coverage |
| e2e/dashboard-features.spec.ts | Adds dashboard route redirect coverage |
| e2e/auth.spec.ts | Adds auth page smoke checks |
| e2e/accessibility.spec.ts | Adds basic a11y checks (alt text, labels, focus) |
| components/ui/tooltip.tsx | Adds Tooltip primitive wrapper |
| components/ui/tabs.tsx | Adds Tabs primitive wrapper |
| components/ui/table.tsx | Adds Table component wrappers |
| components/ui/sonner.tsx | Adds global toast Toaster configuration |
| components/ui/skeleton.tsx | Adds Skeleton component |
| components/ui/separator.tsx | Adds Separator primitive wrapper |
| components/ui/pagination.tsx | Adds Pagination components |
| components/ui/label.tsx | Adds Label primitive wrapper |
| components/ui/input.tsx | Adds Input component styling |
| components/ui/form.tsx | Adds react-hook-form helpers + accessible field wiring |
| components/ui/dialog.tsx | Adds Dialog primitive wrapper |
| components/ui/card.tsx | Adds Card components |
| components/ui/button.tsx | Adds Button component + variants |
| components/ui/badge.tsx | Adds Badge component + variants |
| components/ui/alert-dialog.tsx | Adds AlertDialog primitive wrapper |
| components/dashboards/StatusBanner.tsx | Adds request status banner helper + component |
| components/dashboards/SessionCompleteForm.tsx | Adds toasts + restyles tutor session completion form |
| components/dashboards/RescheduleButton.tsx | Restyles WhatsApp reschedule CTA |
| components/dashboards/OnboardingChecklist.tsx | Adds onboarding checklist component |
| components/dashboards/NextSessionCard.tsx | Restyles next-session card + improves link a11y |
| components/WhatsAppLink.tsx | Restyles WhatsApp link component |
| components/WhatsAppCTA.tsx | Restyles WhatsApp CTA |
| components/WeeklyAvailabilityPicker.tsx | Adds interactive weekly availability picker |
| components/CopyMessageButton.tsx | Restyles copy/open WhatsApp buttons |
| components/AdminPagination.tsx | Adds pagination component for admin lists |
| components.json | Adds shadcn/ui config metadata |
| app/tutor/sessions/page.tsx | Restyles tutor sessions list |
| app/tutor/profile/page.tsx | Changes tutor profile reads to admin client (scoped by user) + restyles |
| app/tutor/profile/actions.ts | Uses admin client for tutor profile mutations (scoped by user) |
| app/tutor/loading.tsx | Adds tutor loading skeleton UI |
| app/tutor/layout.tsx | Adds persistent tutor nav + server-action sign out |
| app/tutor/error.tsx | Adds tutor route error UI |
| app/not-found.tsx | Adds global 404 page |
| app/layout.tsx | Adds global TooltipProvider + Toaster |
| app/globals.css | Adds base styling, focus ring, scrollbar, motion preferences |
| app/error.tsx | Adds global error page |
| app/dashboard/tutor/[id]/page.tsx | Adds read-only student view of assigned tutor profile |
| app/dashboard/sessions/page.tsx | Restyles student sessions list + improves link a11y |
| app/dashboard/requests/actions.ts | Adds student request cancellation action |
| app/dashboard/requests/[id]/CancelRequestButton.tsx | Adds cancel request UI |
| app/dashboard/packages/new/page.tsx | Adds tier query param support + restyles package selection |
| app/dashboard/packages/actions.ts | Adds signed URL generation + rejected payment resubmission |
| app/dashboard/loading.tsx | Adds dashboard loading skeleton UI |
| app/dashboard/layout.tsx | Adds persistent student nav + role redirects + server-action sign out |
| app/dashboard/error.tsx | Adds dashboard route error UI |
| app/auth/verify/page.tsx | Restyles verify page + adds geometric panel |
| app/auth/verify/ResendButton.tsx | Adds resend verification CTA |
| app/auth/sign-out/route.ts | Adds GET support and refactors sign-out handler |
| app/auth/sign-in/page.tsx | Restyles sign-in page + geometric panel |
| app/auth/reset-password/page.tsx | Adds reset password page |
| app/auth/profile-setup/page.tsx | Restyles profile setup and swaps to Bauhaus UI components |
| app/auth/forgot-password/page.tsx | Adds forgot password page |
| app/auth/actions.ts | Adds shared sign-out server action |
| app/api/leads/route.ts | Adds IP-based rate limiting to lead submission |
| app/api/cron/expire-packages/route.ts | Adds cron endpoint to expire packages |
| app/admin/tutors/page.tsx | Restyles tutors directory page |
| app/admin/tutors/actions.ts | Refactors to shared requireAdmin |
| app/admin/tutors/[id]/page.tsx | Restyles tutor detail page |
| app/admin/tutors/TutorFilters.tsx | Restyles tutor filters |
| app/admin/tutors/TutorActions.tsx | Restyles approve/revoke actions |
| app/admin/subjects/page.tsx | Adds admin subjects management page |
| app/admin/subjects/actions.ts | Adds subject add/toggle actions |
| app/admin/subjects/AddSubjectForm.tsx | Adds subject creation form |
| app/admin/sessions/page.tsx | Adds pagination + restyles admin sessions list |
| app/admin/requests/actions.ts | Refactors to shared requireAdmin + adds new admin request actions |
| app/admin/requests/[id]/AdminRequestActions.tsx | Adds admin status/cancel controls UI |
| app/admin/requests/RequestFilters.tsx | Restyles request filters |
| app/admin/payments/actions.ts | Refactors to shared requireAdmin + validates inputs with zod |
| app/admin/payments/ViewProofButton.tsx | Adds modal proof viewer with signed URL fetch |
| app/admin/payments/PaymentQuickActions.tsx | Adds compact WA + copy-message quick actions |
| app/admin/payments/PaymentActions.tsx | Restyles payment action forms |
| app/admin/page.tsx | Adds admin dashboard counts + refreshed card styling |
| app/admin/loading.tsx | Adds admin loading skeleton UI |
| app/admin/layout.tsx | Restyles admin layout + server-action sign out |
| app/admin/error.tsx | Adds admin route error UI |
| app/admin/audit/page.tsx | Restyles audit log |
| app/admin/actions.ts | Refactors to shared requireAdmin and removes duplicate signOut |
| .env.example | Documents new env vars (bank, cron, Sentry) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 'use server' | ||
|
|
||
| import { createAdminClient } from '@/lib/supabase/admin' | ||
| import { revalidatePath } from 'next/cache' | ||
| import { z } from 'zod' | ||
|
|
There was a problem hiding this comment.
addSubject and toggleSubjectActive use the service-role admin client but do not call requireAdmin(). Server actions can be invoked directly (bypassing the layout redirect), so these mutations need an explicit admin guard (and ideally audit logging) to prevent unauthorized subject changes.
| import * as React from "react" | ||
| import { Tooltip as TooltipPrimitive } from "radix-ui" | ||
|
|
||
| import { cn } from "@/lib/utils" |
There was a problem hiding this comment.
The Radix import shape here appears incorrect for the usage (TooltipPrimitive.Provider, .Root, etc.), and the Tailwind class origin-(--radix-tooltip-content-transform-origin) is invalid syntax (Tailwind expects bracket form). This will likely fail at build time; switch to the standard Radix Tooltip module import pattern and correct the Tailwind arbitrary value syntax.
| data-slot="tooltip-content" | ||
| sideOffset={sideOffset} | ||
| className={cn( | ||
| "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", |
There was a problem hiding this comment.
The Radix import shape here appears incorrect for the usage (TooltipPrimitive.Provider, .Root, etc.), and the Tailwind class origin-(--radix-tooltip-content-transform-origin) is invalid syntax (Tailwind expects bracket form). This will likely fail at build time; switch to the standard Radix Tooltip module import pattern and correct the Tailwind arbitrary value syntax.
|
|
||
| import * as React from "react" | ||
| import { cva, type VariantProps } from "class-variance-authority" | ||
| import { Tabs as TabsPrimitive } from "radix-ui" |
There was a problem hiding this comment.
TabsPrimitive.Root/List/Trigger/Content usage requires a namespace-style Radix import; importing { Tabs as TabsPrimitive } is unlikely to provide those members and will break compilation/runtime. Use the standard Radix Tabs package import that matches the API surface you’re calling.
| import { Tabs as TabsPrimitive } from "radix-ui" | |
| import * as TabsPrimitive from "@radix-ui/react-tabs" |
| import type { Label as LabelPrimitive } from "radix-ui" | ||
| import { Slot } from "radix-ui" |
There was a problem hiding this comment.
LabelPrimitive is imported as a type-only symbol, but it’s referenced via typeof LabelPrimitive.Root, which requires a runtime value. This will fail type-checking; import the Radix Label primitive as a value (from the correct Radix package) or change the prop type so it doesn’t rely on typeof against a type-only import.
| import type { Label as LabelPrimitive } from "radix-ui" | |
| import { Slot } from "radix-ui" | |
| import * as LabelPrimitive from "@radix-ui/react-label" | |
| import { Slot } from "@radix-ui/react-slot" |
|
|
||
| return ( | ||
| <form action={formAction} className="mt-2 space-y-2 rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-800/50"> | ||
| <form action={formAction} className="mt-2 space-y-2 border border-[#D0D0D0] bg-[#F0F0F0] p-3 /50"> |
There was a problem hiding this comment.
The className includes a stray /50 token at the end (and double spaces), which isn’t a valid Tailwind utility and will be ignored (or potentially flagged by class linting). Remove the stray token and normalize the class list.
| <form action={formAction} className="mt-2 space-y-2 border border-[#D0D0D0] bg-[#F0F0F0] p-3 /50"> | |
| <form action={formAction} className="mt-2 space-y-2 border border-[#D0D0D0] bg-[#F0F0F0] p-3"> |
| .from("requests") | ||
| .select( | ||
| ` | ||
| id, level, subject_id, status, goals, availability, timezone, |
There was a problem hiding this comment.
The request validator/schema uses availability_windows, but this admin fetch selects availability. If the underlying DB column is now availability_windows (or the UI expects that field), this will return the wrong shape/empty data. Align the selected column name with the updated schema and downstream consumers.
| id, level, subject_id, status, goals, availability, timezone, | |
| id, level, subject_id, status, goals, availability_windows, timezone, |
| // Fetch primary_role from user_profiles for role-based route protection | ||
| const { data: profile } = await supabase | ||
| .from('user_profiles') | ||
| .select('primary_role') | ||
| .eq('user_id', user.id) | ||
| .single() | ||
|
|
||
| const primaryRole = profile?.primary_role | ||
|
|
||
| // Redirect non-admin users away from /admin/* | ||
| if (pathname.startsWith('/admin') && primaryRole !== 'admin') { | ||
| const url = request.nextUrl.clone() | ||
| url.pathname = '/dashboard' | ||
| return NextResponse.redirect(url) | ||
| } | ||
|
|
||
| // Redirect non-tutor users away from /tutor/* | ||
| // (admins are allowed to access tutor routes per existing layout logic) | ||
| if ( | ||
| pathname.startsWith('/tutor') && | ||
| primaryRole !== 'tutor' && | ||
| primaryRole !== 'admin' | ||
| ) { | ||
| const url = request.nextUrl.clone() | ||
| url.pathname = '/dashboard' | ||
| return NextResponse.redirect(url) |
There was a problem hiding this comment.
This adds a database round-trip on every protected request (including non-admin/non-tutor pages). To reduce middleware latency/cost, consider only querying user_profiles when pathname is under /admin or /tutor, or move role info into a token claim / cached session metadata so middleware can decide without a DB call.
| // Fetch primary_role from user_profiles for role-based route protection | |
| const { data: profile } = await supabase | |
| .from('user_profiles') | |
| .select('primary_role') | |
| .eq('user_id', user.id) | |
| .single() | |
| const primaryRole = profile?.primary_role | |
| // Redirect non-admin users away from /admin/* | |
| if (pathname.startsWith('/admin') && primaryRole !== 'admin') { | |
| const url = request.nextUrl.clone() | |
| url.pathname = '/dashboard' | |
| return NextResponse.redirect(url) | |
| } | |
| // Redirect non-tutor users away from /tutor/* | |
| // (admins are allowed to access tutor routes per existing layout logic) | |
| if ( | |
| pathname.startsWith('/tutor') && | |
| primaryRole !== 'tutor' && | |
| primaryRole !== 'admin' | |
| ) { | |
| const url = request.nextUrl.clone() | |
| url.pathname = '/dashboard' | |
| return NextResponse.redirect(url) | |
| const isRoleProtectedPath = | |
| pathname.startsWith('/admin') || pathname.startsWith('/tutor') | |
| if (isRoleProtectedPath) { | |
| // Fetch primary_role from user_profiles for role-based route protection | |
| const { data: profile } = await supabase | |
| .from('user_profiles') | |
| .select('primary_role') | |
| .eq('user_id', user.id) | |
| .single() | |
| const primaryRole = profile?.primary_role | |
| // Redirect non-admin users away from /admin/* | |
| if (pathname.startsWith('/admin') && primaryRole !== 'admin') { | |
| const url = request.nextUrl.clone() | |
| url.pathname = '/dashboard' | |
| return NextResponse.redirect(url) | |
| } | |
| // Redirect non-tutor users away from /tutor/* | |
| // (admins are allowed to access tutor routes per existing layout logic) | |
| if ( | |
| pathname.startsWith('/tutor') && | |
| primaryRole !== 'tutor' && | |
| primaryRole !== 'admin' | |
| ) { | |
| const url = request.nextUrl.clone() | |
| url.pathname = '/dashboard' | |
| return NextResponse.redirect(url) | |
| } |
| <p className="mb-6 text-sm text-[#121212]/60"> | ||
| {error.message || 'An unexpected error occurred. Please try again.'} | ||
| </p> |
There was a problem hiding this comment.
Rendering error.message directly to end users can leak internal details (stack-derived messages, DB errors, etc.). Consider showing a generic message in production and logging the detailed error server-side (or gating the detailed message behind NODE_ENV !== 'production').
| return [ | ||
| { | ||
| // Apply security headers to all routes | ||
| source: "/(.*)", | ||
| headers: [ | ||
| { | ||
| key: "X-Frame-Options", | ||
| value: "DENY", | ||
| }, | ||
| { | ||
| key: "X-Content-Type-Options", | ||
| value: "nosniff", | ||
| }, | ||
| { | ||
| key: "Referrer-Policy", | ||
| value: "strict-origin-when-cross-origin", | ||
| }, | ||
| { | ||
| key: "Permissions-Policy", | ||
| value: "camera=(), microphone=(), geolocation=()", | ||
| }, | ||
| { | ||
| key: "Strict-Transport-Security", | ||
| value: "max-age=63072000; includeSubDomains; preload", | ||
| }, | ||
| { | ||
| key: "X-DNS-Prefetch-Control", | ||
| value: "on", | ||
| }, | ||
| ], |
There was a problem hiding this comment.
HSTS should generally only be sent when you’re certain the site is always served over HTTPS (typically production only). Consider conditionally adding this header based on environment/deployment to avoid unintended behavior during local/dev or non-HTTPS environments.
| return [ | |
| { | |
| // Apply security headers to all routes | |
| source: "/(.*)", | |
| headers: [ | |
| { | |
| key: "X-Frame-Options", | |
| value: "DENY", | |
| }, | |
| { | |
| key: "X-Content-Type-Options", | |
| value: "nosniff", | |
| }, | |
| { | |
| key: "Referrer-Policy", | |
| value: "strict-origin-when-cross-origin", | |
| }, | |
| { | |
| key: "Permissions-Policy", | |
| value: "camera=(), microphone=(), geolocation=()", | |
| }, | |
| { | |
| key: "Strict-Transport-Security", | |
| value: "max-age=63072000; includeSubDomains; preload", | |
| }, | |
| { | |
| key: "X-DNS-Prefetch-Control", | |
| value: "on", | |
| }, | |
| ], | |
| const securityHeaders = [ | |
| { | |
| key: "X-Frame-Options", | |
| value: "DENY", | |
| }, | |
| { | |
| key: "X-Content-Type-Options", | |
| value: "nosniff", | |
| }, | |
| { | |
| key: "Referrer-Policy", | |
| value: "strict-origin-when-cross-origin", | |
| }, | |
| { | |
| key: "Permissions-Policy", | |
| value: "camera=(), microphone=(), geolocation=()", | |
| }, | |
| { | |
| key: "X-DNS-Prefetch-Control", | |
| value: "on", | |
| }, | |
| ]; | |
| if (process.env.NODE_ENV === "production") { | |
| securityHeaders.push({ | |
| key: "Strict-Transport-Security", | |
| value: "max-age=63072000; includeSubDomains; preload", | |
| }); | |
| } | |
| return [ | |
| { | |
| // Apply security headers to all routes | |
| source: "/(.*)", | |
| headers: securityHeaders, |
Changes
This pull request introduces several improvements and refactors to the admin analytics dashboard and related backend logic. The main focus is enhancing platform health visibility, especially around package renewals, and improving code maintainability and UI consistency.
Admin Analytics Dashboard Enhancements:
getExpiringPackagesservice and joins user profile data for display. [1] [2]UI and Styling Updates:
Backend and Auth Refactor:
requireAdminfunction fromlib/auth/requireAdmin, removing the local duplicate implementation and unused sign-out code. [1] [2] [3]Documentation and Environment Setup:
CLAUDE.mdfile detailing repository tooling, workflows, coding standards, DB migration discipline, and architectural conventions for onboarding and automation..env.exampleto include new environment variables for bank details, Sentry, and cron secrets, clarifying required fields for deployment and local development.