import { GetActiveSession, GetSetupSprintDemoUsed, SaveSetupSprintDemoUsed, type ActiveSession, } from '@/lib/asyncStorage'; import { DEFAULT_FOCUS_DURATION_MINUTES } from '@/lib/sessionDefaults'; import { getSetupStatus, type SetupStepKey } from '@/lib/setupStatus'; import { finalizeStoredSession } from '@/lib/sessionLifecycle'; import { supabase } from '@/lib/supabase'; import { Session } from '@supabase/supabase-js'; import { Redirect, Stack, router, useFocusEffect, useLocalSearchParams } from 'expo-router'; import { useCallback, useEffect, useState } from 'react'; import { Pressable, ScrollView, Text, View } from 'react-native'; type SetupState = { subjectId: string | null; assignmentId: string | null; taskId: string | null; completedFocusSessions: number; }; const SETUP_STEPS = [ { key: 'subject', title: 'Create your first subject', description: 'Start with one course or study area so the rest of the structure has a clear home.', }, { key: 'assignment', title: 'Create your first assignment', description: 'Add one project, exercise set, or exam-prep block inside that subject.', }, { key: 'task', title: 'Create your first task', description: 'Break the assignment into one concrete thing you can actually sit down and do.', }, { key: 'sprint', title: 'Start your first sprint', description: 'Begin one focused study session so the app immediately turns into action instead of setup.', }, ] as const; export default function SetupScreen() { const { subjectId: subjectIdParam, assignmentId: assignmentIdParam, taskId: taskIdParam, } = useLocalSearchParams<{ subjectId?: string; assignmentId?: string; taskId?: string; }>(); const [session, setSession] = useState(null); const [isAuthLoading, setIsAuthLoading] = useState(true); const [setupState, setSetupState] = useState({ subjectId: subjectIdParam ?? null, assignmentId: assignmentIdParam ?? null, taskId: taskIdParam ?? null, completedFocusSessions: 0, }); const [activeSession, setActiveSession] = useState(null); useEffect(() => { supabase.auth.getSession().then(({ data }) => { setSession(data.session ?? null); setIsAuthLoading(false); }); const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => { setSession(newSession); setIsAuthLoading(false); }); return () => sub.subscription.unsubscribe(); }, []); const loadSetupState = useCallback(async () => { if (!session?.user.id) { setSetupState({ subjectId: null, assignmentId: null, taskId: null, completedFocusSessions: 0, }); setActiveSession(null); return; } const [storedActiveSession, status] = await Promise.all([ GetActiveSession(), getSetupStatus(session.user.id), ]); if (storedActiveSession && storedActiveSession.endTime <= Date.now()) { await finalizeStoredSession('expired', storedActiveSession); setActiveSession(null); } else { setActiveSession(storedActiveSession); } setSetupState({ subjectId: subjectIdParam ?? status.subjectId, assignmentId: assignmentIdParam ?? status.assignmentId, taskId: taskIdParam ?? status.taskId, completedFocusSessions: status.completedFocusSessions, }); }, [assignmentIdParam, session?.user.id, subjectIdParam, taskIdParam]); useFocusEffect( useCallback(() => { void loadSetupState(); }, [loadSetupState]) ); const currentStep: SetupStepKey = (() => { if (!setupState.subjectId) { return 'subject'; } if (!setupState.assignmentId) { return 'assignment'; } if (!setupState.taskId) { return 'task'; } return 'sprint'; })(); const isSetupComplete = setupState.taskId !== null && setupState.completedFocusSessions > 0; const handlePrimaryAction = useCallback(async () => { if (isSetupComplete) { router.replace('/'); return; } if (currentStep === 'subject') { router.push({ pathname: '/subject/upsertSubject', params: { flow: 'setup' }, }); return; } if (currentStep === 'assignment' && setupState.subjectId) { router.push({ pathname: '/assignment/upsertAssignment', params: { sId: setupState.subjectId, flow: 'setup', }, }); return; } if (currentStep === 'task' && setupState.assignmentId) { router.push({ pathname: '/task/upsertTask', params: { aId: setupState.assignmentId, flow: 'setup', }, }); return; } if (!setupState.taskId) { return; } const freshActiveSession = await GetActiveSession(); if (freshActiveSession && freshActiveSession.endTime > Date.now()) { router.push({ pathname: '/task/timer', params: freshActiveSession.taskId ? { tId: freshActiveSession.taskId } : { sessionType: freshActiveSession.sessionType, durationMinutes: String( Math.max(1, Math.round(freshActiveSession.durationSeconds / 60)) ), }, }); return; } if (freshActiveSession) { await finalizeStoredSession('expired', freshActiveSession); setActiveSession(null); } const shouldUseDemoSprint = session?.user.id ? !(await GetSetupSprintDemoUsed(session.user.id)) : false; if (shouldUseDemoSprint && session?.user.id) { await SaveSetupSprintDemoUsed(session.user.id); } router.push({ pathname: '/task/timer', params: shouldUseDemoSprint ? { tId: setupState.taskId, durationSeconds: '5', onboardingDemo: 'true', } : { tId: setupState.taskId, durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES), }, }); }, [currentStep, isSetupComplete, session?.user.id, setupState]); const primaryLabel = isSetupComplete ? 'Go to dashboard' : currentStep === 'subject' ? 'Create first subject' : currentStep === 'assignment' ? 'Create first assignment' : currentStep === 'task' ? 'Create first task' : activeSession ? 'Open active sprint' : 'Start first sprint'; if (isAuthLoading) { return null; } if (!session) { return ; } return ( First-time setup Build one simple study path You only need one subject, one assignment, one task, and one sprint to make the app useful. {SETUP_STEPS.map((step, index) => { const isDone = step.key === 'subject' ? Boolean(setupState.subjectId) : step.key === 'assignment' ? Boolean(setupState.assignmentId) : step.key === 'task' ? Boolean(setupState.taskId) : isSetupComplete; const isCurrent = !isDone && currentStep === step.key; return ( {isDone ? '✓' : index + 1} {step.title} {step.description} ); })} {isSetupComplete ? 'You have already completed at least one focus sprint.' : currentStep === 'sprint' ? 'The structure is ready. The next step is to actually begin a sprint.' : 'Follow the next step below. The rest of the app will make more sense once that path exists.'} {primaryLabel} ); }