Files
spotlightcam/docs/DASHBOARD_PLAN.md
Radosław Gierwiało 901b046a34 feat(backend): implement dashboard API endpoint
- Add GET /api/dashboard endpoint for authenticated users
- Returns active events with user heats
- Returns accepted matches with partner info
- Detects video exchange status from message parsing
- Tracks rating completion status (rated by me/partner)
- Returns incoming/outgoing pending match requests
- Add comprehensive test suite (12 tests, 93% coverage)
- Add DASHBOARD_PLAN.md with full design documentation
2025-11-21 21:00:50 +01:00

24 KiB

Dashboard Plan - spotlight.cam

Created: 2025-11-21 Status: Planning Phase 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:

{
  "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