Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/0xchriswilder/journey/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The bootcamp tracks granular progress across lessons, quizzes, and homework. All progress is persisted to localStorage and displayed in a visual dashboard with charts, badges, and week-by-week breakdowns.
Progress is managed by Zustand with the persist middleware, ensuring data survives page refreshes and browser sessions.

Progress Data Structure

State Interfaces

src/state/bootcampStore.ts
export interface LessonProgress {
  completed: boolean;
  quizScore?: number;
  quizPassed?: boolean;
  timeSpentMinutes: number;
  completedAt?: string;  // ISO 8601 timestamp
}

export interface HomeworkProgress {
  status: HomeworkStatus;  // 'not-started' | 'in-progress' | 'submitted' | 'graded'
  checklist: Record<string, boolean>;  // requirement.id → checked
  submittedAt?: string;
  grade?: number;
}

export interface WeekProgress {
  lessons: Record<string, LessonProgress>;  // lessonId → progress
  homework: HomeworkProgress;
}

// Top-level state
progress: Record<string, WeekProgress>;  // weekId → week progress

Initial State Creation

Progress structure is initialized from the curriculum:
src/state/bootcampStore.ts
function createInitialProgress(): Record<string, WeekProgress> {
  const progress: Record<string, WeekProgress> = {};
  
  curriculum.weeks.forEach((week) => {
    const lessons: Record<string, LessonProgress> = {};
    
    week.lessons.forEach((lesson) => {
      lessons[lesson.id] = {
        completed: false,
        timeSpentMinutes: 0,
      };
    });
    
    progress[week.id] = {
      lessons,
      homework: {
        status: 'not-started',
        checklist: {},
      },
    };
  });
  
  return progress;
}
This function runs once on first load. Subsequent visits load progress from localStorage.

Progress Actions

Complete Lesson

src/state/bootcampStore.ts
completeLesson: (weekId: string, lessonId: string) =>
  set((state) => {
    const newProgress = { ...state.progress };
    if (newProgress[weekId]?.lessons[lessonId]) {
      newProgress[weekId] = {
        ...newProgress[weekId],
        lessons: {
          ...newProgress[weekId].lessons,
          [lessonId]: {
            ...newProgress[weekId].lessons[lessonId],
            completed: true,
            completedAt: new Date().toISOString(),
          },
        },
      };
    }
    return { progress: newProgress };
  }),
Triggered when:
  • User clicks “Complete & Continue” on a lesson
  • User passes a lesson quiz

Set Quiz Score

src/state/bootcampStore.ts
setQuizScore: (weekId: string, lessonId: string, score: number, passed: boolean) =>
  set((state) => {
    const newProgress = { ...state.progress };
    if (newProgress[weekId]?.lessons[lessonId]) {
      newProgress[weekId] = {
        ...newProgress[weekId],
        lessons: {
          ...newProgress[weekId].lessons,
          [lessonId]: {
            ...newProgress[weekId].lessons[lessonId],
            quizScore: score,
            quizPassed: passed,
          },
        },
      };
    }
    return { progress: newProgress };
  }),

Update Homework Status

src/state/bootcampStore.ts
updateHomeworkStatus: (weekId: string, status: HomeworkStatus) =>
  set((state) => {
    const newProgress = { ...state.progress };
    if (newProgress[weekId]) {
      newProgress[weekId] = {
        ...newProgress[weekId],
        homework: {
          ...newProgress[weekId].homework,
          status,
          ...(status === 'submitted' ? { submittedAt: new Date().toISOString() } : {}),
        },
      };
    }
    return { progress: newProgress };
  }),

Toggle Homework Checklist Item

src/state/bootcampStore.ts
toggleHomeworkChecklistItem: (weekId: string, itemId: string) =>
  set((state) => {
    const newProgress = { ...state.progress };
    if (newProgress[weekId]) {
      const currentVal = newProgress[weekId].homework.checklist[itemId] || false;
      newProgress[weekId] = {
        ...newProgress[weekId],
        homework: {
          ...newProgress[weekId].homework,
          checklist: {
            ...newProgress[weekId].homework.checklist,
            [itemId]: !currentVal,
          },
        },
      };
    }
    return { progress: newProgress };
  }),

Add Time Spent

src/state/bootcampStore.ts
addTimeSpent: (weekId: string, lessonId: string, minutes: number) =>
  set((state) => {
    const newProgress = { ...state.progress };
    if (newProgress[weekId]?.lessons[lessonId]) {
      newProgress[weekId] = {
        ...newProgress[weekId],
        lessons: {
          ...newProgress[weekId].lessons,
          [lessonId]: {
            ...newProgress[weekId].lessons[lessonId],
            timeSpentMinutes:
              newProgress[weekId].lessons[lessonId].timeSpentMinutes + minutes,
          },
        },
      };
    }
    return { progress: newProgress };
  }),
Time tracking is not yet implemented in the UI, but the data structure supports it for future features.

Derived Getters

The store provides computed progress metrics:

Week Progress

src/state/bootcampStore.ts
getWeekProgress: (weekId: string) => {
  const state = get();
  const weekData = state.progress[weekId];
  if (!weekData) return { completed: 0, total: 0, percentage: 0 };
  
  const lessons = Object.values(weekData.lessons);
  const completed = lessons.filter((l) => l.completed).length;
  const total = lessons.length;
  
  return {
    completed,
    total,
    percentage: total > 0 ? Math.round((completed / total) * 100) : 0,
  };
},
Usage:
const { completed, total, percentage } = getWeekProgress('week-1');
// { completed: 2, total: 3, percentage: 67 }

Overall Progress

src/state/bootcampStore.ts
getOverallProgress: () => {
  const state = get();
  const allIds = getAllLessonIds();
  let completed = 0;
  
  allIds.forEach(({ weekId, lessonId }) => {
    if (state.progress[weekId]?.lessons[lessonId]?.completed) {
      completed++;
    }
  });
  
  const total = getTotalLessons();
  return {
    completed,
    total,
    percentage: total > 0 ? Math.round((completed / total) * 100) : 0,
  };
},
Usage:
const { completed, total, percentage } = getOverallProgress();
// { completed: 5, total: 12, percentage: 42 }

Lesson/Week Completion Checks

src/state/bootcampStore.ts
isLessonCompleted: (weekId: string, lessonId: string) => {
  const state = get();
  return state.progress[weekId]?.lessons[lessonId]?.completed ?? false;
},

isWeekCompleted: (weekId: string) => {
  const state = get();
  const weekData = state.progress[weekId];
  if (!weekData) return false;
  return Object.values(weekData.lessons).every((l) => l.completed);
},

Progress Page UI

The /progress route displays a comprehensive analytics dashboard:
src/pages/ProgressPage.tsx
import ProgressPage from '@/pages/ProgressPage';

Key Metrics Cards

src/pages/ProgressPage.tsx
const overall = getOverallProgress();

// Calculate total time spent
const totalMinutes = Object.values(progress).reduce((total, weekData) =>
  total + Object.values(weekData.lessons).reduce((wTotal, lesson) =>
    wTotal + lesson.timeSpentMinutes, 0
  ), 0
);

// Count quizzes taken and average score
let quizzesTaken = 0;
let totalQuizScore = 0;
Object.values(progress).forEach((weekData) => {
  Object.values(weekData.lessons).forEach((lesson) => {
    if (lesson.quizScore !== undefined) {
      quizzesTaken++;
      totalQuizScore += lesson.quizScore;
    }
  });
});
const avgQuizScore = quizzesTaken > 0 ? Math.round(totalQuizScore / quizzesTaken) : 0;

// Count submitted homework
const homeworkSubmitted = Object.values(progress).filter(
  (w) => w.homework.status === 'submitted' || w.homework.status === 'graded'
).length;
Rendered as:

Overall Progress

42% (5/12 lessons)

Lessons Done

5 / 12

Avg Quiz Score

85%

HW Submitted

1 / 4

Week-by-Week Breakdown

src/pages/ProgressPage.tsx
{curriculum.weeks.map((week) => {
  const wp = getWeekProgress(week.id);
  const hwStatus = progress[week.id]?.homework.status ?? 'not-started';
  
  return (
    <Card key={week.id}>
      <CardHeader>
        <CardTitle className="flex items-center justify-between">
          <div>
            <Badge variant="outline">Week {week.number}</Badge>
            {week.title}
          </div>
          <div className="flex items-center gap-2">
            {wp.percentage === 100 && <Trophy className="text-yellow-500" />}
            <span>{wp.percentage}%</span>
          </div>
        </CardTitle>
      </CardHeader>
      <CardContent>
        <Progress value={wp.percentage} />
        
        {/* Lesson list */}
        {week.lessons.map((lesson, li) => {
          const lessonDone = isLessonCompleted(week.id, lesson.id);
          const quizScore = progress[week.id]?.lessons[lesson.id]?.quizScore;
          
          return (
            <div className={lessonDone ? 'bg-green-500/5' : 'bg-muted/30'}>
              {lessonDone ? <CheckCircle className="text-green-500" /> : <Circle />}
              <span>{week.number}.{li + 1}</span>
              <span>{lesson.title}</span>
              {quizScore !== undefined && (
                <Badge className={quizScore >= 70 ? 'text-green-600' : 'text-red-600'}>
                  Quiz: {quizScore}%
                </Badge>
              )}
            </div>
          );
        })}
        
        {/* Homework status */}
        <div>
          <FileText className="text-orange-500" />
          <span>Homework: {week.homework.title}</span>
          <Badge className={hwStatus === 'submitted' ? 'text-green-600' : ''}>
            {hwStatus}
          </Badge>
        </div>
      </CardContent>
    </Card>
  );
})}

Completion Celebration

src/pages/ProgressPage.tsx
{overall.percentage === 100 && (
  <Card className="border-yellow-500/30 bg-yellow-500/5">
    <CardContent className="p-8 text-center">
      <Trophy className="h-16 w-16 text-yellow-500 mx-auto" />
      <h3 className="text-2xl font-bold">Bootcamp Complete!</h3>
      <p className="text-muted-foreground">
        You've finished all lessons and homework. Congratulations on becoming an FHEVM developer!
      </p>
    </CardContent>
  </Card>
)}

Persistence

Progress is automatically persisted using Zustand’s persist middleware:
src/state/bootcampStore.ts
export const useBootcampStore = create<BootcampState>()()
  persist(
    (set, get) => ({ /* state and actions */ }),
    {
      name: 'fhevm-bootcamp-progress',
      partialize: (state) => ({
        learningMode: state.learningMode,
        instructorMode: state.instructorMode,
        currentWeekId: state.currentWeekId,
        currentLessonId: state.currentLessonId,
        progress: state.progress,
      }),
    }
  )
);
What gets persisted:
  • learningMode (self-paced vs cohort)
  • instructorMode (on/off)
  • currentWeekId and currentLessonId (navigation state)
  • progress (all lesson/quiz/homework data)
What doesn’t get persisted:
  • UI state (confettiTrigger, celebrationModal)
  • Derived getters (recomputed on demand)
Clearing localStorage will reset all progress. Implement cloud sync for production use.

Access Control (Cohort Mode)

The store includes gating logic for cohort-based learning:
src/state/bootcampStore.ts
canAccessWeek: (weekId: string) => {
  const state = get();
  if (state.learningMode === 'self-paced') return true;
  
  // Cohort mode: must complete previous week
  const weekIndex = curriculum.weeks.findIndex((w) => w.id === weekId);
  if (weekIndex <= 0) return true;
  
  const prevWeekId = curriculum.weeks[weekIndex - 1].id;
  return state.isWeekCompleted(prevWeekId);
},

canAccessLesson: (weekId: string, lessonId: string) => {
  const state = get();
  if (!state.canAccessWeek(weekId)) return false;
  if (state.learningMode === 'self-paced') return true;
  
  // Cohort mode: must complete previous lesson in same week
  const week = curriculum.weeks.find((w) => w.id === weekId);
  if (!week) return false;
  
  const lessonIndex = week.lessons.findIndex((l) => l.id === lessonId);
  if (lessonIndex <= 0) return true;
  
  const prevLessonId = week.lessons[lessonIndex - 1].id;
  return state.isLessonCompleted(weekId, prevLessonId);
},
Usage in UI:
const canAccess = canAccessLesson(weekId, lessonId);

<Button disabled={!canAccess}>
  {canAccess ? 'Start Lesson' : 'Complete Previous Lesson First'}
</Button>
In self-paced mode, all content is unlocked by default. In cohort mode, content unlocks sequentially.

Reset Progress

For testing and development:
src/state/bootcampStore.ts
resetProgress: () =>
  set({
    progress: createInitialProgress(),
    currentWeekId: 'week-1',
    currentLessonId: null,
  }),
Usage:
<Button onClick={() => useBootcampStore.getState().resetProgress()}>
  Reset All Progress
</Button>
This action is irreversible without a backup.

Next Steps

Curriculum

Understand the overall curriculum structure

Instructor Mode

Learn about teaching and facilitation features