/* @component-map * App β€” Main container, tab navigation [app.jsx] * ChallengeTracker β€” Daily challenge grid with check-off [components/ChallengeTracker.jsx] * Leaderboard β€” Group leaderboard of completion percentages [components/Leaderboard.jsx] * ChallengeSetup β€” Admin view to define daily challenges [components/ChallengeSetup.jsx] * @end-component-map */ // DEPLOY_CONFIG: {"triggers": [{"name": "30_day_completion_certificate", "on": "collection.update", "collection": "users", "actions": [{"type": "event", "event_type": "group.notify", "payload": {"message": "A user has completed all 30 days of the challenge."}}, {"type": "email", "to": "{{user.email}}", "subject": "Congratulations on completing all 30 days!", "body": "Attached is your PDF certificate of completion."}]}]} import { useState, useEffect, useMemo, useRef } from 'react'; import { useAuth, usePresence, useCollection, useAI, sendEmail, generatePDF } from '@deplixo/sdk'; import { ChallengeTracker } from './components/ChallengeTracker.jsx'; import { Leaderboard } from './components/Leaderboard.jsx'; import { ChallengeSetup } from './components/ChallengeSetup.jsx'; function App() { const [tab, setTab] = useState('challenge'); const [certificateStatus, setCertificateStatus] = useState('idle'); const [certificateMessage, setCertificateMessage] = useState(''); const certificateRef = useRef(null); const { user, loading, logout } = useAuth(); const { items: challengeItems, add: addChallenge, update: updateChallenge, loading: challengesLoading } = useCollection('daily_challenges', { personal: false }); const { items: settingsItems, add: addSetting, update: updateSetting, loading: settingsLoading } = useCollection('challenge_settings', { personal: false }); const { items: completionItems } = useCollection('challenge_completions', { personal: true }); const { generate: generateAI } = useAI(); const selectedLevel = settingsItems?.[0]?.selectedLevel || 'intermediate'; const challengePack = challengeItems || []; const completions = completionItems || []; const completedCount = completions.filter((item) => item.completed).length; const completionPercent = Math.round((completedCount / 30) * 100); const isComplete = completedCount >= 30; const hasChallenges = challengePack.length > 0; useEffect(() => { if (!user) return; if (!settingsItems || settingsItems.length === 0) { addSetting({ selectedLevel: 'intermediate', updatedBy: user.id, updatedByName: user.name, updatedAt: Date.now(), }); } }, [user, settingsItems, addSetting]); useEffect(() => { if (!user || !hasChallenges) return; const needsLevelSync = challengePack.some((item) => !item.fitnessLevel || !item.variation || !item.aiGenerated); if (!needsLevelSync) return; challengePack.forEach((item) => { if (!item.fitnessLevel || !item.variation || !item.aiGenerated) { updateChallenge(item.id, { fitnessLevel: item.fitnessLevel || selectedLevel, variation: item.variation || 'intermediate', aiGenerated: true, }); } }); }, [user, hasChallenges, challengePack, selectedLevel, updateChallenge]); const ensureDailyChallenges = async (level) => { if (hasChallenges) return; const prompt = `Create 30 daily fitness challenge entries for a 30-day group challenge. Return a concise JSON array. Each item must include day (1-30), title, emoji, category, and three personalized variations for beginner, intermediate, and advanced fitness levels. Make the challenges safe, motivating, and varied. Focus on bodyweight, mobility, core, cardio, strength, and recovery.`; const result = await generateAI(prompt); let parsed = []; try { const jsonMatch = String(result || '').match(/\[[\s\S]*\]/); parsed = JSON.parse(jsonMatch ? jsonMatch[0] : result); } catch (e) { parsed = []; } const fallback = Array.from({ length: 30 }, (_, index) => ({ day: index + 1, title: `Daily challenge ${index + 1}`, emoji: 'πŸ”₯', category: 'core', beginner: '10 min easy movement', intermediate: '15 min mixed workout', advanced: '25 min performance workout', })); const normalized = (Array.isArray(parsed) && parsed.length ? parsed : fallback).slice(0, 30).map((item, index) => ({ day: item.day || index + 1, title: item.title || `Daily challenge ${index + 1}`, emoji: item.emoji || 'πŸ”₯', category: item.category || 'core', fitnessLevel: level, beginner: item.beginner || item.variations?.beginner || '10 min easy movement', intermediate: item.intermediate || item.variations?.intermediate || '15 min mixed workout', advanced: item.advanced || item.variations?.advanced || '25 min performance workout', variation: level, aiGenerated: true, updatedAt: Date.now(), })); for (const challenge of normalized) { await addChallenge(challenge); } }; useEffect(() => { if (!user || challengesLoading || settingsLoading) return; if (!hasChallenges) { ensureDailyChallenges(selectedLevel); } }, [user, challengesLoading, settingsLoading, hasChallenges, selectedLevel]); const buildCertificateHTML = () => `
πŸ…
Certificate of Completion

30-Day Challenge

This certifies that ${user?.name || 'Participant'} has successfully completed all 30 days of the fitness challenge.

Level completed: ${selectedLevel}
Participant
Date: ${new Date().toLocaleDateString()}
`; const generateCertificate = async () => { if (!certificateRef.current || !isComplete) return null; const filename = `30-day-challenge-certificate-${(user?.name || 'participant').toLowerCase().replace(/[^a-z0-9]+/g, '-')}.pdf`; await generatePDF(certificateRef.current, { filename, margin: 10 }); return filename; }; const handleCertificateDownload = async () => { try { setCertificateStatus('loading'); setCertificateMessage('Generating your certificate...'); await generateCertificate(); setCertificateStatus('success'); setCertificateMessage('Certificate downloaded successfully.'); } catch (error) { setCertificateStatus('error'); setCertificateMessage('Could not generate the certificate right now.'); } }; const handleEmailCertificate = async () => { try { setCertificateStatus('loading'); setCertificateMessage('Generating certificate and preparing email...'); const filename = await generateCertificate(); await sendEmail({ to: user.email, subject: 'Congratulations on completing all 30 days!', html: `

Congratulations, ${user?.name || 'Participant'}!

Attached is your PDF certificate of completion: ${filename || '30-day-challenge-certificate.pdf'}.

`, attachments: certificateRef.current ? [{ name: filename || 'certificate.pdf', node: certificateRef.current }] : undefined, }); setCertificateStatus('success'); setCertificateMessage('Certificate emailed successfully.'); } catch (error) { setCertificateStatus('error'); setCertificateMessage('Could not email the certificate right now.'); } }; // Real-time presence for authenticated users only. // This broadcasts the current participant identity so today’s challenge can show active users. const presencePayload = user ? { id: user.id, name: user.name, email: user.email, avatar: user.avatar, tab, activity: 'challenge', fitnessLevel: selectedLevel, updatedAt: Date.now(), } : null; usePresence(presencePayload); if (loading) { return (

Warming up...

); } if (!user) { return (
πŸ”₯

30-Day Challenge

Sign in with Google to join the group

Your completed days, presence, reactions, and fitness level are tied to your signed-in participant identity.

You'll be redirected to Deplixo login for Google OAuth sign-in.

); } return (
πŸ”₯

30-Day Challenge

Welcome, {user.name} β€’ Level: {selectedLevel}

{tab === 'challenge' && ( <>

Completion Certificate

Download and email your completion certificate once you finish all 30 days.

{isComplete ? 'Complete' : `${completionPercent}% done`}
{certificateMessage ?

{certificateMessage}

: null}
{buildCertificateHTML()}
)} {tab === 'leaderboard' && } {tab === 'setup' && ( { const current = settingsItems?.[0]; if (current) { await updateSetting(current.id, { selectedLevel: nextLevel, updatedBy: user.id, updatedByName: user.name, updatedAt: Date.now() }); } else { await addSetting({ selectedLevel: nextLevel, updatedBy: user.id, updatedByName: user.name, updatedAt: Date.now() }); } challengePack.forEach((item) => { if (item.id) { updateChallenge(item.id, { fitnessLevel: nextLevel, variation: nextLevel, updatedAt: Date.now(), }); } }); }} /> )}
); } // PROGRESS:sc_001:complete:Setting up the fitness challenge shell ReactDOM.createRoot(document.getElementById("root")).render();