- Move completed DASHBOARD_PLAN.md to docs/archive/ - Update COMPLETED.md with dashboard implementation details - Update TODO.md to reflect dashboard completion - Mark remaining dashboard features as optional Phase 4
24 KiB
Dashboard Plan - spotlight.cam
✅ COMPLETED: 2025-11-21 Archived: This plan has been fully implemented. See
docs/archive/COMPLETED.mdfor 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:
- Unread messages (new messages first)
- Pending ratings (video sent but not rated)
- 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.idANDstatus='pending' - Outgoing:
Match.user1Id = currentUser.idANDstatus='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:
-
Video Exchange Detection:
- Query
Messagetable for match's chat room - Check for messages containing "📹 Video sent:" or type='link'
- Track sender to determine who sent/received
- Query
-
Unread Message Count:
- Option A: Add
lastReadAtfield to Match model (requires migration) - Option B: Client-side tracking with localStorage
- Option C: Skip for MVP, add later
- Option A: Add
-
Online Count:
- Socket.IO activeUsers map (real-time, not persistent)
- Return from backend when available
Frontend Components
New Components to Create:
DashboardPage.jsx- Main dashboard pagecomponents/dashboard/EventCard.jsx- Active event card (reuse/extend existing EventCard)MatchCard.jsx- Active match card with status indicatorsMatchRequestCard.jsx- Incoming/outgoing request cardVideoExchangeStatus.jsx- Visual indicator for video statusRatingStatus.jsx- Visual indicator for rating statusEmptyState.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/dashboardroute - 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_updatefor 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
/dashboardroute - Redirect to
/dashboardafter 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_updateevent - 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+)
-
Activity Feed
- Timeline of all user activities
- Filterable by type (matches, messages, ratings)
-
Statistics Dashboard
- Total matches
- Average rating received
- Events attended
- Videos exchanged
-
Calendar Integration
- Show upcoming events on calendar
- Add to Google Calendar / iCal
-
Quick Actions
- "Find matches in my next event"
- "Review past collaborations"
- "Update my competition heats"
-
Notifications Center
- Centralized notification inbox
- Mark as read/unread
- Notification preferences
-
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