import { useEffect, useMemo, useState } from 'react'; import { useAuth, useCollection, usePresence, useAI } from '@deplixo/sdk'; import { Timer } from './components/Timer.jsx'; import { SessionLog } from './components/SessionLog.jsx'; import { Stats } from './components/Stats.jsx'; function App() { const { user, loading, logout } = useAuth(); const [activeTab, setActiveTab] = useState('meditate'); const { items: sessionItems, loading: sessionsLoading } = useCollection('sessions', { personal: true }); const { items: statsItems, loading: statsLoading } = useCollection('stats', { personal: true }); const { users: presenceUsers, update: updatePresence } = usePresence( user ? { name: user.name, avatar: user.avatar, status: 'idle' } : { name: 'Guest', status: 'idle' } ); const { generate, loading: aiLoading, error: aiError } = useAI(); const [scheduleAnalysis, setScheduleAnalysis] = useState({ status: 'idle', suggestion: '', error: '' }); const [analysisRequested, setAnalysisRequested] = useState(false); const [shareStatus, setShareStatus] = useState('idle'); const tabs = [ { id: 'meditate', label: '🧘 Meditate', icon: '🧘' }, { id: 'stats', label: 'šŸ“Š Stats', icon: 'šŸ“Š' }, { id: 'history', label: 'šŸ“œ History', icon: 'šŸ“œ' } ]; const sessionCount = Array.isArray(sessionItems) ? sessionItems.length : 0; const sessionsReady = !sessionsLoading && sessionCount >= 10; const sortedSessions = useMemo(() => { return [...(Array.isArray(sessionItems) ? sessionItems : [])].sort((a, b) => { const aTime = new Date(a.createdAt || a.date || a.timestamp || 0).getTime(); const bTime = new Date(b.createdAt || b.date || b.timestamp || 0).getTime(); return aTime - bTime; }); }, [sessionItems]); const weeklyMinutesData = useMemo(() => buildWeeklyMinutesData(sessionItems), [sessionItems]); const streakTrendData = useMemo(() => buildStreakTrendData(sessionItems), [sessionItems]); const currentStreak = streakTrendData.data.length ? streakTrendData.data[streakTrendData.data.length - 1] : 0; const totalMeditations = sessionCount; const shareText = useMemo(() => { return `I’m on a ${currentStreak}-day meditation streak with ${totalMeditations} total meditations in Ember Glow. ✨`; }, [currentStreak, totalMeditations]); const shareLink = useMemo(() => { if (typeof window === 'undefined') return ''; const url = new URL(window.location.href); url.searchParams.set('streak', String(currentStreak)); url.searchParams.set('meditations', String(totalMeditations)); url.searchParams.set('shared', '1'); return url.toString(); }, [currentStreak, totalMeditations]); useEffect(() => { if (typeof window === 'undefined') return; const params = new URLSearchParams(window.location.search); const streak = params.get('streak'); const meditations = params.get('meditations'); if (params.get('shared') === '1' && streak && meditations) { setShareStatus('shared'); } }, []); useEffect(() => { const runAnalysis = async () => { if (!sessionsReady || analysisRequested) return; setAnalysisRequested(true); setScheduleAnalysis({ status: 'loading', suggestion: '', error: '' }); try { const prompts = sortedSessions.slice(-20).map((session, index) => { const createdAt = new Date(session.createdAt || session.date || session.timestamp || Date.now()).toISOString(); const duration = Number(session.duration || session.minutes || session.length || 0); const sound = session.sound || session.soundName || 'silence'; return `${index + 1}. Date: ${createdAt}, Duration: ${duration} min, Sound: ${sound}`; }).join('\n'); const prompt = `You are a meditation coach analyzing a user's session history. The user has completed at least 10 meditation sessions. Review these sessions and produce a practical optimal meditation schedule suggestion. Focus on: preferred time of day patterns, session duration trends, consistency, recovery after missed days, and an ideal weekly plan. Return concise, friendly guidance in 3 short paragraphs and include a bullet list schedule recommendation with specific days/times/durations. Session history:\n${prompts}`; const result = await generate(prompt); const suggestion = typeof result === 'string' ? result : (result?.text || result?.output || JSON.stringify(result)); setScheduleAnalysis({ status: 'success', suggestion, error: '' }); } catch (err) { setScheduleAnalysis({ status: 'error', suggestion: '', error: err?.message || 'Unable to generate your schedule suggestion right now.' }); } }; runAnalysis(); }, [sessionsReady, analysisRequested, sortedSessions, generate]); const activeMeditators = (presenceUsers || []).filter((person) => person && person.status === 'meditating'); const handleShareStreak = async () => { const text = shareText; const url = shareLink; try { if (navigator.share) { await navigator.share({ title: 'My Ember Glow streak', text, url }); setShareStatus('shared'); return; } if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(`${text} ${url}`.trim()); setShareStatus('copied'); return; } setShareStatus('fallback'); } catch (err) { setShareStatus('error'); } }; if (loading) { return (

Ember Glow

Mindful Meditation

Signing in with Google...

); } if (!user) { return (

Ember Glow

Mindful Meditation

Please sign in with Google to access your personal session history and stats.

); } const dataLoading = sessionsLoading || statsLoading; const showAnalysisSection = sessionCount >= 10; return (
{user.avatar ? {user.name} : {user.name?.[0] || 'U'}}
{user.name} Google sign-in

Ember Glow

Mindful Meditation

Meditating now

{activeMeditators.length} active
{activeMeditators.length > 0 ? (
    {activeMeditators.map((person) => (
  • {person.avatar ? {person.name} : {person.name?.[0] || 'U'}}
    {person.name || 'Anonymous'} In session
  • ))}
) : (

No one is meditating right now. Start a session to appear here.

)}

Share your streak

Show your current streak and total meditation count

{currentStreak}
Day streak
{totalMeditations}
Total meditations
{ e.preventDefault(); handleShareStreak(); }}> Copy/share link
{shareStatus === 'copied' &&

Your streak link was copied to the clipboard.

} {shareStatus === 'shared' &&

Share sheet opened with your streak details.

} {shareStatus === 'error' &&

We couldn't share automatically, but your streak is ready to copy.

} {shareStatus === 'shared' && (

This page was opened from a shared streak link.

)}
{showAnalysisSection && (

Optimal schedule suggestion

AI review of your meditation patterns after 10 sessions

{scheduleAnalysis.status === 'loading' || aiLoading ? (
Analyzing your sessions and building a personalized plan...
) : scheduleAnalysis.status === 'error' || aiError ? (

{scheduleAnalysis.error || aiError?.message || 'We could not generate your schedule suggestion right now.'}

) : scheduleAnalysis.status === 'success' ? (

{scheduleAnalysis.suggestion}

) : (
Your personalized schedule suggestion will appear here once enough sessions are available.
)}
)} {dataLoading ? (
Loading your personal meditation data...
) : ( <> {activeTab === 'meditate' && } {activeTab === 'stats' && } {activeTab === 'history' && } )}
); } function buildWeeklyMinutesData(sessionItems) { const sessions = Array.isArray(sessionItems) ? sessionItems : []; const now = new Date(); const mondayOffset = (now.getDay() + 6) % 7; const startOfThisWeek = new Date(now); startOfThisWeek.setDate(now.getDate() - mondayOffset); startOfThisWeek.setHours(0, 0, 0, 0); const weekStarts = Array.from({ length: 6 }, (_, i) => { const d = new Date(startOfThisWeek); d.setDate(d.getDate() - (5 - i) * 7); return d; }); const labels = weekStarts.map((d) => { const end = new Date(d); end.setDate(end.getDate() + 6); return `${d.getMonth() + 1}/${d.getDate()}-${end.getMonth() + 1}/${end.getDate()}`; }); const data = weekStarts.map((weekStart) => { const weekEnd = new Date(weekStart); weekEnd.setDate(weekEnd.getDate() + 7); return sessions.reduce((sum, session) => { const createdAt = new Date(session.createdAt || session.date || session.timestamp || 0); const duration = Number(session.duration || session.minutes || session.length || 0); if (createdAt >= weekStart && createdAt < weekEnd) return sum + duration; return sum; }, 0); }); return { labels, data }; } function buildStreakTrendData(sessionItems) { const sessions = Array.isArray(sessionItems) ? sessionItems : []; const sortedDays = [...new Set( sessions .map((session) => new Date(session.createdAt || session.date || session.timestamp || 0)) .filter((d) => !Number.isNaN(d.getTime())) .map((d) => d.toISOString().slice(0, 10)) )].sort(); const labels = []; const data = []; let streak = 0; let previousDay = null; sortedDays.forEach((day) => { const current = new Date(day + 'T00:00:00'); if (!previousDay) { streak = 1; } else { const diffDays = Math.round((current - previousDay) / (1000 * 60 * 60 * 24)); streak = diffDays === 1 ? streak + 1 : 1; } labels.push(day.slice(5)); data.push(streak); previousDay = current; }); return { labels, data }; } function StatsWithCharts({ sessionItems = [], statsItems = [] }) { return ; } ReactDOM.createRoot(document.getElementById("root")).render();