Backend Changes:
- Added public API endpoint /api/public/log-404 (no auth required)
- Created backend/src/routes/public.js for public endpoints
- Added ACTIONS.SYSTEM_404 and CATEGORIES.system to activity log service
- Registered public routes in app.js
Frontend Changes:
- Created NotFoundPage.jsx with standalone layout (no auth required)
- Added publicAPI.log404() to log 404 access attempts
- Logs both authenticated and anonymous users
- Changed profile route from /@:username to /u/:username
- Made profile route public (removed ProtectedRoute wrapper)
- Updated all profile links from /@${username} to /u/${username} in:
- ChatMessage.jsx
- DashboardMatchCard.jsx
- MatchRequestCards.jsx
- MatchCard.jsx
- UserListItem.jsx
- MatchChatPage.jsx
- PublicProfilePage.jsx
Fixes:
- React Router doesn't support @ in path segments
- 404 page now accessible to non-authenticated users without redirect
- Profile route no longer catches all unmatched routes
- Moved EventDetailsPage from /events/:slug/details to /admin/events/:slug/details
- Added admin authentication check with redirect to login/home
- Updated all navigation links across the app:
- EventsPage: "View details (admin)" button
- EventChatPage: "View QR Code (admin)" link
- EventCard: handleViewDetails navigation
- Fixed relative imports after moving to admin folder
This page contains admin-only features (QR codes, participants list,
matching config, scheduler config, matching runs) and should only be
accessible to administrators.
- Fixed issue where empty string success filter was interpreted as false
- Backend was filtering for only failed logs when success='' was sent
- Added check to skip sending success parameter when empty string
- Activity logs page now shows all logs when filters are set to 'All'
Bug: When user selected 'All' for success filter, frontend sent success='',
backend parsed this as success=false, showing only failed logs (usually none).
Added message length validation to prevent spam and improve UX with
character counter feedback.
Backend:
- Added MESSAGE_MAX_LENGTH constant (2000 characters)
- Validation in send_event_message handler:
- Check if content is string
- Check if not empty after trim
- Check if not exceeding max length
- Validation in send_match_message handler (same checks)
- Returns error message if validation fails
Frontend:
- Added MESSAGE_MAX_LENGTH constant (2000 characters)
- ChatInput component enhancements:
- maxLength attribute on input (hard limit)
- Character counter shows when >80% of limit
- Counter turns red when at limit
- Submit button disabled when at limit
- Counter format: "X/2000"
UX:
- User sees counter at 1600+ characters (80% of limit)
- Hard limit prevents typing beyond 2000 chars
- Clear visual feedback (red text) when at limit
- Consistent validation on both event and match chats
Security:
- Prevents spam with extremely long messages
- Protects against potential DoS via message size
- Database already uses TEXT type (supports limit)
Fixed issue where active users list in event chat did not update in
real-time when new users joined. Users had to refresh the page to see
newly joined participants.
Root Cause:
- getAllDisplayUsers() used checkedInUsers (loaded once from API) as
base list, with activeUsers (Socket.IO real-time) only for isOnline flag
- When new user joined chat, they appeared in activeUsers but not in
checkedInUsers, so they were not displayed
Solution:
- Rewrote getAllDisplayUsers() to prioritize activeUsers (real-time data)
- Merges activeUsers (online) with checkedInUsers (offline checked-in users)
- Uses Socket.IO data as source of truth for online users
- Enriches with database data when available (firstName, lastName, etc)
- Sorts online users first, offline second
Changes:
- EventChatPage.jsx: Rewrote getAllDisplayUsers() to merge activeUsers
with checkedInUsers, prioritizing real-time Socket.IO data
- useEventChat.js: Added debug logging for active_users events
- socket/index.js: Added debug logging for active_users emissions
Testing:
- User A in chat sees User B appear immediately when B joins
- No page refresh required
- Online/offline status updates in real-time
Complete implementation of admin activity logs dashboard with real-time
streaming capabilities. Admin users can now monitor all system activity
through a comprehensive web interface.
Features:
- Stats dashboard with 4 key metrics (total logs, unique users, failures, 24h activity)
- Category breakdown visualization with color-coded badges
- Advanced filtering (date range, category, action type, username, success/failure)
- Paginated log table (50 entries per page) with sort by timestamp
- Real-time streaming toggle using Socket.IO
- Color-coded action badges (blue=auth, green=event, purple=match, red=admin, yellow=chat)
- Admin-only access with automatic redirect for non-admin users
- Responsive design for mobile and desktop
Frontend Changes:
- Created ActivityLogsPage.jsx (600+ lines) with complete UI implementation
- Added 3 admin API methods to api.js (getActivityLogs, getActivityLogActions, getActivityLogStats)
- Added /admin/activity-logs route to App.jsx
- Added admin navigation link to Navbar (desktop & mobile) with Shield icon
- Only visible to users with isAdmin flag
Implementation Details:
- Uses getSocket() from socket service for real-time updates
- Joins 'admin_activity_logs' Socket.IO room on streaming enable
- Receives 'activity_log_entry' events and prepends to table (first page only)
- Comprehensive error handling and loading states
- Empty states for no data
- Clean disconnect handling when streaming disabled
Testing:
- Build successful (no errors)
- Ready for manual testing and verification
Phase 8 (Testing) remains for manual verification of all features.
Backend:
- Extend GET /api/matches to include RecordingSuggestions alongside Match objects
- Add 'type' field: 'manual' for user-created matches, 'auto' for algorithm suggestions
- Fetch suggestions where user is dancer (to be recorded) or recorder (recording others)
- Transform suggestions to match format with partner info
- Support status filtering for both types
Frontend:
- Display 'Auto' (purple) or 'Manual' (gray) badge on match cards
- For pending auto suggestions: show 'Go to Records' button instead of Accept/Reject
- For accepted auto suggestions without slug: show 'Chat not available yet'
- Only allow Accept/Reject actions on manual match requests
- Extend dashboard API to include recordingSuggestions for each event
- Add toBeRecorded and toRecord arrays with heat and user details
- Export RecordingSummaryCard component
- Add Recording Assignments section to DashboardPage
- Filter and display events with recording suggestions
- Show up to 2 suggestions per event with View Details link
- Increase font size from xs to sm for better readability
- Reduce avatar size from xs to 24px for better proportions
- Add proper layout with heat names in separate line
- Add truncate for long usernames to prevent overflow
- Style status badges with colored backgrounds and icons (pending/accepted)
- Fix EventChatPage to read and handle ?tab=records URL parameter
- Map 'records' query param to 'recording' tab for proper navigation
Backend changes:
- Restrict suggestion status updates to recorder only
- Dancers can now only view who is assigned to record them
- Return 403 error if non-recorder tries to update status
Frontend changes:
- Remove Accept/Reject buttons from dancer view (TO_BE_RECORDED)
- Add "Pending" status badge with clock icon for pending suggestions
- Keep Accept/Reject buttons for recorder view (TO_RECORD)
- Dancers see only status badge and optional chat button
UX flow:
- Dancer sees: "Recording you: @username [Pending]"
- Recorder sees: "You record: @username [Accept] [Reject]"
- Only recorder's action creates the Match
- Admin list endpoint returns totalSuggestions, assignedCount, aggregatedNotFoundCount per run
- UI: show Total/Matched/Not found columns using fresh aggregates
- Add anchor link Run #ID and wording 'Pairs created in this run'
- Extend saveMatchingResults(eventId, suggestions, runId) and set originRunId
- Scheduler/Admin run-now: always pass runId
- Admin API: GET /api/admin/events/:slug/matching-runs/:runId/suggestions
- Prisma: add compound index on (origin_run_id, status)
- Frontend: add getRunSuggestions, expand row in MatchingRunsSection with 'Pairs created in this run' wording
- New adminAPI for run-now and runs listing
- MatchingRunsSection with refresh and run controls
- Integrate into EventDetailsPage under matching configuration
Backend changes:
- Modified getUserSuggestions to include match data (id, slug, status)
- Returns match info for both toBeRecorded and toRecord suggestions
Frontend changes:
- Added useNavigate hook to RecordingTab
- Capture match data from updateSuggestionStatus response
- Added MessageCircle icon and chat button to SuggestionCard
- Show "Open Chat" button for accepted suggestions with active matches
- Navigate to /matches/{matchSlug}/chat when clicked
This completes the recording stats flow by allowing users to easily
access the match chat after accepting a recording suggestion.
- Updated all profile links to use /@username format
- Made usernames clickable in chat messages
- Added URL parameter sanitization to strip @ when fetching user data
- Ensures consistent profile URL format across the application
Implemented display of country flags and competitor numbers in event chat messages:
- Country flags displayed as emoji (🇸🇪, 🇵🇱, etc.) with proper emoji font support
- Competitor numbers shown in #123 format next to usernames
- Normalized data architecture with user and participant caches on frontend
- User data (username, avatar, country) and participant data (competitorNumber) cached separately
- Messages store only core data (id, content, userId, createdAt)
- Prevents data inconsistency when users update profile information
- Fixed duplicate message keys React warning with deduplication logic
- Backend sends nested user/participant objects for cache population
- Auto-updates across all messages when user changes avatar or country
Backend changes:
- Socket.IO event_message and message_history include nested user/participant data
- API /events/:slug/messages endpoint restructured with same nested format
- Batch lookup of competitor numbers for efficiency
Frontend changes:
- useEventChat hook maintains userCache and participantCache
- ChatMessage component accepts separate user/participant props
- ChatMessageList performs cache lookups during render
- Emoji font family support for cross-platform flag rendering
- Save vertical space on mobile by showing page title in navbar
- Mobile: "spotlight.cam - {PageTitle}" instead of separate h1
- Desktop: unchanged - page titles remain as separate headings
- Saves ~60-80px vertical space on mobile devices
Pages updated:
- Dashboard, Events, Matches, History, Profile
- Add w-full to MatchCard for consistent card width
- Use Layout noPadding to avoid padding conflicts
- Add explicit padding and width control to main container
- Ensure All, Pending, and Active tabs have identical width
- Show error alert when login fails instead of console-only logging
- Display user-friendly error message: "Invalid email or password"
- Clear error state before new login attempt
- Use existing Alert component for consistency with RegisterPage
- Replace "Edit heats" button with icon-only version
- Remove connection status indicator (show "Disconnected" warning only when offline)
- Remove event location from header
- Hide Participants tab on desktop (sidebar already visible)
- Remove "Participants" header from sidebar on desktop
- Add fullWidth prop to Layout component for chat pages
- Redesign EventChatPage and MatchChatPage with fixed layout:
- Navbar sticky to top (no gaps)
- Event/partner header directly below navbar
- Chat content fills available space (flex-1)
- Input area fixed to bottom
- Full screen width on mobile (no margins)
- Translate RecordingTab UI strings to English
- Move Leave Event button to header
- Remove unnecessary margins and max-width constraints
This provides a better mobile experience with full-screen chat
interface similar to native messaging apps.
- Add CONNECTION_STATE (disconnected, connecting, connected, failed)
- Add SUGGESTION_TYPE (toBeRecorded, toRecord)
- Update useWebRTC.js to use CONNECTION_STATE
- Update MatchChatPage.jsx to use CONNECTION_STATE
- Update RecordingTab.jsx to use SUGGESTION_TYPE
- Create constants/statuses.js with MATCH_STATUS, SUGGESTION_STATUS, MATCH_FILTER
- Update MatchCard, MatchesPage, HistoryPage, RatePartnerPage to use MATCH_STATUS
- Update RecordingTab to use SUGGESTION_STATUS
- Update Navbar to use MATCH_STATUS for API calls
Split DashboardPage (578 lines) into focused components:
- DashboardEventCard: event card with chat access
- DashboardMatchCard: match card with status indicators
- MatchRequestCards: incoming/outgoing request cards
- EmptyState: reusable empty state component (in common/)
DashboardPage now 295 lines (-49%)
Split large EventDetailsPage (545 lines) into smaller, focused components:
- QRCodeSection: QR code display and copy link functionality
- ParticipantsSection: participants list with avatars
- MatchingConfigSection: deadline and matching controls
- ScheduleConfigSection: division slot configuration
EventDetailsPage now 148 lines (-73%)
Allow event organizers to configure which divisions run in parallel
(same time slot) for accurate collision detection in the auto-matching
algorithm. Divisions in the same slot will collide with each other.
- Add scheduleConfig JSON field to Event model
- Add PUT /events/:slug/schedule-config API endpoint
- Update matching algorithm to use slot-based collision detection
- Add UI in EventDetailsPage for managing division slots
- Add unit tests for schedule-based collision detection
Add frontend components for auto-matching recording partners:
- RecordingTab component with suggestions list and opt-out toggle
- Tab navigation in EventChatPage (Chat, Uczestnicy, Nagrywanie)
- Matching configuration in EventDetailsPage (deadline, run matching)
- matchingAPI functions in api.js
- Return registrationDeadline and matchingRunAt in GET /events/:slug/details
UI allows users to:
- View who will record their heats
- View heats they need to record
- Accept/reject suggestions
- Opt-out from being a recorder
- Set registration deadline (admin)
- Manually trigger matching (admin)
Allow participants to set their bib/competitor number per event.
Display as badge next to username in participant lists.
- Add competitorNumber field to EventParticipant model
- Add PUT /events/:slug/competitor-number endpoint
- Include competitorNumber in heats/me and heats/all responses
- Add input field in HeatsBanner component
- Display badge in UserListItem component
- Add unit tests for competitor number feature
Track unread messages in match chats and display count badge:
- Schema: Add user1LastReadAt/user2LastReadAt to Match model
- Backend: Calculate unreadCount in dashboard API
- Socket: Update lastReadAt when user joins match room
- Frontend: Display red badge with unread count on match avatar
Show real-time count of users currently in each event chat room.
- Backend: Export getEventsOnlineCounts from socket module
- Dashboard API: Include onlineCount for each active event
- Frontend: Display online count with animated green dot indicator
Replace simple spinner with skeleton loading placeholders that match
the dashboard layout structure, providing better visual feedback during
data loading.
- Install react-hot-toast library
- Add Toaster component to App.jsx
- Show success/error toasts for match accept/reject/cancel
- Show toasts for real-time match events
- Update tests with toast mocks
- Create DashboardPage with active events, matches, and requests
- Add dashboardAPI.getData() to services/api.js
- Add /dashboard route as default landing after login
- Update Navbar with Dashboard and Events links
- Show video exchange and rating status for matches
- Handle match accept/reject/cancel actions
Bug: EventChatPage referenced formatHeat() function in header's heat display
(line 365) but the function was removed during Phase 3 refactoring when
creating the HeatBadges component.
Solution:
1. Enhanced HeatBadges component with badgeClassName prop to support
custom styling (needed for dark header background)
2. Replaced manual heat rendering in EventChatPage header with
HeatBadges component
3. Passed custom badge styling to match the dark primary-700 header
Changes:
- HeatBadges.jsx: Added badgeClassName prop for style customization
- EventChatPage.jsx: Replaced manual heat map with HeatBadges component
Fixes: "Uncaught ReferenceError: formatHeat is not defined" error
Separate concerns - move Socket.IO and form logic from components to reusable hooks
New Hooks:
- useForm: Generic form state management with handleChange/handleSubmit/reset
- useEventChat: Extract Socket.IO logic from EventChatPage (156 lines)
* Manages messages, active users, connection state
* Handles send message, load older messages with scroll preservation
* Real-time updates via Socket.IO event listeners
- useMatchChat: Extract Socket.IO logic from MatchChatPage (115 lines)
* Manages 1:1 chat messages and connection
* Loads message history from API
* Real-time message sync via Socket.IO
Pages Refactored:
- EventChatPage: 661 → 564 lines (-97 lines, -15%)
- MatchChatPage: 517 → 446 lines (-71 lines, -14%)
Benefits:
- Cleaner component code - UI separated from business logic
- Reusable hooks can be used in other components
- Easier to test - hooks can be unit tested independently
- Better code organization - single responsibility principle
- 168 lines eliminated from pages, moved to 271 lines of reusable hooks
Phase 2 Total: -168 lines
Grand Total (Phase 1+2): -389 lines (-12%)
- Add Alert component with 4 variants (success/error/warning/info)
- Add LoadingSpinner and LoadingButton components
- Add FormInput and FormSelect components with icon support
- Add Modal and ConfirmationModal components
- Add ChatMessage, ChatMessageList, and ChatInput components
- Add EventCard component
These components will eliminate ~450 lines of duplicated code across pages.
Part of Phase 1 (Quick Wins) frontend refactoring.
- Add reusable Avatar with fallback, status dot, ring
- Replace <img> uses in Navbar, Profile, PublicProfile
- Use Avatar in MatchChatPage and EventChatPage messages and sidebars
- Fix own-message detection for snake_case payloads