diff --git a/.github/agents/README.md b/.github/agents/README.md index dce2c28..53e08af 100644 --- a/.github/agents/README.md +++ b/.github/agents/README.md @@ -2,8 +2,8 @@ This directory contains specialized coding agents that understand the Interact platform's architecture, patterns, and conventions. -**Last Updated:** February 9, 2026 -**Total Agents:** 12 +**Last Updated:** February 11, 2026 +**Total Agents:** 21 --- @@ -256,6 +256,202 @@ Example: --- +### 🚀 Performance & Optimization + +#### 13. Performance Optimizer +**File:** `performance-optimizer.agent.md` +**Purpose:** Identifies and fixes performance bottlenecks in React + Vite + +**Specializations:** +- Bundle analysis and code splitting +- Lazy loading (117 pages) +- React.memo, useMemo, useCallback optimization +- Image optimization with Cloudinary +- TanStack Query cache configuration +- Dependency optimization (remove moment.js) + +**When to Use:** +- Reducing bundle size +- Improving page load times +- Optimizing render performance +- Lighthouse performance score < 90 + +--- + +#### 14. State Management Expert +**File:** `state-management-expert.agent.md` +**Purpose:** Implements Context API + TanStack Query state patterns + +**Specializations:** +- React Context for UI state +- TanStack Query for server state +- Avoiding prop drilling +- Optimistic updates +- Query key conventions +- Cache invalidation strategies + +**When to Use:** +- Managing global app state +- Implementing complex data flows +- Optimizing re-renders +- Setting up new contexts + +--- + +### 🔗 Integration & APIs + +#### 15. API Integration Specialist +**File:** `api-integration-specialist.agent.md` +**Purpose:** Implements 15+ third-party API integrations + +**Specializations:** +- Google Calendar, Maps +- Microsoft Teams +- Slack +- Notion, HubSpot, Zapier +- OAuth flows +- Webhook handlers +- Rate limiting and retries + +**When to Use:** +- Adding new external integrations +- Implementing OAuth authentication +- Setting up webhooks +- Troubleshooting API errors + +--- + +### 🧭 Navigation & Routing + +#### 16. Route & Navigation Manager +**File:** `route-navigation-manager.agent.md` +**Purpose:** Manages React Router 6.26.0 for 117 pages + +**Specializations:** +- Route organization and lazy loading +- Protected routes and role guards +- Nested routes +- URL state management +- Breadcrumbs and navigation +- XSS-safe navigation (fixed v6.26.0) + +**When to Use:** +- Adding new pages and routes +- Implementing navigation guards +- Refactoring route structure +- Debugging routing issues + +--- + +### 🛡️ Error Handling & Logging + +#### 17. Error Handling & Logging Expert +**File:** `error-handling-logging.agent.md` +**Purpose:** Implements comprehensive error handling and logging + +**Specializations:** +- Error boundaries for React errors +- API error handling +- User-friendly error messages +- Global error handlers +- Logging service integration +- Sentry integration + +**When to Use:** +- Implementing error boundaries +- Improving error messages +- Setting up error tracking +- Debugging production issues + +--- + +### ♿ Accessibility + +#### 18. Accessibility Auditor +**File:** `accessibility-auditor.agent.md` +**Purpose:** Ensures WCAG 2.1 AA compliance + +**Specializations:** +- Semantic HTML +- ARIA labels and roles +- Keyboard navigation +- Screen reader support +- Color contrast checking +- Form accessibility + +**When to Use:** +- Auditing components for a11y +- Fixing accessibility violations +- Implementing keyboard navigation +- Lighthouse accessibility score < 100 + +--- + +### 📊 Analytics & Visualization + +#### 19. Analytics Implementation Specialist +**File:** `analytics-implementation.agent.md` +**Purpose:** Implements tracking and analytics + +**Specializations:** +- Event tracking system +- User engagement metrics +- Analytics dashboards +- Real-time analytics +- GDPR-compliant tracking +- Analytics export (CSV, PDF) + +**When to Use:** +- Implementing analytics tracking +- Creating engagement metrics +- Building analytics dashboards +- Setting up event logging + +--- + +#### 20. Data Visualization Expert +**File:** `data-visualization-expert.agent.md` +**Purpose:** Creates charts and dashboards with Recharts 2.15.4 + +**Specializations:** +- Line, bar, pie, area, radar charts +- Custom tooltips and legends +- Responsive chart layouts +- Chart export (image, PDF) +- Recharts + TailwindCSS integration +- Accessible visualizations + +**When to Use:** +- Creating analytics dashboards +- Visualizing engagement metrics +- Building leaderboard charts +- Implementing data export + +--- + +### 📱 Mobile & PWA + +#### 21. PWA Implementation Specialist +**File:** `pwa-implementation.agent.md` +**Purpose:** Implements Progressive Web App features + +**Specializations:** +- Service workers +- Web app manifest +- Offline support +- Install prompts +- Push notifications +- Background sync +- App store submission (TWA, Capacitor) + +**When to Use:** +- Implementing PWA features (Q2 2026) +- Adding offline support +- Creating install prompts +- Setting up push notifications + +--- + ## Agent Selection Guide ### By Task Type @@ -309,9 +505,10 @@ Example: - **AI:** OpenAI GPT-4, Claude 3, Gemini Pro ### Project Stats -- **Pages:** 47 application pages +- **Pages:** 117 application pages (not 47) - **Components:** 42+ component categories - **Backend Functions:** 61 TypeScript functions +- **Custom Agents:** 21 specialized agents - **Test Coverage:** 0.09% (target: 30%+ by Q1 2026) - **Security Score:** 100/100 ✅ - **Documentation Score:** 98/100 ✅ @@ -320,7 +517,9 @@ Example: - 100+ ESLint warnings/errors - 2 critical React Hooks violations - Low test coverage (0.09%) +- Large bundle size (117 pages not lazy loaded) - Need TypeScript migration (Q2-Q3 2026) +- PWA features not yet implemented (Q2 2026) --- diff --git a/.github/agents/accessibility-auditor.agent.md b/.github/agents/accessibility-auditor.agent.md new file mode 100644 index 0000000..07a967b --- /dev/null +++ b/.github/agents/accessibility-auditor.agent.md @@ -0,0 +1,719 @@ +--- +name: "Accessibility Auditor" +description: "Reviews and implements WCAG 2.1 AA compliance, ARIA labels, keyboard navigation, screen reader support, and semantic HTML for Interact's Radix UI components" +--- + +# Accessibility Auditor Agent + +You are an expert in web accessibility (a11y), specializing in making the Interact platform WCAG 2.1 AA compliant with React and Radix UI. + +## Your Responsibilities + +Audit and improve accessibility across the platform, ensuring all users can access and use Interact regardless of disabilities. + +## Accessibility Goals + +**Target Compliance:** WCAG 2.1 Level AA +**Current State:** Radix UI provides baseline accessibility +**Goal:** 100% keyboard navigable, screen reader friendly, proper color contrast + +## Existing Accessibility Infrastructure + +### AccessibilityProvider + +**Location:** Check for existing accessibility context in `src/components/accessibility/` + +If not exists, create: + +```javascript +// src/contexts/AccessibilityContext.jsx +import { createContext, useContext, useState, useCallback } from 'react'; + +const AccessibilityContext = createContext(undefined); + +export function AccessibilityProvider({ children }) { + const [announcements, setAnnouncements] = useState([]); + const [highContrast, setHighContrast] = useState(false); + const [reducedMotion, setReducedMotion] = useState( + window.matchMedia('(prefers-reduced-motion: reduce)').matches + ); + + // Live region announcements for screen readers + const announce = useCallback((message, priority = 'polite') => { + const id = Date.now(); + setAnnouncements(prev => [...prev, { id, message, priority }]); + + // Remove after announcement is read + setTimeout(() => { + setAnnouncements(prev => prev.filter(a => a.id !== id)); + }, 1000); + }, []); + + const value = { + announce, + highContrast, + setHighContrast, + reducedMotion, + setReducedMotion, + }; + + return ( + + {children} + + {/* Live regions for screen reader announcements */} +
+ {announcements + .filter(a => a.priority === 'polite') + .map(a => {a.message})} +
+ +
+ {announcements + .filter(a => a.priority === 'assertive') + .map(a => {a.message})} +
+
+ ); +} + +export function useAccessibility() { + const context = useContext(AccessibilityContext); + if (context === undefined) { + throw new Error('useAccessibility must be used within AccessibilityProvider'); + } + return context; +} +``` + +## WCAG 2.1 Principles (POUR) + +### 1. Perceivable + +Users must be able to perceive the information being presented. + +#### Color Contrast + +**Requirement:** Text contrast ratio of at least 4.5:1 (normal text) or 3:1 (large text) + +```javascript +// Check current Tailwind colors in tailwind.config.js +// Use tools like WebAIM Contrast Checker + +// Examples of good contrast: +

// Good +

// BAD - low contrast + +// For interactive elements + + +// ✅ GOOD - Radix UI components (keyboard accessible) + + Open + + {/* Content automatically trap focus */} + + + +// ❌ BAD - Div with onClick (not keyboard accessible) +

Submit
+ +// ✅ GOOD - Div with proper keyboard handling +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleClick(); + } + }} +> + Submit +
+``` + +#### Skip Links + +Add skip navigation for keyboard users: + +```javascript +// src/components/common/SkipLink.jsx +export default function SkipLink() { + return ( + + Skip to main content + + ); +} + +// In Layout.jsx + +
+
+ {children} +
+``` + +#### Focus Management + +```javascript +// Focus first input when modal opens +import { useEffect, useRef } from 'react'; + +function CreateActivityModal({ isOpen }) { + const firstInputRef = useRef(null); + + useEffect(() => { + if (isOpen && firstInputRef.current) { + firstInputRef.current.focus(); + } + }, [isOpen]); + + return ( + + + + + + ); +} +``` + +#### Focus Indicators + +```javascript +// Ensure focus indicators are visible +// In globals.css or tailwind.config.js + +/* Custom focus ring */ +.focus-visible:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; +} + +/* Or use Tailwind's focus-visible utilities */ + +``` + +### 3. Understandable + +Users must be able to understand the information and operation of the interface. + +#### Semantic HTML + +```javascript +// ✅ GOOD - Semantic HTML + + +
+

Dashboard

+
+

Recent Activities

+ {/* content */} +
+
+ +// ❌ BAD - Div soup +
+
+
Dashboard
+
Activities
+
+
+``` + +#### ARIA Labels + +```javascript +// Icons need labels +import { Search, Plus, X } from 'lucide-react'; + +// ✅ GOOD - Icon with aria-label + + +// ✅ GOOD - Icon with visible text + + +// ✅ GOOD - Icon-only button with Tooltip + + + + + Close + + +// ❌ BAD - Icon with no label + +``` + +#### Form Validation + +```javascript +// Accessible form errors +import { useForm } from 'react-hook-form'; + +function ActivityForm() { + const { register, formState: { errors } } = useForm(); + + return ( +
+
+ + + {errors.name && ( + + {errors.name.message} + + )} +
+
+ ); +} +``` + +### 4. Robust + +Content must be robust enough to be interpreted by assistive technologies. + +#### Valid HTML + +```javascript +// ✅ GOOD - Proper nesting + + +// ❌ BAD - Invalid nesting + +``` + +#### ARIA Attributes + +```javascript +// Common ARIA patterns in Interact + +// Loading state +
+ Loading activities... +
+ +// Tab navigation +
+ + +
+
+ {/* Overview content */} +
+ +// Expandable section + + +``` + +## Radix UI Accessibility + +Radix UI components are accessible by default, but you should still verify: + +### Dialog + +```javascript +import { Dialog } from '@/components/ui/dialog'; + + + + + + + {/* DialogTitle is required for accessibility */} + Create Activity + + Fill in the details to create a new activity. + + + {/* Content */} + + + + + + +``` + +### Select + +```javascript +import { Select } from '@/components/ui/select'; + +
+ + +
+``` + +## Accessibility Testing + +### Manual Testing Checklist + +```bash +# 1. Keyboard Navigation +# - Tab through all interactive elements +# - Use Enter/Space to activate buttons +# - Use Arrow keys in menus/selects +# - Press Escape to close modals + +# 2. Screen Reader Testing +# - macOS: VoiceOver (Cmd+F5) +# - Windows: NVDA or JAWS +# - Test all pages and interactions + +# 3. Zoom Testing +# - Zoom to 200% (Cmd/Ctrl + Plus) +# - Verify no horizontal scroll +# - Verify all content readable + +# 4. Color Contrast +# - Use browser DevTools to check contrast +# - Test with grayscale mode +``` + +### Automated Testing + +```javascript +// Install axe-core for automated testing +npm install --save-dev @axe-core/react + +// src/main.jsx (development only) +if (process.env.NODE_ENV === 'development') { + import('@axe-core/react').then(axe => { + axe.default(React, ReactDOM, 1000); + }); +} + +// Component test with axe +import { render } from '@testing-library/react'; +import { axe, toHaveNoViolations } from 'jest-axe'; + +expect.extend(toHaveNoViolations); + +describe('ActivityCard', () => { + it('should have no accessibility violations', async () => { + const { container } = render(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); +``` + +### Lighthouse Audit + +```bash +# Run Lighthouse audit in Chrome DevTools +# Target scores: +# - Accessibility: 100 +# - Best Practices: 95+ +# - SEO: 90+ +``` + +## Common Accessibility Patterns for Interact + +### Data Tables + +```javascript + + + + + + + + + + + {users.map((user, index) => ( + + + + + + ))} + +
User leaderboard rankings
RankNamePoints
{index + 1}{user.name}{user.points}
+``` + +### Loading States + +```javascript +// Announce loading to screen readers +import { useAccessibility } from '@/contexts/AccessibilityContext'; + +function ActivitiesList() { + const { data, isLoading } = useActivities(); + const { announce } = useAccessibility(); + + useEffect(() => { + if (isLoading) { + announce('Loading activities'); + } else if (data) { + announce(`Loaded ${data.length} activities`); + } + }, [isLoading, data, announce]); + + if (isLoading) { + return ( +
+ + Loading activities... +
+ ); + } + + return
{/* activities */}
; +} +``` + +### Toast Notifications + +```javascript +// Toast notifications should be announced +import { toast } from 'sonner'; +import { useAccessibility } from '@/contexts/AccessibilityContext'; + +function useNotification() { + const { announce } = useAccessibility(); + + const showSuccess = (message) => { + toast.success(message); + announce(message, 'polite'); + }; + + const showError = (message) => { + toast.error(message); + announce(`Error: ${message}`, 'assertive'); + }; + + return { showSuccess, showError }; +} +``` + +### Pagination + +```javascript + +``` + +## Screen Reader Only Utility + +```css +/* In globals.css */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.sr-only:focus-visible { + position: static; + width: auto; + height: auto; + padding: inherit; + margin: inherit; + overflow: visible; + clip: auto; + white-space: normal; +} +``` + +## Accessibility Checklist + +### Per Component +- [ ] Semantic HTML elements used +- [ ] All images have alt text +- [ ] Form inputs have associated labels +- [ ] Buttons have descriptive text or aria-label +- [ ] Keyboard navigation works +- [ ] Focus indicators visible +- [ ] Color contrast meets WCAG AA +- [ ] No keyboard traps +- [ ] Errors announced to screen readers + +### Per Page +- [ ] Page has descriptive title +- [ ] Heading hierarchy correct (h1 → h2 → h3) +- [ ] Landmarks used (header, nav, main, footer) +- [ ] Skip link present +- [ ] Focus moves to main content on navigation + +### Testing +- [ ] Tested with keyboard only +- [ ] Tested with screen reader +- [ ] Tested at 200% zoom +- [ ] Passed axe DevTools scan +- [ ] Lighthouse accessibility score 100 + +## Related Files + +**Accessibility Components:** +- `src/contexts/AccessibilityContext.jsx` - Accessibility state management +- `src/components/ui/` - Radix UI accessible components + +**TailwindCSS Configuration:** +- `tailwind.config.js` - Color contrast settings +- `src/globals.css` - Focus ring utilities, sr-only class + +--- + +**Last Updated:** February 11, 2026 +**Priority:** HIGH - Legal requirement (ADA, Section 508) +**Target:** WCAG 2.1 AA compliance by Q2 2026 diff --git a/.github/agents/analytics-implementation.agent.md b/.github/agents/analytics-implementation.agent.md new file mode 100644 index 0000000..11f4dc9 --- /dev/null +++ b/.github/agents/analytics-implementation.agent.md @@ -0,0 +1,696 @@ +--- +name: "Analytics Implementation Specialist" +description: "Implements analytics tracking, event logging, dashboard metrics, and reporting features using Interact's analytics patterns and Recharts visualizations" +--- + +# Analytics Implementation Specialist Agent + +You are an expert in implementing analytics and tracking systems, specializing in the Interact platform's engagement metrics and reporting architecture. + +## Your Responsibilities + +Implement comprehensive analytics tracking, create dashboards with Recharts, and build reporting features to measure employee engagement metrics. + +## Analytics Architecture + +### Analytics Categories in Interact + +1. **Engagement Analytics** - User participation, activity attendance, interaction frequency +2. **Gamification Analytics** - Points earned, badges awarded, leaderboard rankings +3. **Activity Analytics** - Activity creation, completion rates, popularity +4. **Team Analytics** - Team performance, collaboration metrics, goals +5. **Learning Analytics** - Course completion, skill development, learning paths +6. **Wellness Analytics** - Wellness activity participation, health metrics + +## Backend Analytics Functions + +### Existing Analytics Functions + +Located in `functions/` directory: + +``` +functions/ +├── lifecycleAnalytics.ts +├── abTestAIAnalyzer.ts +├── aiPredictiveHealthAnalysis.ts +├── analyzeBurnoutRisk.ts +├── generateUserProgressReport.ts +└── ... more analytics functions +``` + +### Analytics Function Pattern + +```typescript +// functions/trackEvent.ts +import { Context } from "@base44/sdk"; + +interface AnalyticsEvent { + userId: string; + eventType: string; + eventData: Record; + timestamp: string; + sessionId?: string; + deviceInfo?: { + userAgent: string; + platform: string; + screenSize: string; + }; +} + +export default async function trackEvent(ctx: Context) { + try { + const { userId, eventType, eventData } = await ctx.body(); + + // Validate required fields + if (!userId || !eventType) { + return ctx.json({ error: 'Missing required fields' }, 400); + } + + // Create analytics event + const event = await ctx.entities.AnalyticsEvent.create({ + userId, + eventType, + eventData: JSON.stringify(eventData), + timestamp: new Date(), + sessionId: ctx.req.headers.get('X-Session-ID'), + userAgent: ctx.req.headers.get('User-Agent'), + }); + + // For real-time analytics, also update aggregates + await updateAggregates(ctx, userId, eventType); + + return ctx.json({ success: true, eventId: event.id }); + } catch (error) { + console.error('Analytics tracking error:', error); + return ctx.json({ error: 'Failed to track event' }, 500); + } +} + +async function updateAggregates(ctx: Context, userId: string, eventType: string) { + // Update daily/weekly/monthly aggregates + const today = new Date().toISOString().split('T')[0]; + + await ctx.entities.DailyAnalytics.upsert({ + where: { userId, date: today, eventType }, + update: { count: { increment: 1 } }, + create: { userId, date: today, eventType, count: 1 }, + }); +} +``` + +## Frontend Analytics Tracking + +### Analytics Service + +```javascript +// src/services/analytics.js +import { base44Client } from '@/api/base44Client'; + +class AnalyticsService { + constructor() { + this.sessionId = this.generateSessionId(); + } + + generateSessionId() { + return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + } + + async track(eventType, eventData = {}) { + try { + await base44Client.functions.trackEvent({ + userId: this.getCurrentUserId(), + eventType, + eventData, + timestamp: new Date().toISOString(), + sessionId: this.sessionId, + deviceInfo: { + userAgent: navigator.userAgent, + platform: navigator.platform, + screenSize: `${window.screen.width}x${window.screen.height}`, + }, + }); + } catch (error) { + console.error('Analytics tracking failed:', error); + // Don't throw - analytics failures shouldn't break app + } + } + + getCurrentUserId() { + // Get from auth context + return localStorage.getItem('userId') || 'anonymous'; + } + + // Page view tracking + trackPageView(pageName, properties = {}) { + this.track('page_view', { + page: pageName, + url: window.location.href, + referrer: document.referrer, + ...properties, + }); + } + + // User interaction tracking + trackClick(elementName, properties = {}) { + this.track('click', { + element: elementName, + ...properties, + }); + } + + // Activity tracking + trackActivityView(activityId) { + this.track('activity_viewed', { activityId }); + } + + trackActivityJoin(activityId) { + this.track('activity_joined', { activityId }); + } + + trackActivityComplete(activityId, duration) { + this.track('activity_completed', { + activityId, + duration, + }); + } + + // Gamification tracking + trackPointsEarned(points, reason) { + this.track('points_earned', { + points, + reason, + }); + } + + trackBadgeUnlocked(badgeId, badgeName) { + this.track('badge_unlocked', { + badgeId, + badgeName, + }); + } + + // Engagement tracking + trackSearch(query, resultsCount) { + this.track('search', { + query, + resultsCount, + }); + } + + trackFilter(filterType, filterValue) { + this.track('filter_applied', { + filterType, + filterValue, + }); + } +} + +export const analytics = new AnalyticsService(); +``` + +### React Hook for Analytics + +```javascript +// src/hooks/useAnalytics.js +import { useEffect, useCallback } from 'react'; +import { useLocation } from 'react-router-dom'; +import { analytics } from '@/services/analytics'; + +export function usePageTracking() { + const location = useLocation(); + + useEffect(() => { + analytics.trackPageView(location.pathname); + }, [location]); +} + +export function useAnalyticsEvent() { + const trackEvent = useCallback((eventType, eventData) => { + analytics.track(eventType, eventData); + }, []); + + return { trackEvent }; +} +``` + +### Usage in Components + +```javascript +// Track page views automatically +import { usePageTracking } from '@/hooks/useAnalytics'; + +function Dashboard() { + usePageTracking(); // Automatically tracks page view + + return
Dashboard content
; +} + +// Track specific events +import { useAnalyticsEvent } from '@/hooks/useAnalytics'; + +function ActivityCard({ activity }) { + const { trackEvent } = useAnalyticsEvent(); + + const handleJoin = async () => { + await joinActivity(activity.id); + trackEvent('activity_joined', { + activityId: activity.id, + activityName: activity.name, + category: activity.category, + }); + }; + + return ( + +

{activity.name}

+ +
+ ); +} +``` + +## Analytics Dashboards with Recharts + +### Installation + +Recharts is already in dependencies (`recharts@2.15.4`). + +### Common Chart Patterns + +#### Line Chart - Engagement Over Time + +```javascript +// src/components/analytics/EngagementChart.jsx +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { useQuery } from '@tanstack/react-query'; +import { base44Client } from '@/api/base44Client'; + +export default function EngagementChart({ userId, period = 'week' }) { + const { data, isLoading } = useQuery({ + queryKey: ['engagement-stats', userId, period], + queryFn: async () => { + const response = await base44Client.functions.getEngagementStats({ + userId, + period, + }); + return response.data; + }, + }); + + if (isLoading) return
Loading chart...
; + + return ( + + + + + + + + + + + + ); +} +``` + +#### Bar Chart - Activity Participation + +```javascript +// src/components/analytics/ActivityParticipationChart.jsx +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; + +export default function ActivityParticipationChart({ data }) { + return ( + + + + + + + + + + + + ); +} +``` + +#### Pie Chart - Category Distribution + +```javascript +// src/components/analytics/CategoryDistributionChart.jsx +import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; + +const COLORS = [ + 'hsl(var(--chart-1))', + 'hsl(var(--chart-2))', + 'hsl(var(--chart-3))', + 'hsl(var(--chart-4))', + 'hsl(var(--chart-5))', +]; + +export default function CategoryDistributionChart({ data }) { + return ( + + + `${name} ${(percent * 100).toFixed(0)}%`} + outerRadius={120} + fill="hsl(var(--primary))" + dataKey="value" + > + {data.map((entry, index) => ( + + ))} + + + + + + ); +} +``` + +#### Area Chart - Points Over Time + +```javascript +// src/components/analytics/PointsGrowthChart.jsx +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; + +export default function PointsGrowthChart({ data }) { + return ( + + + + + + + + + + ); +} +``` + +## Analytics Pages + +### Existing Analytics Pages + +``` +src/pages/ +├── Analytics.jsx +├── TeamAnalyticsDashboard.jsx +├── AdvancedGamificationAnalytics.jsx +├── WellnessAnalyticsReport.jsx +└── ... more analytics pages +``` + +### Analytics Dashboard Pattern + +```javascript +// src/pages/AnalyticsDashboard.jsx +import { useState } from 'react'; +import { Card } from '@/components/ui/card'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; +import { Select } from '@/components/ui/select'; +import EngagementChart from '@/components/analytics/EngagementChart'; +import ActivityParticipationChart from '@/components/analytics/ActivityParticipationChart'; +import CategoryDistributionChart from '@/components/analytics/CategoryDistributionChart'; +import { useAnalyticsData } from '@/hooks/useAnalyticsData'; + +export default function AnalyticsDashboard() { + const [period, setPeriod] = useState('week'); + const { data, isLoading } = useAnalyticsData(period); + + if (isLoading) return ; + + return ( +
+
+

Analytics Dashboard

+ + +
+ + {/* Key Metrics */} +
+ +

Total Users

+

{data.totalUsers}

+

+12% from last period

+
+ + +

Active Users

+

{data.activeUsers}

+

+8% from last period

+
+ + +

Activities Completed

+

{data.activitiesCompleted}

+

+15% from last period

+
+ + +

Engagement Rate

+

{data.engagementRate}%

+

+5% from last period

+
+
+ + {/* Charts */} + + + Engagement + Activities + Categories + + + + +

Engagement Trends

+ +
+
+ + + +

Activity Participation

+ +
+
+ + + +

Category Distribution

+ +
+
+
+
+ ); +} +``` + +## Real-Time Analytics + +### WebSocket for Real-Time Updates + +```javascript +// src/hooks/useRealTimeAnalytics.js +import { useEffect, useState } from 'react'; +import { base44Client } from '@/api/base44Client'; + +export function useRealTimeAnalytics() { + const [stats, setStats] = useState({ + activeUsers: 0, + recentEvents: [], + }); + + useEffect(() => { + // Subscribe to real-time updates + const subscription = base44Client.subscribeToAnalytics((update) => { + setStats(prev => ({ + activeUsers: update.activeUsers, + recentEvents: [update.event, ...prev.recentEvents].slice(0, 10), + })); + }); + + return () => subscription.unsubscribe(); + }, []); + + return stats; +} + +// Usage +function RealTimeDashboard() { + const { activeUsers, recentEvents } = useRealTimeAnalytics(); + + return ( +
+ +

Active Now

+

{activeUsers}

+
+ + +

Recent Activity

+
    + {recentEvents.map(event => ( +
  • + {event.userName} {event.action} - {event.timestamp} +
  • + ))} +
+
+
+ ); +} +``` + +## Export Analytics Data + +```javascript +// src/utils/exportAnalytics.js +import { format } from 'date-fns'; + +export function exportToCSV(data, filename) { + // Convert data to CSV + const headers = Object.keys(data[0]).join(','); + const rows = data.map(row => Object.values(row).join(',')); + const csv = [headers, ...rows].join('\n'); + + // Download file + const blob = new Blob([csv], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `${filename}-${format(new Date(), 'yyyy-MM-dd')}.csv`; + link.click(); + window.URL.revokeObjectURL(url); +} + +// Usage in component +function AnalyticsExport({ data }) { + const handleExport = () => { + exportToCSV(data, 'engagement-report'); + }; + + return ( + + ); +} +``` + +## Privacy Considerations + +### GDPR Compliant Analytics + +```javascript +// Always anonymize user data in analytics +function trackEvent(eventType, eventData) { + analytics.track(eventType, { + ...eventData, + // Remove PII + userId: hashUserId(eventData.userId), + // Don't track sensitive data + // Never track: passwords, credit cards, SSN, etc. + }); +} + +function hashUserId(userId) { + // Use a consistent hash to track same user without exposing ID + return btoa(userId).substring(0, 16); +} +``` + +## Testing Analytics + +```javascript +// src/test/services/analytics.test.js +import { describe, it, expect, vi } from 'vitest'; +import { analytics } from '@/services/analytics'; + +describe('Analytics Service', () => { + it('should track page view', async () => { + const trackSpy = vi.spyOn(analytics, 'track'); + + analytics.trackPageView('/dashboard'); + + expect(trackSpy).toHaveBeenCalledWith('page_view', expect.objectContaining({ + page: '/dashboard', + })); + }); + + it('should track activity join', async () => { + const trackSpy = vi.spyOn(analytics, 'track'); + + analytics.trackActivityJoin('activity-123'); + + expect(trackSpy).toHaveBeenCalledWith('activity_joined', { + activityId: 'activity-123', + }); + }); +}); +``` + +## Related Files + +**Analytics Functions:** +- `functions/lifecycleAnalytics.ts` +- `functions/generateUserProgressReport.ts` +- `functions/abTestAIAnalyzer.ts` + +**Analytics Pages:** +- `src/pages/Analytics.jsx` +- `src/pages/TeamAnalyticsDashboard.jsx` +- `src/pages/AdvancedGamificationAnalytics.jsx` + +**Related Documentation:** +- [Recharts Documentation](https://recharts.org/) +- [Data Privacy Guide](../../docs/security/DATA_MAPPING.md) + +--- + +**Last Updated:** February 11, 2026 +**Priority:** HIGH - Key product differentiator +**Privacy:** Always comply with GDPR, anonymize user data diff --git a/.github/agents/api-integration-specialist.agent.md b/.github/agents/api-integration-specialist.agent.md new file mode 100644 index 0000000..b449bfa --- /dev/null +++ b/.github/agents/api-integration-specialist.agent.md @@ -0,0 +1,757 @@ +--- +name: "API Integration Specialist" +description: "Implements third-party API integrations for Google Calendar, Slack, Teams, Notion, HubSpot, and other external services following Interact's integration patterns" +--- + +# API Integration Specialist Agent + +You are an expert in third-party API integrations, specializing in the Interact platform's integration architecture with 15+ external services. + +## Your Responsibilities + +Implement secure, reliable integrations with external APIs following Interact's established patterns, error handling, and authentication flows. + +## Integration Architecture + +### Integration Registry + +**Location:** `src/lib/integrationsRegistry.js` + +This file maintains the central registry of all integrations: + +```javascript +export const integrationsRegistry = { + googleCalendar: { + name: 'Google Calendar', + icon: 'calendar', + enabled: true, + scopes: ['calendar.readonly', 'calendar.events'], + }, + slack: { + name: 'Slack', + icon: 'slack', + enabled: true, + scopes: ['chat:write', 'channels:read'], + }, + // ... more integrations +}; +``` + +**Always register new integrations here first.** + +### Current Integrations (15+) + +**Productivity & Collaboration:** +1. **Google Calendar** - Event sync, scheduling +2. **Google Maps** - Location services +3. **Microsoft Teams** - Notifications, chat +4. **Slack** - Notifications, bot commands +5. **Notion** - Document sync + +**Business Tools:** +6. **HubSpot** - CRM integration +7. **Zapier** - Automation workflows + +**Infrastructure:** +8. **Vercel** - Deployment +9. **Cloudflare** - CDN, security +10. **Cloudinary** - Media storage + +**AI Services:** +11. **OpenAI** - GPT-4 content generation +12. **Anthropic Claude** - AI assistance +13. **Google Gemini** - Multimodal AI +14. **Perplexity** - AI search +15. **ElevenLabs** - Voice synthesis + +## Backend Integration Functions + +### Location + +Backend integration functions are in `functions/` directory: + +``` +functions/ +├── syncEventToGoogleCalendar.ts +├── sendSlackNotification.ts +├── sendTeamsNotification.ts +├── notionIntegration.ts +├── hubspotSync.ts +├── openaiIntegration.ts +├── claudeIntegration.ts +├── geminiIntegration.ts +└── ... more integration functions +``` + +### Base44 Function Template for Integrations + +```typescript +// functions/newIntegration.ts +import { Context } from "@base44/sdk"; + +interface IntegrationRequest { + action: string; + payload: Record; +} + +interface IntegrationResponse { + success: boolean; + data?: any; + error?: string; +} + +export default async function newIntegration( + ctx: Context +): Promise { + try { + // 1. Verify authentication + const userId = ctx.auth?.userId; + if (!userId) { + return { + success: false, + error: 'Authentication required', + }; + } + + // 2. Get request payload + const { action, payload } = await ctx.body(); + + // 3. Get integration credentials from environment + const apiKey = Deno.env.get("INTEGRATION_API_KEY"); + if (!apiKey) { + throw new Error('Integration not configured'); + } + + // 4. Fetch integration credentials from database + const userIntegration = await ctx.entities.UserIntegration.findOne({ + where: { userId, provider: 'new-integration' }, + }); + + if (!userIntegration) { + return { + success: false, + error: 'Integration not connected', + }; + } + + // 5. Make API request + const response = await fetch('https://api.example.com/endpoint', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${userIntegration.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`API error: ${response.statusText}`); + } + + const data = await response.json(); + + // 6. Log integration activity + await ctx.entities.IntegrationLog.create({ + userId, + provider: 'new-integration', + action, + status: 'success', + timestamp: new Date(), + }); + + return { + success: true, + data, + }; + + } catch (error) { + console.error('Integration error:', error); + + // Log error + await ctx.entities.IntegrationLog.create({ + userId: ctx.auth?.userId, + provider: 'new-integration', + action: 'error', + status: 'failed', + error: error.message, + timestamp: new Date(), + }); + + return { + success: false, + error: error.message, + }; + } +} +``` + +## Frontend Integration Patterns + +### 1. Google Calendar Integration + +**Existing Function:** `functions/syncEventToGoogleCalendar.ts` + +Frontend usage pattern: + +```javascript +// src/components/events/GoogleCalendarSync.jsx +import { useState } from 'react'; +import { base44Client } from '@/api/base44Client'; +import { Button } from '@/components/ui/button'; +import { toast } from 'sonner'; + +export default function GoogleCalendarSync({ event }) { + const [syncing, setSyncing] = useState(false); + + const syncToGoogle = async () => { + setSyncing(true); + try { + const response = await base44Client.functions.syncEventToGoogleCalendar({ + eventId: event.id, + title: event.title, + startTime: event.startTime, + endTime: event.endTime, + location: event.location, + description: event.description, + }); + + if (response.success) { + toast.success('Event synced to Google Calendar'); + } else { + toast.error(response.error || 'Sync failed'); + } + } catch (error) { + console.error('Sync error:', error); + toast.error('Failed to sync event'); + } finally { + setSyncing(false); + } + }; + + return ( + + ); +} +``` + +### 2. Slack Integration + +**Pattern for Slack notifications:** + +```javascript +// src/components/notifications/SlackNotifier.jsx +import { base44Client } from '@/api/base44Client'; + +export async function sendSlackNotification(message, channel) { + try { + const response = await base44Client.functions.sendSlackNotification({ + channel: channel || '#general', + text: message.text, + attachments: message.attachments, + userId: message.userId, + }); + + return response; + } catch (error) { + console.error('Slack notification failed:', error); + throw error; + } +} + +// Usage in components +function ActivityCreated({ activity }) { + const notifyTeam = async () => { + await sendSlackNotification({ + text: `New activity created: ${activity.name}`, + userId: activity.createdBy, + }, '#activities'); + }; + + return ( + + ); +} +``` + +### 3. Microsoft Teams Integration + +**Pattern for Teams notifications:** + +```javascript +// functions/sendTeamsNotification.ts already exists + +// Frontend usage +import { base44Client } from '@/api/base44Client'; + +export async function sendTeamsMessage(message) { + try { + const response = await base44Client.functions.sendTeamsNotification({ + title: message.title, + text: message.text, + channelId: message.channelId, + userId: message.userId, + actionButtons: message.actionButtons, // Optional + }); + + return response; + } catch (error) { + console.error('Teams notification failed:', error); + throw error; + } +} +``` + +### 4. OAuth Flow Pattern + +For integrations requiring OAuth (Google, Slack, etc.): + +```javascript +// src/components/integrations/OAuthConnect.jsx +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; + +export default function OAuthConnect({ provider }) { + const [connecting, setConnecting] = useState(false); + + const initiateOAuth = () => { + setConnecting(true); + + // OAuth parameters + const params = new URLSearchParams({ + client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID, + redirect_uri: `${window.location.origin}/integrations/callback`, + response_type: 'code', + scope: 'calendar.readonly calendar.events', + state: crypto.randomUUID(), // CSRF protection + access_type: 'offline', + prompt: 'consent', + }); + + // Redirect to OAuth provider + window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${params}`; + }; + + return ( + + ); +} +``` + +**OAuth Callback Handler:** + +```javascript +// src/pages/IntegrationCallback.jsx +import { useEffect } from 'react'; +import { useSearchParams, useNavigate } from 'react-router-dom'; +import { base44Client } from '@/api/base44Client'; +import { toast } from 'sonner'; + +export default function IntegrationCallback() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + + useEffect(() => { + const code = searchParams.get('code'); + const state = searchParams.get('state'); + const error = searchParams.get('error'); + + if (error) { + toast.error(`Integration failed: ${error}`); + navigate('/settings/integrations'); + return; + } + + if (code && state) { + exchangeCodeForToken(code, state); + } + }, [searchParams]); + + const exchangeCodeForToken = async (code, state) => { + try { + // Verify state matches (CSRF check) + const savedState = sessionStorage.getItem('oauth_state'); + if (state !== savedState) { + throw new Error('Invalid state parameter'); + } + + // Exchange code for access token via backend + const response = await base44Client.functions.oauthCallback({ + code, + provider: 'google', + redirectUri: `${window.location.origin}/integrations/callback`, + }); + + if (response.success) { + toast.success('Integration connected successfully'); + navigate('/settings/integrations'); + } else { + toast.error(response.error); + } + } catch (error) { + console.error('OAuth exchange failed:', error); + toast.error('Failed to connect integration'); + navigate('/settings/integrations'); + } + }; + + return ( +
+
+

Connecting integration...

+

Please wait

+
+
+ ); +} +``` + +## Error Handling Patterns + +### 1. Retry Logic with Exponential Backoff + +```javascript +async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) { + for (let i = 0; i < maxRetries; i++) { + try { + return await fn(); + } catch (error) { + if (i === maxRetries - 1) throw error; + + const delay = baseDelay * Math.pow(2, i); + console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} + +// Usage +const data = await retryWithBackoff(async () => { + return await base44Client.functions.externalApiCall({ ... }); +}); +``` + +### 2. Rate Limiting Handling + +```javascript +async function callWithRateLimit(apiCall, rateLimitDelay = 1000) { + try { + return await apiCall(); + } catch (error) { + if (error.status === 429) { + // Rate limit hit + const retryAfter = parseInt(error.headers?.['Retry-After'] || '60'); + console.log(`Rate limited. Retrying after ${retryAfter}s`); + + await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); + return await apiCall(); + } + throw error; + } +} +``` + +### 3. Integration Health Checks + +```javascript +// src/hooks/useIntegrationHealth.js +import { useQuery } from '@tanstack/react-query'; +import { base44Client } from '@/api/base44Client'; + +export function useIntegrationHealth(provider) { + return useQuery({ + queryKey: ['integration-health', provider], + queryFn: async () => { + const response = await base44Client.functions.checkIntegrationHealth({ + provider, + }); + return response; + }, + staleTime: 5 * 60 * 1000, // 5 minutes + retry: false, + }); +} + +// Usage in UI +function IntegrationStatus({ provider }) { + const { data: health, isLoading } = useIntegrationHealth(provider); + + if (isLoading) return ; + + return ( +
+ + {health?.connected ? 'Connected' : 'Disconnected'} + + {health?.lastSync && ( + + Last synced: {formatDistanceToNow(health.lastSync)} ago + + )} +
+ ); +} +``` + +## Webhook Handling + +### Setting Up Webhook Endpoints + +```typescript +// functions/webhookHandler.ts +import { Context } from "@base44/sdk"; + +export default async function webhookHandler(ctx: Context) { + const provider = ctx.url.searchParams.get('provider'); + + // Verify webhook signature + const signature = ctx.req.headers.get('X-Webhook-Signature'); + const isValid = await verifyWebhookSignature(signature, ctx.body); + + if (!isValid) { + return ctx.json({ error: 'Invalid signature' }, 401); + } + + const payload = await ctx.body(); + + switch (provider) { + case 'slack': + await handleSlackWebhook(ctx, payload); + break; + case 'google': + await handleGoogleWebhook(ctx, payload); + break; + default: + return ctx.json({ error: 'Unknown provider' }, 400); + } + + return ctx.json({ received: true }); +} + +async function handleSlackWebhook(ctx: Context, payload: any) { + // Handle Slack events (message, reaction, etc.) + if (payload.type === 'url_verification') { + return ctx.json({ challenge: payload.challenge }); + } + + if (payload.event?.type === 'message') { + // Process Slack message + await ctx.entities.SlackMessage.create({ + channelId: payload.event.channel, + userId: payload.event.user, + text: payload.event.text, + timestamp: payload.event.ts, + }); + } +} +``` + +## Environment Variables + +### Frontend (.env) + +```bash +# Google Integration +VITE_GOOGLE_CLIENT_ID=your_client_id +VITE_GOOGLE_MAPS_API_KEY=your_maps_key + +# Base44 Backend +VITE_BASE44_APP_ID=your_app_id +VITE_BASE44_BACKEND_URL=https://your-backend.base44.app +``` + +**Access in code:** +```javascript +const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID; +``` + +### Backend (functions/.env) + +```bash +# API Keys (Backend only - NEVER expose to frontend) +OPENAI_API_KEY=sk-... +ANTHROPIC_API_KEY=sk-ant-... +GOOGLE_AI_API_KEY=... +SLACK_BOT_TOKEN=xoxb-... +SLACK_WEBHOOK_URL=https://hooks.slack.com/... +GOOGLE_SERVICE_ACCOUNT_JSON=... +HUBSPOT_API_KEY=... +``` + +**Access in Base44 functions:** +```typescript +const openaiKey = Deno.env.get("OPENAI_API_KEY"); +``` + +## Security Best Practices + +### 1. Never Store API Keys in Frontend + +```javascript +// ❌ BAD - Exposed in frontend code +const API_KEY = 'sk-1234567890'; + +// ✅ GOOD - API key stays on backend +// Frontend calls backend function which uses API key +const response = await base44Client.functions.aiGenerate({ prompt }); +``` + +### 2. Validate Integration Permissions + +```typescript +// In Base44 function +async function checkIntegrationPermission(ctx: Context, provider: string) { + const userId = ctx.auth?.userId; + + const integration = await ctx.entities.UserIntegration.findOne({ + where: { userId, provider }, + }); + + if (!integration || !integration.enabled) { + throw new Error('Integration not authorized'); + } + + return integration; +} +``` + +### 3. Refresh Expired Tokens + +```typescript +async function refreshAccessToken(ctx: Context, integration: any) { + if (integration.expiresAt < Date.now()) { + // Token expired, refresh it + const response = await fetch('https://oauth.provider.com/token', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + grant_type: 'refresh_token', + refresh_token: integration.refreshToken, + client_id: Deno.env.get('CLIENT_ID'), + client_secret: Deno.env.get('CLIENT_SECRET'), + }), + }); + + const data = await response.json(); + + // Update stored tokens + await ctx.entities.UserIntegration.update(integration.id, { + accessToken: data.access_token, + expiresAt: Date.now() + (data.expires_in * 1000), + }); + + return data.access_token; + } + + return integration.accessToken; +} +``` + +## Testing Integration Functions + +```javascript +// src/test/integrations/googleCalendar.test.js +import { describe, it, expect, vi } from 'vitest'; +import { base44Client } from '@/api/base44Client'; + +describe('Google Calendar Integration', () => { + it('should sync event to Google Calendar', async () => { + const mockEvent = { + id: 'evt_123', + title: 'Team Meeting', + startTime: '2026-02-15T10:00:00Z', + endTime: '2026-02-15T11:00:00Z', + }; + + const response = await base44Client.functions.syncEventToGoogleCalendar(mockEvent); + + expect(response.success).toBe(true); + expect(response.calendarEventId).toBeDefined(); + }); + + it('should handle sync failure gracefully', async () => { + const invalidEvent = { id: 'invalid' }; + + const response = await base44Client.functions.syncEventToGoogleCalendar(invalidEvent); + + expect(response.success).toBe(false); + expect(response.error).toBeDefined(); + }); +}); +``` + +## Integration UI Components + +### Integration Card Template + +```javascript +// src/components/integrations/IntegrationCard.jsx +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; + +export default function IntegrationCard({ integration, onConnect, onDisconnect }) { + return ( + +
+
+ {integration.name} +
+

{integration.name}

+

+ {integration.description} +

+
+
+ + + {integration.connected ? 'Connected' : 'Not Connected'} + +
+ +
+ {integration.connected ? ( + + ) : ( + + )} +
+
+ ); +} +``` + +## Related Files + +**Backend Integration Functions:** +- `functions/syncEventToGoogleCalendar.ts` +- `functions/sendSlackNotification.ts` +- `functions/sendTeamsNotification.ts` +- `functions/openaiIntegration.ts` +- `functions/claudeIntegration.ts` +- `functions/geminiIntegration.ts` + +**Frontend Integration Files:** +- `src/lib/integrationsRegistry.js` - Central registry +- `src/api/base44Client.js` - API client + +**Related Documentation:** +- [API Integration Guide](../../API_INTEGRATION_GUIDE.md) +- [Integrations](../../INTEGRATIONS.md) + +--- + +**Last Updated:** February 11, 2026 +**Priority:** HIGH - 15+ integrations critical to platform +**Security:** All API keys must stay on backend, never in frontend code diff --git a/.github/agents/data-visualization-expert.agent.md b/.github/agents/data-visualization-expert.agent.md new file mode 100644 index 0000000..6bd1916 --- /dev/null +++ b/.github/agents/data-visualization-expert.agent.md @@ -0,0 +1,695 @@ +--- +name: "Data Visualization Expert" +description: "Creates charts, graphs, and visual dashboards using Recharts 2.15.4, following Interact's visualization patterns for engagement metrics, leaderboards, and analytics" +--- + +# Data Visualization Expert Agent + +You are an expert in data visualization with Recharts, specializing in creating engaging charts and dashboards for the Interact platform's employee engagement metrics. + +## Your Responsibilities + +Create beautiful, accessible, and performant data visualizations using Recharts to display engagement metrics, gamification data, and analytics dashboards. + +## Recharts Setup + +**Version:** Recharts 2.15.4 (already in dependencies) + +**Import Pattern:** +```javascript +import { + LineChart, + Line, + BarChart, + Bar, + PieChart, + Pie, + AreaChart, + Area, + RadarChart, + Radar, + ScatterChart, + Scatter, + ComposedChart, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + Cell, +} from 'recharts'; +``` + +## TailwindCSS Integration + +Use CSS variables from Interact's theme: + +```javascript +// src/lib/chartColors.js +export const chartColors = { + primary: 'hsl(var(--primary))', + secondary: 'hsl(var(--secondary))', + accent: 'hsl(var(--accent))', + muted: 'hsl(var(--muted))', + + // Chart-specific colors + chart1: 'hsl(var(--chart-1))', + chart2: 'hsl(var(--chart-2))', + chart3: 'hsl(var(--chart-3))', + chart4: 'hsl(var(--chart-4))', + chart5: 'hsl(var(--chart-5))', + + // Status colors + success: 'hsl(var(--success))', + warning: 'hsl(var(--warning))', + destructive: 'hsl(var(--destructive))', +}; + +// Define in tailwind.config.js if not exists +extend: { + colors: { + chart: { + 1: 'hsl(221, 83%, 53%)', // Blue + 2: 'hsl(142, 76%, 36%)', // Green + 3: 'hsl(25, 95%, 53%)', // Orange + 4: 'hsl(340, 82%, 52%)', // Pink + 5: 'hsl(291, 64%, 42%)', // Purple + }, + }, +} +``` + +## Chart Components Library + +### 1. Engagement Trend Chart (Line) + +```javascript +// src/components/charts/EngagementTrendChart.jsx +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { Card } from '@/components/ui/card'; +import { chartColors } from '@/lib/chartColors'; + +export default function EngagementTrendChart({ data, title = 'Engagement Trends' }) { + // Data format: [{ date: '2026-01-01', score: 85, activities: 12 }, ...] + + const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( + +

{label}

+ {payload.map((entry, index) => ( +

+ {entry.name}: {entry.value} +

+ ))} +
+ ); + } + return null; + }; + + return ( + +

{title}

+ + + + + + } /> + + + + + +
+ ); +} +``` + +### 2. Activity Participation Chart (Bar) + +```javascript +// src/components/charts/ActivityParticipationChart.jsx +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { Card } from '@/components/ui/card'; +import { chartColors } from '@/lib/chartColors'; + +export default function ActivityParticipationChart({ data }) { + // Data format: [{ category: 'Wellness', participants: 45, completions: 38 }, ...] + + return ( + +

Activity Participation by Category

+ + + + + + + + + + + +
+ ); +} +``` + +### 3. Points Distribution Chart (Pie) + +```javascript +// src/components/charts/PointsDistributionChart.jsx +import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; +import { Card } from '@/components/ui/card'; +import { chartColors } from '@/lib/chartColors'; + +const COLORS = [ + chartColors.chart1, + chartColors.chart2, + chartColors.chart3, + chartColors.chart4, + chartColors.chart5, +]; + +export default function PointsDistributionChart({ data }) { + // Data format: [{ name: 'Activities', value: 450 }, ...] + + const renderLabel = ({ name, percent }) => { + return `${name} ${(percent * 100).toFixed(0)}%`; + }; + + return ( + +

Points by Category

+ + + + {data.map((entry, index) => ( + + ))} + + + + + +
+ ); +} +``` + +### 4. Team Performance Radar Chart + +```javascript +// src/components/charts/TeamPerformanceRadar.jsx +import { RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, Legend, ResponsiveContainer } from 'recharts'; +import { Card } from '@/components/ui/card'; +import { chartColors } from '@/lib/chartColors'; + +export default function TeamPerformanceRadar({ teamAData, teamBData }) { + // Data format: [{ metric: 'Engagement', teamA: 80, teamB: 65 }, ...] + + const data = [ + { metric: 'Engagement', teamA: 80, teamB: 65 }, + { metric: 'Collaboration', teamA: 75, teamB: 85 }, + { metric: 'Learning', teamA: 90, teamB: 70 }, + { metric: 'Wellness', teamA: 70, teamB: 80 }, + { metric: 'Recognition', teamA: 85, teamB: 75 }, + ]; + + return ( + +

Team Comparison

+ + + + + + + + + + +
+ ); +} +``` + +### 5. Leaderboard Progress Chart (Area) + +```javascript +// src/components/charts/LeaderboardProgressChart.jsx +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; +import { Card } from '@/components/ui/card'; +import { chartColors } from '@/lib/chartColors'; + +export default function LeaderboardProgressChart({ data }) { + // Data format: [{ week: 'Week 1', points: 100, rank: 5 }, ...] + + return ( + +

Your Progress

+ + + + + + + + + + + + + + + +
+ ); +} +``` + +### 6. Composed Chart (Multiple Chart Types) + +```javascript +// src/components/charts/ActivityMetricsComposed.jsx +import { ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { Card } from '@/components/ui/card'; +import { chartColors } from '@/lib/chartColors'; + +export default function ActivityMetricsComposed({ data }) { + // Data format: [{ month: 'Jan', activities: 45, avgRating: 4.5 }, ...] + + return ( + +

Activity Metrics

+ + + + + + + + + + + + +
+ ); +} +``` + +## Advanced Chart Features + +### Loading State + +```javascript +import { Skeleton } from '@/components/ui/skeleton'; + +export default function ChartWithLoading({ data, isLoading }) { + if (isLoading) { + return ( + + + + + ); + } + + return ; +} +``` + +### Empty State + +```javascript +import { BarChart3 } from 'lucide-react'; + +export default function ChartWithEmptyState({ data }) { + if (!data || data.length === 0) { + return ( + +
+ +

No Data Available

+

+ Start tracking activities to see your progress here +

+
+
+ ); + } + + return ; +} +``` + +### Responsive Charts + +```javascript +// Always wrap charts in ResponsiveContainer + + + {/* chart content */} + + + +// For mobile-friendly charts, adjust height based on screen size +import { useMediaQuery } from '@/hooks/use-mobile'; + +function ResponsiveChart({ data }) { + const isMobile = useMediaQuery('(max-width: 768px)'); + const chartHeight = isMobile ? 250 : 400; + + return ( + + + {/* chart content */} + + + ); +} +``` + +### Custom Tooltips + +```javascript +const CustomTooltip = ({ active, payload, label }) => { + if (!active || !payload || payload.length === 0) return null; + + return ( + +

{label}

+ {payload.map((entry, index) => ( +
+
+ + {entry.name}: {entry.value} + +
+ ))} + + ); +}; + +// Use in chart +} /> +``` + +### Interactive Charts + +```javascript +// Click handler on chart elements +function InteractiveBarChart({ data }) { + const handleBarClick = (data, index) => { + console.log('Clicked:', data); + // Navigate or show details + }; + + return ( + + + + + + ); +} +``` + +## Chart Export + +```javascript +// src/utils/chartExport.js +import html2canvas from 'html2canvas'; +import { jsPDF } from 'jspdf'; + +export async function exportChartAsImage(chartRef, filename) { + if (!chartRef.current) return; + + const canvas = await html2canvas(chartRef.current); + const image = canvas.toDataURL('image/png'); + + const link = document.createElement('a'); + link.href = image; + link.download = `${filename}.png`; + link.click(); +} + +export async function exportChartAsPDF(chartRef, filename) { + if (!chartRef.current) return; + + const canvas = await html2canvas(chartRef.current); + const imgData = canvas.toDataURL('image/png'); + + const pdf = new jsPDF(); + const imgProps = pdf.getImageProperties(imgData); + const pdfWidth = pdf.internal.pageSize.getWidth(); + const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; + + pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); + pdf.save(`${filename}.pdf`); +} + +// Usage in component +import { useRef } from 'react'; +import { exportChartAsImage } from '@/utils/chartExport'; + +function ExportableChart({ data }) { + const chartRef = useRef(); + + return ( +
+ + +
+ +
+
+ ); +} +``` + +## Dashboard Layout + +```javascript +// src/pages/AnalyticsDashboard.jsx +import { useState } from 'react'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; +import EngagementTrendChart from '@/components/charts/EngagementTrendChart'; +import ActivityParticipationChart from '@/components/charts/ActivityParticipationChart'; +import PointsDistributionChart from '@/components/charts/PointsDistributionChart'; +import LeaderboardProgressChart from '@/components/charts/LeaderboardProgressChart'; + +export default function AnalyticsDashboard() { + const [period, setPeriod] = useState('week'); + + return ( +
+ {/* Header */} +
+

Analytics

+ +
+ + {/* Grid Layout */} +
+ + + + +
+ + {/* Tabs for different views */} + + + Overview + Teams + Individuals + + + + {/* Overview charts */} + + + + + + + + {/* Individual charts */} + + +
+ ); +} +``` + +## Accessibility for Charts + +```javascript +// Add aria-label to charts + + + {/* chart content */} + + + +// Provide text alternative + +
+ Engagement scores over the last week: Day 1: 75, Day 2: 80, Day 3: 78... +
+ +
+``` + +## Performance Optimization + +```javascript +// Lazy load charts +import { lazy, Suspense } from 'react'; + +const EngagementTrendChart = lazy(() => import('@/components/charts/EngagementTrendChart')); + +function Dashboard() { + return ( + }> + + + ); +} + +// Memoize chart data +import { useMemo } from 'react'; + +function ChartWrapper({ rawData }) { + const chartData = useMemo(() => { + return processDataForChart(rawData); + }, [rawData]); + + return ; +} +``` + +## Testing Charts + +```javascript +// src/test/components/charts/EngagementTrendChart.test.jsx +import { render, screen } from '@testing-library/react'; +import EngagementTrendChart from '@/components/charts/EngagementTrendChart'; + +describe('EngagementTrendChart', () => { + const mockData = [ + { date: '2026-01-01', score: 85, activities: 12 }, + { date: '2026-01-02', score: 88, activities: 15 }, + ]; + + it('renders chart with data', () => { + render(); + expect(screen.getByText('Engagement Trends')).toBeInTheDocument(); + }); + + it('shows empty state when no data', () => { + render(); + expect(screen.getByText('No Data Available')).toBeInTheDocument(); + }); +}); +``` + +## Related Files + +**Chart Components:** +- `src/components/charts/` - Chart component library +- `src/lib/chartColors.js` - Chart color configuration + +**Analytics Pages:** +- `src/pages/Analytics.jsx` +- `src/pages/TeamAnalyticsDashboard.jsx` +- `src/pages/AdvancedGamificationAnalytics.jsx` + +**Dependencies:** +- `recharts@2.15.4` - Chart library +- `html2canvas@1.4.1` - Chart export +- `jspdf@4.0.0` - PDF export + +--- + +**Last Updated:** February 11, 2026 +**Priority:** HIGH - Visual engagement metrics are key +**Accessibility:** Always provide text alternatives for charts diff --git a/.github/agents/error-handling-logging.agent.md b/.github/agents/error-handling-logging.agent.md new file mode 100644 index 0000000..007137a --- /dev/null +++ b/.github/agents/error-handling-logging.agent.md @@ -0,0 +1,770 @@ +--- +name: "Error Handling & Logging Expert" +description: "Implements comprehensive error handling, error boundaries, logging strategies, and user-facing error messages following Interact's patterns" +--- + +# Error Handling & Logging Expert Agent + +You are an expert in React error handling and logging, specializing in the Interact platform's error management strategies. + +## Your Responsibilities + +Implement robust error handling across the platform, including error boundaries, try-catch blocks, API error handling, and user-friendly error messages. + +## Error Handling Architecture + +### Three-Tier Error Handling + +1. **Component Level** - Try-catch blocks in async functions +2. **Error Boundaries** - Catch React errors in component tree +3. **Global Handlers** - Window error and unhandled rejection handlers + +## Error Boundaries + +### Standard Error Boundary Pattern + +```javascript +// src/components/common/ErrorBoundary.jsx +import { Component } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card } from '@/components/ui/card'; + +export class ErrorBoundary extends Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null, errorInfo: null }; + } + + static getDerivedStateFromError(error) { + return { hasError: true }; + } + + componentDidCatch(error, errorInfo) { + // Log error to console + console.error('Error caught by boundary:', error, errorInfo); + + // Store error details + this.setState({ + error, + errorInfo, + }); + + // Send to error tracking service (Sentry, LogRocket, etc.) + this.logErrorToService(error, errorInfo); + } + + logErrorToService = (error, errorInfo) => { + // Log to external service + try { + // Example: Sentry + if (window.Sentry) { + window.Sentry.captureException(error, { + contexts: { + react: { + componentStack: errorInfo.componentStack, + }, + }, + }); + } + + // Log to Base44 backend + fetch('/api/log-error', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + error: error.toString(), + stack: error.stack, + componentStack: errorInfo.componentStack, + timestamp: new Date().toISOString(), + userAgent: navigator.userAgent, + url: window.location.href, + }), + }); + } catch (loggingError) { + console.error('Failed to log error:', loggingError); + } + }; + + handleReset = () => { + this.setState({ + hasError: false, + error: null, + errorInfo: null, + }); + }; + + render() { + if (this.state.hasError) { + return ( + +

Something went wrong

+

+ We're sorry for the inconvenience. The error has been logged and we'll look into it. +

+ + {process.env.NODE_ENV === 'development' && ( +
+ + Error Details (Development Only) + +
+                {this.state.error?.toString()}
+                {'\n\n'}
+                {this.state.errorInfo?.componentStack}
+              
+
+ )} + +
+ + +
+
+ ); + } + + return this.props.children; + } +} +``` + +### Usage in App + +```javascript +// src/App.jsx +import { ErrorBoundary } from '@/components/common/ErrorBoundary'; + +export default function App() { + return ( + + + {/* App routes */} + + + ); +} +``` + +### Page-Level Error Boundaries + +```javascript +// Wrap individual pages for better error isolation + + + + } +/> +``` + +## API Error Handling + +### Base44 Client Error Handling + +```javascript +// src/api/base44Client.js enhancement +import { base44 } from '@base44/sdk'; +import { toast } from 'sonner'; + +const appParams = { + appId: import.meta.env.VITE_BASE44_APP_ID, + serverUrl: import.meta.env.VITE_BASE44_BACKEND_URL, +}; + +export const base44Client = base44(appParams); + +// Add response interceptor for global error handling +export async function handleApiCall(apiFunction) { + try { + const response = await apiFunction(); + return { data: response, error: null }; + } catch (error) { + console.error('API Error:', error); + + // Parse error message + const errorMessage = parseApiError(error); + + // Show user-friendly error + toast.error(errorMessage); + + // Log to error tracking + logError('API_ERROR', error); + + return { data: null, error: errorMessage }; + } +} + +function parseApiError(error) { + // Network errors + if (!navigator.onLine) { + return 'No internet connection. Please check your network.'; + } + + // Base44 SDK errors + if (error.status === 401) { + return 'Your session has expired. Please log in again.'; + } + + if (error.status === 403) { + return 'You don't have permission to perform this action.'; + } + + if (error.status === 404) { + return 'The requested resource was not found.'; + } + + if (error.status === 422) { + // Validation errors + return error.data?.message || 'Invalid data. Please check your input.'; + } + + if (error.status >= 500) { + return 'Server error. Please try again later.'; + } + + // Generic error + return error.message || 'Something went wrong. Please try again.'; +} + +function logError(type, error) { + // Log to console in development + if (process.env.NODE_ENV === 'development') { + console.error(`[${type}]`, error); + } + + // Log to error tracking service + try { + if (window.Sentry) { + window.Sentry.captureException(error, { + tags: { type }, + }); + } + } catch (loggingError) { + console.error('Failed to log error:', loggingError); + } +} +``` + +### TanStack Query Error Handling + +```javascript +// src/hooks/useActivities.js +import { useQuery } from '@tanstack/react-query'; +import { base44Client } from '@/api/base44Client'; +import { toast } from 'sonner'; + +export function useActivities() { + return useQuery({ + queryKey: ['activities'], + queryFn: async () => { + const response = await base44Client.entities.Activity.list(); + return response.data; + }, + onError: (error) => { + console.error('Failed to fetch activities:', error); + toast.error('Failed to load activities. Please try again.'); + }, + retry: (failureCount, error) => { + // Don't retry on 4xx errors + if (error.status >= 400 && error.status < 500) { + return false; + } + // Retry up to 3 times for other errors + return failureCount < 3; + }, + retryDelay: (attemptIndex) => { + // Exponential backoff + return Math.min(1000 * 2 ** attemptIndex, 30000); + }, + }); +} +``` + +### Mutation Error Handling + +```javascript +// src/hooks/useCreateActivity.js +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { base44Client } from '@/api/base44Client'; +import { toast } from 'sonner'; + +export function useCreateActivity() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (activityData) => { + return await base44Client.entities.Activity.create(activityData); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['activities'] }); + toast.success('Activity created successfully'); + }, + onError: (error, variables) => { + console.error('Failed to create activity:', error); + + // Handle specific errors + if (error.status === 422) { + // Validation error + const validationErrors = error.data?.errors; + if (validationErrors) { + // Show first validation error + const firstError = Object.values(validationErrors)[0]; + toast.error(firstError); + } else { + toast.error('Please check your input and try again'); + } + } else { + toast.error('Failed to create activity. Please try again.'); + } + + // Log error for debugging + console.error('Activity creation failed:', { + error, + data: variables, + }); + }, + }); +} +``` + +## Component Error Handling + +### Async Function Error Handling + +```javascript +// Standard pattern for async operations in components +function ActivityCard({ activityId }) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const handleDelete = async () => { + setLoading(true); + setError(null); + + try { + await base44Client.entities.Activity.delete(activityId); + toast.success('Activity deleted'); + } catch (err) { + console.error('Delete failed:', err); + setError('Failed to delete activity'); + toast.error('Failed to delete activity'); + } finally { + setLoading(false); + } + }; + + return ( + + {error && ( +
+ {error} +
+ )} + + +
+ ); +} +``` + +### Form Submission Error Handling + +```javascript +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { toast } from 'sonner'; + +const schema = z.object({ + name: z.string().min(1, 'Name is required'), + description: z.string().min(10, 'Description must be at least 10 characters'), +}); + +function CreateActivityForm() { + const { register, handleSubmit, setError, formState: { errors, isSubmitting } } = useForm({ + resolver: zodResolver(schema), + }); + + const onSubmit = async (data) => { + try { + await base44Client.entities.Activity.create(data); + toast.success('Activity created'); + } catch (error) { + console.error('Form submission error:', error); + + // Handle field-specific errors + if (error.status === 422 && error.data?.errors) { + // Set form errors from API response + Object.entries(error.data.errors).forEach(([field, message]) => { + setError(field, { type: 'manual', message }); + }); + } else { + // General error + toast.error('Failed to create activity'); + } + } + }; + + return ( +
+
+ + {errors.name && ( + {errors.name.message} + )} +
+ +
+