Finishing My React course

Today I used Claude.ai to help me generate a schedule to finish a React course I want to finish by June.

I want to finish 2 lessons per day until I complete the course. So, I had Claude parse the HTML page from the course to pull out the 150+ lesson titles.

Then, I asked it to generate a node.js script to import each of the lessons as a Todoist task. I ran npm i dotenv --save, then added a .env file with my Todoist API key (accessible in Todoist at Settings > Integrations > Developer).

I ran the script, and it worked perfectly on the first try.

Now all I need to do is diligently follow the tasks laid out for each day in Todoist, and I'll be done with the course by the end of May.

require("dotenv").config();
const axios = require("axios");
const fs = require("fs");
const path = require("path");
const csv = require("csv-parser");

// Todoist API configuration
// Replace YOUR_API_TOKEN with your actual Todoist API token
const TODOIST_API_TOKEN = process.env.TODOIST_API_TOKEN;
const TODOIST_API_URL = "https://api.todoist.com/rest/v2";

// Create a new project for the Joy of React course
async function createTodoistProject() {
  try {
    const response = await axios.post(
      `${TODOIST_API_URL}/projects`,
      {
        name: "Joy of React Course",
      },
      {
        headers: {
          Authorization: `Bearer ${TODOIST_API_TOKEN}`,
          "Content-Type": "application/json",
        },
      }
    );

    console.log("Project created successfully:", response.data.name);
    return response.data.id;
  } catch (error) {
    console.error(
      "Error creating project:",
      error.response ? error.response.data : error.message
    );
    throw error;
  }
}

// Parse the schedule data
function parseScheduleData() {
  return new Promise((resolve, reject) => {
    const results = [];

    // Create a readable stream from our CSV data
    const csvData = `Date,Day,Module,Lesson1,Lesson2
2025-03-01,Sat,Module 3: React Hooks,Running on Mount,On Mount Exercises
2025-03-02,Sun,Module 3: React Hooks,Cleanup,Cleanup Exercises
2025-03-03,Mon,Module 3: React Hooks,Stale Values,State-Setter Callback
2025-03-04,Tue,Module 3: React Hooks,Stale Values Exercises,Strict Mode
2025-03-05,Wed,Module 3: React Hooks,Custom Hooks,Custom Hook Exercises
2025-03-06,Thu,Module 3: React Hooks,Data Fetching,Fetching on Event
2025-03-07,Fri,Module 3: React Hooks,Fetching on Mount,Fetch Exercises
2025-03-08,Sat,Module 3: React Hooks,Async Effect Gotcha,Why React Re-Renders
2025-03-09,Sun,Module 3: React Hooks,Pure Components,The useMemo Hook
2025-03-10,Mon,Module 3: React Hooks,The useCallback Hook,Memo Exercises
2025-03-11,Tue,Module 3: React Hooks,Alternatives,Future of Memoization
2025-03-12,Wed,Module 4: Component API Design,Introduction,The Spectrum of Components
2025-03-16,Sun,Module 4: Component API Design,Spectrum Exercises,Component Libraries
2025-03-17,Mon,Module 4: Component API Design,Prop Delegation,Delegating Props Exercises
2025-03-18,Tue,Module 4: Component API Design,Conflicts,Delegating Styles
2025-03-19,Wed,Module 4: Component API Design,Forwarding Refs,ForwardRef Exercises
2025-03-20,Thu,Module 4: Component API Design,Polymorphism,Polymorphism Exercises
2025-03-21,Fri,Module 4: Component API Design,Compound Components,Slots
2025-03-22,Sat,Module 4: Component API Design,Slots Exercises,Context
2025-03-23,Sun,Module 4: Component API Design,The Problem,Syntax
2025-03-24,Mon,Module 4: Component API Design,Syntax Exercises,Provider Components
2025-03-25,Tue,Module 4: Component API Design,Performance,Modals
2025-03-25,Tue,Project: Toast Component,About This Project,Getting Started
2025-03-26,Wed,Module 4: Component API Design,Modal Exercises,Unstyled Component Libraries
2025-03-26,Wed,Project: Toast Component,Hints,Submit Your Project
2025-03-27,Thu,Module 4: Component API Design,Converting Our Modal,Library Exercises
2025-03-27,Thu,Project: Toast Component,Solution,Unguided Practice
2025-03-28,Fri,Module 5: Happy Practices,Introduction,Leveraging Keys
2025-03-29,Sat,Module 5: Happy Practices,Key Exercises,Elements Revisited
2025-03-30,Sun,Module 5: Happy Practices,Deriving State,Deriving Exercises
2025-03-31,Mon,Module 5: Happy Practices,Breaking the Rules,Lifting Content Up
2025-04-01,Tue,Module 5: Happy Practices,Lifting Content Up Exercises,Single Source of Truth
2025-04-02,Wed,Module 5: Happy Practices,SST Exercises,Principle of Least Privilege
2025-04-03,Thu,Module 5: Happy Practices,Least Privilege Exercises,useReducer
2025-04-04,Fri,Module 5: Happy Practices,Switch / Case,useReducer Exercises
2025-04-05,Sat,Module 5: Happy Practices,Immer,Immer 101
2025-04-06,Sun,Module 5: Happy Practices,Immer Exercises,The "useImmer" Hook
2025-04-07,Mon,Module 5: Happy Practices,Portals,Portal Exercises
2025-04-08,Tue,Module 5: Happy Practices,Refs Revisited,Error Boundaries
2025-04-09,Wed,Module 6: Full Stack React,Introduction,Client vs. Server Rendering
2025-04-10,Thu,Module 6: Full Stack React,Hydration,SSR Flavors
2025-04-11,Fri,Module 6: Full Stack React,React Server Components,Intro to Next.js
2025-04-12,Sat,Module 6: Full Stack React,Hello Next!,Initial Exercises
2025-04-13,Sun,Module 6: Full Stack React,Client Components,Client Components Exercises
2025-04-14,Mon,Module 6: Full Stack React,SSR Gotchas,SSR Exercises
2025-04-15,Tue,Module 6: Full Stack React,Routing,Page Transitions
2025-04-16,Wed,Module 6: Full Stack React,Programmatic Routing,Dynamic Segments
2025-04-17,Thu,Module 6: Full Stack React,Routing Exercises,Next's Metadata API
2025-04-18,Fri,Module 6: Full Stack React,Creating Building Deploying,Starting a New Project
2025-04-19,Sat,Module 6: Full Stack React,Import Aliases,Building for Production
2025-04-20,Sun,Module 6: Full Stack React,Deploying,Rendering Strategies
2025-04-21,Mon,Module 6: Full Stack React,Rendering Strategies Exercises,React Cache
2025-04-22,Tue,Module 6: Full Stack React,Suspense,An Exciting New World
2025-04-23,Wed,Module 6: Full Stack React,Graphing It Out,Understanding Suspense
2025-04-24,Thu,Module 6: Full Stack React,Putting It All Together,Using Suspense in Next
2025-04-25,Fri,Module 6: Full Stack React,Suspense Exercises,Lazy Loading
2025-04-26,Sat,Module 6: Full Stack React,Lazy Loading in Next,Baked-in Laziness
2025-04-27,Sun,Module 6: Full Stack React,Dark Mode,Module Review
2025-04-28,Mon,Bonus: Layout Animations,Intro to Framer Motion,Getting Started
2025-04-29,Tue,Bonus: Layout Animations,Basics Exercises,Layout Animations
2025-04-30,Wed,Bonus: Layout Animations,Layout Exercises,Shared Layout
2025-05-01,Thu,Bonus: Layout Animations,Working With Groups,Shared Layout Exercises
2025-05-02,Fri,Bonus: Layout Animations,One Last Exercise,Motion Accessibility
2025-05-03,Sat,Bonus: Layout Animations,Disabling Motion in Next,Troubleshooting
2025-05-04,Sun,Project: MDX Blog,About This Project,Intro to MDX
2025-05-05,Mon,Project: MDX Blog,MDX in Next.js,Getting Started
2025-05-06,Tue,Project: MDX Blog,Submit Your Project,Solution
2025-05-07,Wed,Project: MDX Blog,Suspense Investigations,Navigation Performance
2025-05-08,Thu,Project: MDX Blog,The Journey Continues,Project Review
2025-05-09,Fri,Bonus: Job Hunting Kit,Introduction,The Hiring Funnel
2025-05-10,Sat,Bonus: Job Hunting Kit,Cover Letters,Quantity vs. Quality
2025-05-11,Sun,Bonus: Job Hunting Kit,Portfolios,Building a Portfolio Site
2025-05-12,Mon,Bonus: Job Hunting Kit,Technical Interviews,FizzBuzz
2025-05-13,Tue,Bonus: Job Hunting Kit,Scroll Position,Todo App
2025-05-14,Wed,Bonus: Job Hunting Kit,Project Euler,Starfield Button
2025-05-15,Thu,Bonus: Job Hunting Kit,Multi-Step Signup,Trello Clone
2025-05-16,Fri,Bonus: Job Hunting Kit,Clicking Outside,Multi Checkbox
2025-05-17,Sat,Bonus: Job Hunting Kit,Fetch Race Condition,useEffectSkipMount
2025-05-18,Sun,Bonus: Job Hunting Kit,Retro Questions,Module Review
2025-05-19,Mon,Course Completion,Course Review,Celebration`;

    // Create a readable stream from the CSV string
    const Readable = require("stream").Readable;
    const s = new Readable();
    s.push(csvData);
    s.push(null); // Indicates EOF

    s.pipe(csv())
      .on("data", (data) => results.push(data))
      .on("end", () => {
        resolve(results);
      })
      .on("error", (error) => {
        reject(error);
      });
  });
}

// Create tasks in Todoist for each lesson
async function createTasks(projectId, scheduleData) {
  console.log(
    `Creating ${scheduleData.length * 2} tasks (2 lessons per day)...`
  );

  // Create a counter to track progress
  let tasksCreated = 0;

  // Process tasks in batches to avoid rate limiting
  const batchSize = 10;
  for (let i = 0; i < scheduleData.length; i += batchSize) {
    const batch = scheduleData.slice(i, i + batchSize);

    // Create tasks for each item in the batch
    const promises = batch.flatMap((item) => {
      const dateStr = item.Date; // Format: 2025-03-01

      // Create two tasks - one for each lesson
      return [
        createTask(projectId, `${item.Module} - ${item.Lesson1}`, dateStr),
        createTask(projectId, `${item.Module} - ${item.Lesson2}`, dateStr),
      ];
    });

    // Wait for all tasks in this batch to be created
    await Promise.all(promises);

    // Update progress
    tasksCreated += batch.length * 2;
    console.log(
      `Progress: ${tasksCreated}/${scheduleData.length * 2} tasks created`
    );

    // Add a small delay between batches to avoid rate limiting
    if (i + batchSize < scheduleData.length) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }

  console.log("All tasks created successfully!");
}

// Create a single task in Todoist
async function createTask(projectId, content, dueDate) {
  try {
    const response = await axios.post(
      `${TODOIST_API_URL}/tasks`,
      {
        project_id: projectId,
        content: content,
        due_date: dueDate,
      },
      {
        headers: {
          Authorization: `Bearer ${TODOIST_API_TOKEN}`,
          "Content-Type": "application/json",
        },
      }
    );

    return response.data;
  } catch (error) {
    console.error(
      "Error creating task:",
      error.response ? error.response.data : error.message
    );
    throw error;
  }
}

// Main function to run the script
async function main() {
  try {
    console.log("Starting Joy of React schedule import to Todoist...");

    // Create a new project in Todoist
    const projectId = await createTodoistProject();

    // Parse the schedule data
    const scheduleData = await parseScheduleData();

    // Create tasks for each lesson
    await createTasks(projectId, scheduleData);

    console.log("Import completed successfully!");
  } catch (error) {
    console.error("Error running the script:", error);
  }
}

// Run the script
main();