# Dashboard Plan - spotlight.cam > **✅ COMPLETED:** 2025-11-21 > **Archived:** This plan has been fully implemented. See `docs/archive/COMPLETED.md` for implementation details. **Created:** 2025-11-21 **Status:** ✅ IMPLEMENTED **Priority:** HIGH - Core UX improvement --- ## 🎯 Overview Create a centralized dashboard for logged-in users to: - View checked-in events with quick access to chats - Manage active matches and conversations - Track video exchange status - Monitor rating completion - See pending match requests **Route:** `/dashboard` (default landing page after login) --- ## 📊 Dashboard Sections ### 1. Active Events Section **Purpose:** Show events user is currently participating in **Data Source:** `EventParticipant` joined with `Event` **Card Content:** ``` ┌─────────────────────────────────────────┐ │ 🎉 Warsaw Dance Festival │ │ 📍 Warsaw, Poland │ │ 📅 Nov 23-25, 2025 │ │ │ │ Your heats: J&J NOV 1 L, STR INT 2 F │ │ 👥 45 participants • 12 online │ │ │ │ [Enter Event Chat] ──────────────────► │ └─────────────────────────────────────────┘ ``` **Features:** - Event name, location, dates - User's declared heats (from `EventUserHeat`) - Participant count, online count (from Socket.IO active users) - **Primary action:** "Enter Event Chat" → `/events/:slug/chat` - Sort: Upcoming events first, then by start date **Empty State:** ``` ┌─────────────────────────────────────────┐ │ 📅 No Active Events │ │ │ │ Check in at an event to start │ │ connecting with other dancers! │ │ │ │ [Browse Events] ────────────────────► │ └─────────────────────────────────────────┘ ``` --- ### 2. Active Matches Section **Purpose:** Show ongoing match conversations and their status **Data Source:** `Match` joined with `User`, `Event`, `Rating`, `Message` **Card Content:** ``` ┌─────────────────────────────────────────┐ │ 👤 Sarah Martinez │ │ @sarah_swings │ │ 📍 Warsaw Dance Festival │ │ │ │ Video Exchange: │ │ ✅ You sent • ✅ Received │ │ │ │ Ratings: │ │ ✅ You rated • ⏳ Waiting for partner │ │ │ │ 💬 3 new messages │ │ │ │ [Open Chat] [Rate Partner] ──────────► │ └─────────────────────────────────────────┘ ``` **Status Indicators:** **Video Exchange:** - ✅ You sent video (check for message: "📹 Video sent:") - ✅ You received video (check for message from partner) - ⏳ No videos yet - 🔗 Link shared (message type: 'link') **Ratings:** - ✅ You rated (Rating exists with raterId = currentUser) - ✅ Partner rated you (Rating exists with ratedId = currentUser) - ⏳ Not rated yet - ⚠️ Complete collaboration to rate **Actions:** - **Primary:** "Open Chat" → `/matches/:slug` - **Secondary:** "Rate Partner" → `/matches/:slug/rate` (only if video exchanged) **Match States:** - **Pending** (status='pending'): Show "Waiting for acceptance" - **Accepted** (status='accepted'): Active conversation - **Completed** (status='completed'): Both rated, show summary **Sort Order:** 1. Unread messages (new messages first) 2. Pending ratings (video sent but not rated) 3. Recently active (latest message timestamp) **Empty State:** ``` ┌─────────────────────────────────────────┐ │ 🤝 No Active Matches │ │ │ │ Join an event chat and send a match │ │ request to start collaborating! │ └─────────────────────────────────────────┘ ``` --- ### 3. Match Requests Section **Purpose:** Show pending incoming/outgoing match requests **Data Source:** `Match` where `status='pending'` **Incoming Request Card:** ``` ┌─────────────────────────────────────────┐ │ 📨 Match Request from John Dancer │ │ @john_dancer │ │ 📍 Warsaw Dance Festival │ │ Heats: J&J NOV 1 L, STR INT 2 F │ │ │ │ [Accept] [Decline] ──────────────────► │ └─────────────────────────────────────────┘ ``` **Outgoing Request Card:** ``` ┌─────────────────────────────────────────┐ │ 📤 Request sent to Sarah Martinez │ │ @sarah_swings │ │ 📍 Warsaw Dance Festival │ │ │ │ ⏳ Waiting for response... │ │ │ │ [Cancel Request] ────────────────────► │ └─────────────────────────────────────────┘ ``` **Logic:** - **Incoming:** `Match.user2Id = currentUser.id` AND `status='pending'` - **Outgoing:** `Match.user1Id = currentUser.id` AND `status='pending'` **Empty State:** ``` ┌─────────────────────────────────────────┐ │ 📭 No Pending Requests │ └─────────────────────────────────────────┘ ``` --- ### 4. Recent Activity Feed (Optional - Phase 2) **Purpose:** Show recent notifications and updates **Content:** - 🎉 New match accepted - 💬 New message in match chat - ⭐ Partner rated you - 📹 Video received - 📨 New match request --- ## 🔧 Technical Implementation ### Backend - New API Endpoint **Endpoint:** `GET /api/dashboard` **Response Structure:** ```json { "success": true, "data": { "activeEvents": [ { "id": 420, "slug": "another-dance-event", "name": "Another Dance Event", "location": "Warsaw, Poland", "startDate": "2025-11-23", "endDate": "2025-11-25", "participantsCount": 45, "onlineCount": 12, "myHeats": [ { "id": 1, "competitionType": { "name": "Jack & Jill", "abbreviation": "J&J" }, "division": { "name": "Novice", "abbreviation": "NOV" }, "heatNumber": 1, "role": "Leader" } ] } ], "activeMatches": [ { "id": 123, "slug": "match-abc123", "partner": { "id": 456, "username": "sarah_swings", "firstName": "Sarah", "lastName": "Martinez", "avatar": "https://..." }, "event": { "id": 420, "name": "Warsaw Dance Festival" }, "videoExchange": { "sentByMe": true, "receivedFromPartner": true, "lastVideoTimestamp": "2025-11-21T15:30:00Z" }, "ratings": { "ratedByMe": true, "ratedByPartner": false }, "unreadCount": 3, "lastMessageAt": "2025-11-21T16:00:00Z", "status": "accepted" } ], "matchRequests": { "incoming": [ { "id": 124, "slug": "match-def456", "requester": { "id": 789, "username": "john_dancer", "firstName": "John", "lastName": "Dancer", "avatar": "https://..." }, "event": { "id": 420, "name": "Warsaw Dance Festival" }, "requesterHeats": [ /* heats array */ ], "createdAt": "2025-11-21T14:00:00Z" } ], "outgoing": [ { "id": 125, "slug": "match-ghi789", "recipient": { "id": 101, "username": "anna_swing", "firstName": "Anna", "lastName": "Swing", "avatar": "https://..." }, "event": { "id": 420, "name": "Warsaw Dance Festival" }, "createdAt": "2025-11-21T13:00:00Z" } ] } } } ``` **Implementation Notes:** 1. **Video Exchange Detection:** - Query `Message` table for match's chat room - Check for messages containing "📹 Video sent:" or type='link' - Track sender to determine who sent/received 2. **Unread Message Count:** - Option A: Add `lastReadAt` field to Match model (requires migration) - Option B: Client-side tracking with localStorage - Option C: Skip for MVP, add later 3. **Online Count:** - Socket.IO activeUsers map (real-time, not persistent) - Return from backend when available --- ### Frontend Components **New Components to Create:** 1. **`DashboardPage.jsx`** - Main dashboard page 2. **`components/dashboard/`** - `EventCard.jsx` - Active event card (reuse/extend existing EventCard) - `MatchCard.jsx` - Active match card with status indicators - `MatchRequestCard.jsx` - Incoming/outgoing request card - `VideoExchangeStatus.jsx` - Visual indicator for video status - `RatingStatus.jsx` - Visual indicator for rating status - `EmptyState.jsx` - Generic empty state component **Component Hierarchy:** ``` DashboardPage ├── Layout │ ├── Header │ └── Content │ ├── Section: Active Events │ │ ├── EventCard (multiple) │ │ └── EmptyState (if no events) │ ├── Section: Active Matches │ │ ├── MatchCard (multiple) │ │ │ ├── VideoExchangeStatus │ │ │ └── RatingStatus │ │ └── EmptyState (if no matches) │ └── Section: Match Requests │ ├── Subsection: Incoming │ │ └── MatchRequestCard (multiple) │ └── Subsection: Outgoing │ └── MatchRequestCard (multiple) ``` --- ## 📋 Implementation Roadmap ### Phase 1: Backend Foundation (4-5 hours) **Step 1: Create Dashboard API Endpoint (2h)** - [ ] Create `GET /api/dashboard` route - [ ] Implement data aggregation logic: - Query user's active events with heats - Query accepted matches with partner info - Detect video exchange from messages - Check rating completion - Query pending match requests - [ ] Add unit tests - [ ] Test with real data **Step 2: Enhance Match Model (Optional, 1h)** - [ ] Consider adding fields to Match: - `videosSent` (JSON: { user1Sent: bool, user2Sent: bool }) - `lastReadBy` (JSON: { user1: timestamp, user2: timestamp }) - [ ] Create migration if needed - [ ] Update API to use new fields **Step 3: Socket.IO Enhancement (1h)** - [ ] Add event: `dashboard_update` for real-time updates - [ ] Emit when: - New match request received - Match accepted - New message in match chat - Partner rated ### Phase 2: Frontend Implementation (6-8 hours) **Step 1: Create Dashboard Components (3h)** - [ ] Create `pages/DashboardPage.jsx` - [ ] Create `components/dashboard/EventCard.jsx` - [ ] Create `components/dashboard/MatchCard.jsx` - [ ] Create `components/dashboard/MatchRequestCard.jsx` - [ ] Create `components/dashboard/VideoExchangeStatus.jsx` - [ ] Create `components/dashboard/RatingStatus.jsx` - [ ] Create `components/dashboard/EmptyState.jsx` **Step 2: API Integration (2h)** - [ ] Add `dashboardAPI.getData()` to services/api.js - [ ] Fetch dashboard data on mount - [ ] Handle loading and error states **Step 3: Routing & Navigation (1h)** - [ ] Add `/dashboard` route - [ ] Redirect to `/dashboard` after login (instead of `/events`) - [ ] Update navbar to highlight dashboard when active - [ ] Add "Dashboard" link to main navigation **Step 4: Real-time Updates (1h)** - [ ] Listen to Socket.IO `dashboard_update` event - [ ] Update dashboard data when events occur - [ ] Show toast notifications for important events **Step 5: Polish & Responsive Design (1h)** - [ ] Mobile-responsive layout (stack cards vertically) - [ ] Loading skeletons - [ ] Smooth animations - [ ] Empty states with helpful CTAs ### Phase 3: Testing & Refinement (2h) - [ ] Manual testing of all dashboard features - [ ] Test edge cases (no events, no matches, all completed) - [ ] Test real-time updates - [ ] Fix bugs and polish UX --- ## 🎨 Design Mockup ``` ┌─────────────────────────────────────────────────────────────┐ │ [Logo] Dashboard Events Matches Profile [👤] │ └─────────────────────────────────────────────────────────────┘ 🏠 Dashboard ┌─────────────────────────────────────────────────────────────┐ │ 📅 Your Events [View All]│ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │ 🎉 Warsaw Festival │ │ 🎉 Swing Camp BCN │ │ │ │ Nov 23-25 │ │ Dec 10-12 │ │ │ │ 45 participants │ │ 120 participants │ │ │ │ [Enter Chat] ───────►│ │ [Enter Chat] ───────►│ │ │ └──────────────────────┘ └──────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ 💬 Active Matches [View All]│ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 👤 Sarah Martinez @sarah_swings │ │ │ │ 📍 Warsaw Dance Festival │ │ │ │ │ │ │ │ Videos: ✅ Sent • ✅ Received │ │ │ │ Ratings: ✅ You rated • ⏳ Waiting │ │ │ │ │ │ │ │ 💬 3 new messages │ │ │ │ │ │ │ │ [Open Chat] ──────────────────────────────────────► │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 👤 John Dancer @john_dancer │ │ │ │ 📍 Swing Camp Barcelona │ │ │ │ │ │ │ │ Videos: ✅ Sent • ⏳ Waiting │ │ │ │ Ratings: ⏳ Not ready to rate │ │ │ │ │ │ │ │ [Open Chat] [Send Video] ─────────────────────────► │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ 📨 Match Requests │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Incoming (2) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 👤 Anna Swing @anna_swing │ │ │ │ 📍 Warsaw Dance Festival │ │ │ │ Heats: J&J NOV 1 L │ │ │ │ │ │ │ │ [Accept] [Decline] ───────────────────────────────► │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ Outgoing (1) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 👤 Mike Lead @mike_lead │ │ │ │ 📍 Swing Camp Barcelona │ │ │ │ │ │ │ │ ⏳ Waiting for response... [Cancel] │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 📊 Success Metrics **User Engagement:** - % of users who visit dashboard after login (target: 80%+) - Average time spent on dashboard (target: 2-3 min) - Click-through rate to event chats (target: 60%+) - Click-through rate to match chats (target: 80%+) **Feature Adoption:** - % of users who complete video exchange (track via dashboard) - % of users who complete ratings (track via dashboard) - Match acceptance rate from dashboard (target: 70%+) **Technical Performance:** - Dashboard load time (target: < 500ms) - API response time (target: < 200ms) - Real-time update latency (target: < 100ms) --- ## 🚧 Future Enhancements (Phase 4+) 1. **Activity Feed** - Timeline of all user activities - Filterable by type (matches, messages, ratings) 2. **Statistics Dashboard** - Total matches - Average rating received - Events attended - Videos exchanged 3. **Calendar Integration** - Show upcoming events on calendar - Add to Google Calendar / iCal 4. **Quick Actions** - "Find matches in my next event" - "Review past collaborations" - "Update my competition heats" 5. **Notifications Center** - Centralized notification inbox - Mark as read/unread - Notification preferences 6. **Mobile-First Enhancements** - Swipe gestures for actions - Bottom navigation bar - Native app feel --- ## ⚠️ Technical Considerations ### Video Exchange Tracking **Current State:** - Videos sent via WebRTC P2P (don't touch server) - Only confirmation message saved: "📹 Video sent: {filename} ({size} MB)" - No explicit tracking in database **Options:** **Option A: Parse messages (MVP approach)** - ✅ No database changes - ✅ Works with existing implementation - ❌ Fragile (depends on message format) - ❌ Can't distinguish between re-sends **Option B: Add tracking fields to Match model** - ✅ Explicit, reliable tracking - ✅ Can track timestamps, file info - ❌ Requires migration - ❌ Needs frontend changes to send tracking events **Recommendation:** Start with Option A (parse messages) for MVP, migrate to Option B in Phase 2. ### Unread Message Count **Options:** **Option A: Client-side with localStorage** - ✅ No backend changes - ✅ Simple implementation - ❌ Not synced across devices - ❌ Lost on cache clear **Option B: Add lastReadAt to Match model** - ✅ Synced across devices - ✅ Persistent - ❌ Requires migration - ❌ More complex logic **Option C: Separate ReadReceipt table** - ✅ Most flexible - ✅ Can support group chats later - ❌ Most complex - ❌ Additional queries **Recommendation:** Start with Option A for MVP, migrate to Option B in Phase 2. --- ## 📝 Notes - Dashboard should be the default landing page after login - Current flow: Login → `/events` (should become → `/dashboard`) - Keep existing navigation working (direct links to /events, /matches still accessible) - Dashboard is read-only (no actions except navigation) - Actions (accept match, send message) happen on dedicated pages - Focus on information density without overwhelming the user - Use consistent design language with existing pages --- **Total Estimated Effort:** 12-15 hours **Priority:** HIGH (Core UX improvement) **Dependencies:** None (works with existing backend) **Risk:** LOW (additive feature, doesn't break existing functionality) --- **Created by:** Claude Code **Date:** 2025-11-21 **Version:** 1.0