continuous-integration/drone/push Build is passingDetails
- Create ClubTabNavigation component with responsive horizontal scroll
- Create ClubDetailHeader component with shared layout (breadcrumb, header, tabs)
- Add layout.tsx for club_id route to wrap all sub-pages
- Extract Courts tab into separate page route
- Remove redundant AdminAuthGuard from individual pages (now in layout)
- Remove container wrappers from components (now in shared header)
- Remove "Back to club" links (breadcrumb now in header)
- Standardize colors: gray-* to slate-* for consistent dark text styling
continuous-integration/drone/push Build is passingDetails
Add complete admin UI for managing individual slot instances:
Components:
- SlotInstancesComponent: day view with date navigation and filters
- SlotInstanceEditModal: edit times, capacity, convert to manual
- ManualSlotModal: create one-off manual slots
Features:
- Date picker with prev/next day navigation
- Filter by court and show/hide cancelled slots
- Group slots by court in table view
- Status badges (available, pending, booked, cancelled)
- Origin badges (template, manual, maintenance)
- Convert definition-based slots to manual (detach from materializer)
- Create manual slots for special events
- Edit slot times/capacity (protected when has bookings)
- Cancel and delete slots with confirmation
API Client:
- getSlotInstances, createSlotInstance, updateSlotInstance, deleteSlotInstance
- cancelSlotInstance, convertToManualSlot helpers
Types:
- SlotInstance, SlotInstancesResponse
- CreateSlotInstanceRequest, UpdateSlotInstanceRequest
- Helper functions for formatting and status colors
Also adds "Slot Instances" tab to ClubDetailTabs navigation.
continuous-integration/drone/push Build is passingDetails
- Add PlanEntitlementsEditor component for Pay-Per-Court settings
- Add EntitlementsConfigModal for editing plan entitlements
- Update PlanCard with entitlements badges and configure button
- Update MembershipPlansComponent to fetch/display entitlements
- Create credits management page with balance list and adjustment modal
- Create transfers management page with filtering and stats
- Add Credits and Transfers tabs to ClubDetailTabs navigation
- Add API client functions for credits and transfers endpoints
- Update facility-admin types with credit and transfer interfaces
continuous-integration/drone/push Build is passingDetails
- Add PlanTemplate type and listPlanTemplates API function
- Create TemplateCard and TemplatePicker components
- Modify PlanFormModal to accept initialValues from template
- Update MembershipPlansComponent to show template picker first
- Add missing translations (Venue Management, Club Management, etc.)
continuous-integration/drone/push Build is passingDetails
Add 'Venue Admin' menu item in hamburger navigation:
- Links to /admin/clubs for facility management
- Purple accent to distinguish from player features
- Positioned after assessment, before language selector
Add tabs to club detail page:
- Plans tab (purple accent)
- Members tab (purple accent)
- Settings tab (purple accent)
- All tabs link to new admin portal pages
Navigation is now complete for accessing all facility management features.
continuous-integration/drone/push Build is passingDetails
Implements Phase B1-B4 of the admin portal frontend:
Phase B1 - Setup & Types:
- Add TypeScript type definitions for all admin APIs
- Add complete API client with all 15 endpoint integrations
Phase B2 - Membership Plans UI:
- Add plans listing page with create/edit/delete functionality
- Add PlanCard, PlanFormModal, and PlanListSkeleton components
- Support billing periods, pricing, and activation status
Phase B3 - Member Management UI:
- Add members listing page with search and filters
- Add member CRUD modals with role/plan assignment
- Add RoleBadge, StatusBadge, and MemberCard components
- Support filtering by role and status
Phase B4 - Policy Configuration UI:
- Add facility settings page for policy management
- Add AccessModelSelector for membership/payg/open models
- Add GuestPricingInput and BookingLimitsForm components
- Support configuring booking limits and access control
All components follow existing design patterns with purple/indigo
gradients, responsive layouts, and loading states.
continuous-integration/drone/push Build is passingDetails
The API now returns address at facility.address instead of facility.settings.address, and uses field names like address_line_1 and zip instead of line_1 and postal_code. Updated the ClubProfile type and form to match.
continuous-integration/drone/push Build is passingDetails
The API now returns 'facility' with expanded address/settings fields instead of 'club', and slot types have been restructured to use flat properties instead of nested booking/meta objects.
continuous-integration/drone/push Build is passingDetails
- Add MaterialisationJobInfo interface with job status, timing, and results
- Add last_job field to MaterialisationStatus (BUILD 353 support)
- Add calculateElapsedTime and formatElapsedTime helper functions
- Implement job status polling (every 2s while queued/running)
- Add elapsed time counter for running jobs with live updates
- Display job progress states:
- Queued: 'Job queued, waiting to start...'
- Running: 'Generating slots... (45s)' with policy and horizon info
- Completed: '✅ Generated X slots (Y cancelled, Z errors)' with detailed metrics
- Failed: '❌ Job failed' with error message
- Update rate limit display logic (only show when can_trigger=false AND no active job)
- Disable trigger button when job is active (queued/running)
- Resolves UX confusion where users saw rate limits while jobs were actually running
- Implements Backend Brooke BUILD 353 job tracking feature
continuous-integration/drone/push Build is passingDetails
- 'Up to date' badge now shows when status is idle with last_success_at
- 'Not yet run' badge only shows when status is idle AND last_run_at is null
- Success details section now shows for both completed and idle-after-success states
- Fixes issue where successful materialisation showed 'Not yet run' after transitioning from 'completed' to 'idle' status
continuous-integration/drone/push Build is passingDetails
- Set default value to today's date
- Add validation before form submission
- Update UI label to show required field with asterisk
- Fixes validation error: 'valid_from' required field
continuous-integration/drone/push Build is passingDetails
Backend Brooke reverted the breaking change from BUILD:347. The generate
endpoint continues to use "preset" field name, not "pattern_type".
This reverts commit 834e1a8 and restores the original API contract.
Changes:
- Reverted GenerateSlotDefinitionsRequest: pattern_type → preset
- Reverted GenerateSlotDefinitionsModal to send preset in request body
Backend BUILD:348 has restored the original "preset" field.
continuous-integration/drone/push Build is passingDetails
Backend API changed field name from "preset" to "pattern_type" in the
generate slot definitions endpoint (BUILD:347).
Changes:
- Updated GenerateSlotDefinitionsRequest interface: preset → pattern_type
- Updated GenerateSlotDefinitionsModal to send pattern_type in request body
This fixes compatibility with the updated backend API contract.
continuous-integration/drone/push Build is passingDetails
Migrated all admin API clients to automatically send X-Locale and X-Timezone headers
with every request for proper internationalization and timezone-aware operations.
Changes:
- Migrated slot-definitions.ts, materialisation.ts, courts.ts, booking-admin.ts to use apiFetch utility
- Added getLocaleHeaders() helper to admin-clubs.ts for SSR compatibility
- Added getSlotDefinitionPresets() endpoint to fetch localized preset metadata
- Updated slot-definitions types to support API-based preset loading
- Refactored GenerateSlotDefinitionsModal to fetch presets from API with localized titles/descriptions
Benefits:
- All API requests now include user's locale (e.g., en-US, fr-CH) for i18n
- Timezone-aware operations use browser's Intl.DateTimeFormat timezone
- Preset titles and descriptions are localized per user language
- Consistent header management across all admin endpoints
Technical Details:
- apiFetch extracts locale from URL pathname via getPathnameLocale()
- apiFetch automatically adds credentials: 'include' for session auth
- admin-clubs.ts uses manual headers for SSR cookie forwarding compatibility
- GenerateSlotDefinitionsModal shows loading state while fetching presets
continuous-integration/drone/push Build is passingDetails
Built complete bulk operations UI for slot definitions based on Backend Brooke's spec (BUILD:345).
New Components:
- GenerateSlotDefinitionsModal (380 lines)
- Preset selector with 4 options (workday_standard, weekend_extended, all_week_uniform, hourly_daytime)
- Court multi-select with Select All
- Validity date pickers (optional)
- Advanced pattern overrides (collapsible): custom days, time range, duration, interval, capacity
- Real-time validation and error handling
- CloneSlotDefinitionModal (280 lines)
- Source definition display with full details
- Target courts multi-select
- Target days multi-select (pre-selected source day)
- Validity date overrides (optional)
- Preview of estimated clone count
Integration:
- Added Generate button (blue, magic wand icon) to page header
- Added Clone button (blue copy icon) to each table row
- Wired up modals with success callbacks to reload data
- Success callbacks trigger table refresh
Features:
- All modals use professional slate theme
- Loading states with spinners
- Error display with user-friendly messages
- Form validation before submission
- Modal backdrop dismissal
Ready for testing against staging (BUILD:345).
continuous-integration/drone/push Build is passingDetails
Removed USE_MOCKS and USE_MOCK_DATA flags from all API client functions. All endpoints now call the real backend API directly without conditional mock data paths.
Changes:
- SlotDefinitionsComponent: removed mock flags from loadData() and loadDefinitions()
- materialisation.ts: removed mock conditionals from getMaterialisationStatus() and triggerMaterialisation()
- admin-clubs.ts: removed MaybeMock wrapper functions and USE_MOCK_DATA flag
- courts.ts: removed mock conditionals from all 7 CRUD functions
All API calls now go directly to https://api.playchoo.com backend.
continuous-integration/drone/push Build is passingDetails
Changed from hardcoded MOCK_COURTS to fetching real court data from club detail API.
Dropdown now shows actual court names (e.g., 'Padel 1', 'Tennis Int 1') instead of generic 'Court 1', 'Court 2'.
Changes:
- Fetch courts from getAdminClubDetail() on component mount
- Pass real courts array to SlotDefinitionForm
- Split loadData() (courts + definitions) and loadDefinitions() (refresh after CRUD)
Artifacts: src/app/[locale]/admin/clubs/[club_id]/slot-definitions/SlotDefinitionsComponent.tsx:36-38,298
continuous-integration/drone/push Build is passingDetails
Fixes from tsc type checking:
- Re-added useEffect import to ClubCourtsTab (used in CourtFormModal)
- Updated mock courts data to include created_at/updated_at timestamps
- Fixed CourtError dependencies type to match CourtDependencies structure
(slot_instances_future/booked instead of upcoming_bookings)
All court-related TypeScript errors resolved. Build succeeds.
continuous-integration/drone/push Build is passingDetails
Root cause: GET /admin/clubs/{id}/courts endpoint doesn't include
sport_variation nested structure, but GET /admin/clubs/{id} does.
Changes:
- Updated AdminClubDetail.courts to use Court[] type (includes sport_variation)
- ClubDetailTabs now passes courts + onUpdate callback to ClubCourtsTab
- ClubCourtsTab accepts courts as props instead of fetching separately
- Removed redundant getCourts() API call
- Removed debug logging
This fixes the court edit dropdown issue where sport variation wasn't
displaying because the data wasn't available from the courts endpoint.
continuous-integration/drone/push Build is passingDetails
Added debug logging to investigate sport variation dropdown issue:
- Log court data object on mount
- Log extracted sport_variation_id value
- Added visual disabled state (gray background) for locked dropdown
- Extracted initialSportVariationId to const for better debugging
This will help identify if:
1. Court data has sport_variation nested structure
2. sport_variation_id is being extracted correctly
3. Dropdown value is being set properly
Related: BUILD #21 sport variation display issue
continuous-integration/drone/push Build is passingDetails
Fixed two issues reported by user:
1. Slot definition duration validation error:
- Changed step from "15" to "1" on duration input
- HTML5 validation was failing because with min="1" and step="15",
valid values were 1, 16, 31, 46, 61, 76, 91... (not 90!)
- Now accepts any integer duration >= 1 minute
- Error "nearest 76 or 91" resolved
2. Sport variation not showing in court edit form:
- Updated Court type to include nested sport_variation structure
- Backend now returns full sport_variation object with sport details
- Updated CourtFormModal to extract sport_variation_id from nested
structure: court.sport_variation.sport_variation_id
- Added sport variation display in court list (e.g., "Padel - Indoor")
- Maintains backward compatibility with deprecated flat field
Changes:
- SlotDefinitionForm: step="1" instead of step="15"
- Court interface: added optional sport_variation nested object
- ClubCourtsTab: reads sport_variation_id from nested structure
- ClubCourtsTab: displays "Sport - Variation" in court list
Related: Backend Brooke nested sport_variation response structure
continuous-integration/drone/push Build is passingDetails
Fixed two critical issues:
1. Profile form blur bug - ACTUAL FIX:
- Removed ALL blur auto-close handlers from EditableField component
- Removed blur auto-close from name and timezone inline inputs
- Users can now type freely without inputs closing prematurely
- Form uses Save/Cancel buttons for submission, not blur events
2. Settings parsing from GET response:
- Backend GET /admin/clubs/{id} returns nested structure with club object
- Updated getClubProfile to extract club from response.club
- Settings now properly loaded and displayed from GET response
- PATCH response already worked (returns club directly)
Root cause of blur bug: blur handlers were closing inputs when field had
content, but this prevented multi-character input. Removed blur logic
entirely since form has explicit Save/Cancel buttons.
Impact: Profile form now fully functional for editing all fields including
settings (address + contact). Settings persist and display correctly.
Related: BUILD #20, Backend Brooke BUILD:297 (settings support)
continuous-integration/drone/push Build is passingDetails
Fixed critical bug where EditableField component would close after typing
first character. Root cause was stale closure variable in onBlur handler.
Changes:
- Replace inline onBlur with handleBlur() function that evaluates current
value at blur time instead of using stale hasValue closure variable
- Fixes issue where typing first char would set hasValue=true in closure,
causing immediate blur to close input
Impact: Users can now fill in profile fields without being kicked out
Related: BUILD #19
continuous-integration/drone/push Build is passingDetails
Court Sport Variation (BUILD:296-297 requirement):
- Added Sport and SportVariation types to courts.ts
- Created getSports() API client for GET /admin/sports
- Updated Court and CourtRequest types with sport_variation_id field
- Added sport variation dropdown to court creation modal
- Auto-selects first variation for new courts
- Sport type locked after creation (disabled in edit mode)
- Mock data updated with sport_variation_id values
Profile Bug Fix:
- Fixed click-to-edit crash when field values are empty/undefined
- Added null checks before calling .trim() in hasFieldValue() and EditableField
- Error: "Cannot read properties of undefined (reading 'trim')"
- Now safely handles empty string values
UI Details:
- Sport dropdown shows "Padel - Indoor", "Padel - Outdoor", etc.
- Loading spinner while fetching sports
- Validation error if no sport variation selected
- Form includes both name and sport_variation_id in POST request
Backend Brooke message: Court creation now requires sport_variation_id parameter
continuous-integration/drone/push Build is passingDetails
UX Improvements:
- Fields with existing data display as read-only text (not inputs)
- Pencil icon appears on hover (right side of field)
- Click text or icon to convert to editable input
- Auto-blur closes edit mode when field has value
- Prevents accidental edits
Implementation:
- Created reusable EditableField component with pencil icon
- Applied to all optional fields: address (5 fields), contact (3 fields)
- Name and timezone use inline click-to-edit (no separate component)
- Edit state tracked per-field with Set<string>
- Form prefills with existing profile data from API
Visual Design:
- Hover: Border changes to slate-300, background to slate-50
- Pencil icon: slate-400, opacity 0→100 on hover
- Maintains existing validation error styling (red borders)
- Consistent with professional slate theme
User Request: "should not be an input directly (put a pen on the side as well as making the text clickable) and on click convert to input (prevents accidental edits)"
continuous-integration/drone/push Build is passingDetails
Phase 2 implementation per Chief Cole approval (Chief_Cole-20251107103829)
Club Detail Page Restructure:
- Replaced single-view club detail with tabbed interface
- Three tabs: Profile, Courts, Slot Definitions (links to existing page)
- Tab navigation with active state highlighting
- Breadcrumb navigation maintained
Profile Tab (ClubProfileTab):
- Full club metadata editing form with sections:
- Basic Information: name (required), timezone (required, IANA dropdown)
- Location: address line 1/2, city, postal code, country (all optional)
- Contact: phone, email (validated), website (validated) (all optional)
- Integration: provider, remote club ID (read-only for Phase 2)
- Client-side validation: required fields, email format, URL format
- Server-side validation: maps backend errors to fields
- Success/error messaging with auto-hide success toast
- Cancel button resets form to original values
- Professional slate theme form styling
Courts Tab (ClubCourtsTab):
- Court list view with empty state
- Add/edit/delete actions per court
- Add Court modal: single field (name), duplicate name validation
- Edit Court modal: pre-populated, same validation
- Delete with cascade blocking:
- Dependency check before delete (slot definitions, upcoming bookings)
- Blocking modal shows dependency counts with resolution steps
- Confirmation modal for clean delete (no dependencies)
- In-memory mock data management (3 courts default)
- Court 1 has mock dependencies (12 slot defs, 45 bookings) for testing
Modal Components:
- CourtFormModal: Add/edit with field-level validation
- DeleteConfirmationModal: Confirm before deletion
- DependenciesBlockingModal: Shows why delete is blocked + resolution steps
All components use professional slate theme
All modals use backdrop blur + center positioning
All forms have loading states + error handling
Build tested and passed (npm run build successful)
Total new lines: ~1400 across 3 components
Ready for mock-driven testing while Backend Brooke builds APIs
continuous-integration/drone/push Build is passingDetails
Implement full CRUD form modal for slot definitions with:
- All fields: court, day, time, duration, capacity, date range, description
- Field-level validation with inline error messages
- API contract alignment (HH:MM:SS, 0=Monday format, optional valid_to)
- Overlap detection (409 handling)
- No end date checkbox
- Edit mode support
- Professional slate theme
- Loading states
- Delete functionality integrated
Form validates:
- Required fields
- Duration must be positive
- Capacity must be positive
- End date must be after start date
- Slot cannot extend past midnight
- Backend validation errors shown inline
Fully functional create/edit/delete flow ready for backend integration.
continuous-integration/drone/push Build is passingDetails
Add TypeScript types, API client, and table view for slot definitions.
Uses mock data until backend endpoints are ready.
Components:
- SlotDefinitionsComponent: table with create/edit/delete actions
- TypeScript types matching API contract specs
- API client with CRUD operations (POST/GET/PATCH/DELETE)
- Mock data for local development
Features:
- Professional slate theme throughout
- Empty state with CTA
- Table showing: day, time range, duration, capacity, valid period, description
- Edit/delete action buttons (TODOs for modals)
- Loading and error states with proper UX
- Breadcrumb navigation back to club
Contract alignment:
- Day format: 0=Monday, 6=Sunday
- Time format: HH:MM:SS with display helpers
- Date range: valid_from required, valid_to optional
- RFC-7807 error handling ready
Next: Create/edit form modal with validation
continuous-integration/drone/push Build is passingDetails
Extended the professional slate gray color scheme from the landing page
to all admin components and navigation. Replaced all purple/pink/indigo
gradients with clean slate-900/600/200 colors for a timeless,
enterprise-focused aesthetic.
Changes:
- Updated AdminClubsList.tsx with slate theme
- Updated AdminClubDetail.tsx with slate theme
- Updated AdminAuthGuard.tsx loading states with slate colors
- Updated Navigation.tsx header, menu items, and footer with slate theme
- Updated layout.tsx body and footer with slate colors
- Added ~30 new translation keys to dictionaries/en.json
Design system:
- Primary: slate-900 (almost black)
- Text: slate-900 (headings), slate-600 (body), slate-500 (muted)
- Borders: slate-200 (2px), slate-300 on hover
- Backgrounds: white, slate-50, slate-100
- Professional rounded corners: rounded-2xl, rounded-xl, rounded-lg
- Clean typography: tracking-tight, font-bold/medium/light
continuous-integration/drone/push Build is passingDetails
Fixed translation issues and replaced colorful gradients with sophisticated,
timeless design suitable for enterprise venue management platform.
Changes:
1. Translations Fixed:
- Removed all untranslated strings causing blank text
- Only using existing translation keys (e.g., t('Login'))
- All text now hardcoded in English (proper i18n to be added later)
2. Professional Design System:
- Removed purple/pink gradients
- Clean slate gray color palette (slate-50 to slate-900)
- Minimal use of color - black/white/gray focused
- Subtle gradients only where needed (slate-50 to white)
3. Timeless Layout:
- Clean typography with proper hierarchy
- Generous white space
- Elegant borders and shadows
- Professional card-based layout
- Trust indicators with stats (Real-time, 24/7, Secure, Multi-venue)
4. Enterprise Aesthetic:
- Sophisticated, not playful
- Focus on efficiency and professionalism
- Clear value propositions
- Business-focused copy
Result: Landing page conveys class and professionalism appropriate for
venue management platform used by serious business operators.
continuous-integration/drone/push Build is passingDetails
Per Chief Cole's guidance (Chief_Cole-20251107080515), enhanced manager
portal with showcase landing and auth protection.
Changes:
1. Marketing Landing Page:
- Hero section with gradient backgrounds and value props
- Feature tiles showcasing club management, scheduling, booking insights
- CTA buttons linking to login
- Marketing copy focused on venue admin benefits
- Responsive design matching consumer app style
2. Auth Guards for /admin Routes:
- Created AdminAuthGuard component using useSwissOIDAuth hook
- Redirects unauthenticated users to login immediately
- Shows loading state during auth check
- Preserves locale in redirect flow
- Wrapped all /admin/clubs pages with guard
3. Protected Routes:
- /admin/clubs - guarded
- /admin/clubs/[club_id] - guarded
- / - public landing (no guard needed)
Result: Unauthenticated users see marketing showcase at root, and are
redirected to login if they try to access /admin routes directly.
Refs: docs/owners/Frontend_Faye-needs-to-read-from-Chief_Cole-20251107080515.md
continuous-integration/drone/push Build is passingDetails
Build #6 failed because client components were trying to receive
translations as props from server components, which breaks Next.js
serialization boundaries.
Fix: Use useTranslation() hook directly in client components to access
translations context, matching the pattern used in consumer app.
Changes:
- AdminClubsList: Remove locale/t props, use useTranslation() hook
- AdminClubDetail: Remove locale/t props, use useTranslation() hook
- Simplified page.tsx wrappers to not pass translations
This matches the consumer app pattern and allows proper SSR/client
component boundaries.
continuous-integration/drone/push Build is failingDetails
Responding to Chief Cole's feedback after validation of BUILD:283:
- Manager portal should match consumer app pattern (client-side fetching)
- Fix 404 errors for /dashboard and /admin/clubs/[id]
- Ensure locale-prefixed routing works smoothly
Changes:
1. Client-side rendering for admin views:
- Converted /admin/clubs list to use 'use client' pattern
- Created AdminClubsList.tsx component with useEffect data fetching
- Removed SSR cookie forwarding (no longer needed for client-side)
2. Added club detail page:
- Created /admin/clubs/[club_id] route with AdminClubDetail.tsx
- Client-side fetching with proper loading/error states
- Support for 401/403/404 error handling with user-friendly messages
- Breadcrumb navigation back to clubs list
3. Added dashboard route:
- Created /dashboard that redirects to /admin/clubs
- Resolves post-login 404 error reported in testing
4. Locale handling:
- Middleware automatically adds locale prefix to all routes
- Internal links include locale parameter to avoid extra redirects
- /dashboard → /en-US/dashboard → /en-US/admin/clubs
Result: Manager portal now follows consumer app patterns with full
client-side data fetching, proper error handling, and complete routing.
Refs: docs/owners/Frontend_Faye-needs-to-read-from-Chief_Cole-20251107073302.md
continuous-integration/drone/push Build is passingDetails
Root cause: Next.js server-side rendering was making API calls without
forwarding browser cookies, causing AUTHORIZATION_ERROR during page load.
Changes:
- Updated getAdminClubs() to accept optional cookieHeader parameter
- Updated getAdminClubDetail() to accept optional cookieHeader parameter
- Modified /admin/clubs page to extract cookies via next/headers and
forward them to API calls during SSR
- Use credentials: 'omit' when manually setting Cookie header to avoid
fetch() conflicts
Result: Admin endpoints now authenticate properly during SSR, allowing
page to render club list correctly instead of showing error state.
Resolves: SSR authorization error reported in BUILD:283 regression test