Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 1 addition & 174 deletions sites/mainweb/app/(portal)/club/bootcamp/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ import {
Clock,
ExternalLink,
CalendarDays,
Check,
} from "lucide-react";
import { LoadingScreen } from "@/components/portal/LoadingScreen";
import { BootcampAddOn } from "@/components/portal/BootcampAddOn";
import { BootcampMaterialsTable } from "@/components/portal/BootcampMaterialsTable";
import { trpc } from "@/lib/trpc";
import {
BOOTCAMP_CURRICULUM,
BOOTCAMP_MEETING_TIME,
BOOTCAMP_ROOM,
BOOTCAMP_START_DATE,
Expand All @@ -35,15 +33,6 @@ function termLabel(term: string) {
return `${season.charAt(0).toUpperCase()}${season.slice(1)} ${year}`;
}

const dateLabel = (date: Date) =>
new Date(date).toLocaleString(undefined, {
weekday: "short",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});

/** Where and when it meets — the thing somebody checks walking to class. */
function MeetingCard() {
return (
Expand Down Expand Up @@ -85,54 +74,6 @@ function WorkspaceLink() {
);
}

/** For an enrolled member when there is genuinely nothing to show yet. */
function NothingScheduledYet({ term }: { term: string }) {
return (
<div className="mt-8 border border-[var(--border-subtle)] bg-[var(--bg-primary)]/60 p-8">
<h2 className="text-xl font-black uppercase italic tracking-tight text-[var(--text-primary)]">
You are in the {term} bootcamp
</h2>
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-[var(--text-muted)]">
Your spot is confirmed — nothing else to do. We are still putting the
weeks together; sessions, slides and notebooks all appear here as they
are set, and you will hear from us before the first meeting.
</p>
</div>
);
}

/** Attended, missed, or not taught yet. Only the clock separates the last two. */
function AttendanceBadge({
attended,
past,
}: {
attended: boolean;
past: boolean;
}) {
if (attended) {
return (
<span className="inline-flex shrink-0 items-center gap-1.5 border border-emerald-400/30 bg-emerald-400/10 px-2.5 py-1 font-mono text-[10px] font-bold uppercase tracking-widest text-emerald-300">
<Check aria-hidden="true" className="h-3 w-3" />
Attended
</span>
);
}

if (past) {
return (
<span className="inline-flex shrink-0 items-center border border-[var(--border-subtle)] px-2.5 py-1 font-mono text-[10px] font-bold uppercase tracking-widest text-[var(--text-subtle)]">
Missed
</span>
);
}

return (
<span className="inline-flex shrink-0 items-center border border-[var(--border-subtle)] px-2.5 py-1 font-mono text-[10px] font-bold uppercase tracking-widest text-[var(--text-subtle)]">
Upcoming
</span>
);
}

/** Members buy the add-on here; non-members go to the flow that sells both. */
function NotEnrolled({ term }: { term: string }) {
const memberStatus = trpc.member.checkStatus.useQuery();
Expand All @@ -144,7 +85,7 @@ function NotEnrolled({ term }: { term: string }) {
You are not in the {termLabel(term)} bootcamp
</h2>
<p className="mt-3 max-w-2xl text-sm text-[var(--text-muted)]">
Twelve weeks of Python and data science, taught in person, with the
Nine weeks of Python and data science, taught in person, with the
notebooks to keep. It runs for one semester, so joining covers this term
{isMember
? ""
Expand All @@ -166,20 +107,6 @@ function NotEnrolled({ term }: { term: string }) {
);
}

type Week = {
week: number;
title: string;
desc: string;
deepnoteUrl?: string;
session?: {
id: string;
eventDate: Date;
location: string | null;
attended: boolean;
past: boolean;
};
};

export default function BootcampPortalPage() {
const { data: session, status } = useSession();
const progress = trpc.bootcamp.myProgress.useQuery(undefined, {
Expand All @@ -195,29 +122,6 @@ export default function BootcampPortalPage() {

const data = progress.data;
const enrolled = !!data?.enrolled;
const sessions = data?.sessions ?? [];
const byWeek = new Map(sessions.map((row) => [row.week, row]));

// Curriculum is the spine, so an unscheduled week still shows.
const extras = sessions.filter(
(row) => !BOOTCAMP_CURRICULUM.some((entry) => entry.week === row.week),
);
const weeks: Week[] = [
...BOOTCAMP_CURRICULUM.map((entry) => ({
week: entry.week,
title: entry.title,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Portal drops syllabus and attendance

High Severity

Enrolled /club/bootcamp dropped the week list that joined BOOTCAMP_CURRICULUM with myProgress sessions, so members no longer see the syllabus or per-week attendance. That page is supposed to show attendance and weeks plus published materials, not the materials table alone.

Fix in Cursor Fix in Web

Triggered by learned rule: Bootcamp syllabus shape and null TBA fields

Reviewed by Cursor Bugbot for commit d09b345. Configure here.

desc: entry.desc,
deepnoteUrl: entry.deepnoteUrl,
session: byWeek.get(entry.week),
})),
...extras.map((row) => ({
week: row.week ?? 0,
title: row.title,
desc: row.description ?? "",
deepnoteUrl: undefined,
session: row,
})),
];

return (
<div className="min-h-screen bg-[var(--bg-primary)] pb-20 text-[var(--text-muted)]">
Expand Down Expand Up @@ -302,83 +206,6 @@ export default function BootcampPortalPage() {
<div className="my-8">
<WorkspaceLink />
</div>

{enrolled && weeks.length === 0 && data ? (
<NothingScheduledYet term={termLabel(data.term)} />
) : (
<section>
<h2 className="mb-4 font-mono text-[10px] font-bold uppercase tracking-widest text-[var(--text-subtle)]">
Syllabus
</h2>

{weeks.length === 0 && (
<p className="border border-accent/30 bg-accent/[0.06] p-5 text-sm leading-relaxed text-[var(--text-muted)]">
Updating soon — the week-by-week syllabus is being written and
will appear here before the first session.
</p>
)}

<ol className="space-y-3">
{weeks.map((entry) => (
<li
key={entry.week}
className="border border-[var(--border-subtle)] bg-[var(--bg-primary)]/60 p-5 transition-ui hover:border-white/20"
>
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1">
<p className="font-mono text-[10px] uppercase tracking-widest text-accent">
Week {String(entry.week).padStart(2, "0")}
</p>
<h3 className="mt-1 text-lg font-bold text-[var(--text-primary)]">
{entry.title}
</h3>
{entry.desc && (
<p className="mt-1 text-sm text-[var(--text-muted)]">
{entry.desc}
</p>
)}

{entry.session && (
<p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[var(--text-subtle)]">
<span className="inline-flex items-center gap-1.5">
<CalendarDays className="h-3.5 w-3.5" />
{dateLabel(entry.session.eventDate)}
</span>
{entry.session.location && (
<span className="inline-flex items-center gap-1.5">
<MapPin className="h-3.5 w-3.5" />
{entry.session.location}
</span>
)}
</p>
)}

{entry.deepnoteUrl && (
<a
href={entry.deepnoteUrl}
target="_blank"
rel="noopener noreferrer"
className="mt-3 inline-flex items-center gap-1.5 text-xs font-bold text-accent hover:underline"
>
<ExternalLink className="h-3.5 w-3.5" />
Notebook
</a>
)}

</div>

{enrolled && entry.session && (
<AttendanceBadge
attended={entry.session.attended}
past={entry.session.past}
/>
)}
</div>
</li>
))}
</ol>
</section>
)}
</main>
</div>
);
Expand Down
12 changes: 6 additions & 6 deletions sites/mainweb/app/bootcamp/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import {
BOOTCAMP_START_DATE,
} from "@/lib/bootcamp-schedule";

// Shared with the portal's bootcamp page, so the syllabus a member sees signed
// in is the one that was advertised.
const curriculum = BOOTCAMP_CURRICULUM;

export default function BootcampPage() {
Expand Down Expand Up @@ -39,7 +37,7 @@ export default function BootcampPage() {
</span>
</h1>
<p className="text-lg text-gray-400 leading-relaxed max-w-2xl mx-auto italic">
Master Python for data science in 12 weeks. From the fundamentals to
Master Python for data science in 9 weeks. From the fundamentals to
machine learning, build the skills you need to succeed.
</p>
{BOOTCAMP_START_DATE && (
Expand Down Expand Up @@ -80,9 +78,11 @@ export default function BootcampPage() {
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-[#00A8A8] transition-colors duration-300">
{item.title}
</h3>
<p className="text-sm text-gray-500 leading-relaxed">
{item.desc}
</p>
{item.desc && (
<p className="text-sm text-gray-500 leading-relaxed">
{item.desc}
</p>
)}
</div>
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions sites/mainweb/lib/bootcamp-schedule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import {
weekEntry,
} from "./bootcamp-schedule";

// A hand-edited constant published on a public page. Most of this is vacuous
// while it is empty — these arm themselves the moment the real weeks are typed
// in, which is the one moment nobody runs a test on purpose.
// A hand-edited constant published on a public page, so the guards are here to
// catch a typo in the edit rather than a bug in the code.

describe("BOOTCAMP_CURRICULUM", () => {
it("numbers each week once", () => {
Expand All @@ -32,8 +31,9 @@ describe("BOOTCAMP_CURRICULUM", () => {

it("gives every week something to render", () => {
for (const entry of BOOTCAMP_CURRICULUM) {
// A week may be a title alone, but a blank one renders as an empty card.
expect(entry.title.trim()).not.toBe("");
expect(entry.desc.trim()).not.toBe("");
if (entry.desc !== undefined) expect(entry.desc.trim()).not.toBe("");
}
});

Expand Down
78 changes: 14 additions & 64 deletions sites/mainweb/lib/bootcamp-schedule.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/**
* The public bootcamp syllabus. The portal's downloadable weekly material is
* edited separately, while the marketing page still needs stable public copy.
* The bootcamp syllabus shown on the public marketing page. The portal lists
* real scheduled workshops from the database instead, so nothing here reaches
* a signed-in member.
*/

export type BootcampWeek = {
week: number;
title: string;
desc: string;
/** Optional: a week may be published as a title alone. */
desc?: string;
/** Overrides the default room for a week that moves. */
room?: string;
/** The notebook for this week, if it has its own link. */
Expand All @@ -26,68 +28,16 @@ export const BOOTCAMP_START_DATE: string | null = "September 22, 2026";
/** The Deepnote workspace every session works out of. */
export const BOOTCAMP_WORKSPACE_URL: string | null = null;

// Restored from f515a3d9. Confirm the titles before the term starts.
export const BOOTCAMP_CURRICULUM: BootcampWeek[] = [
{
week: 1,
title: "Python Basics & Setup",
desc: "Variables, data types, and environment configuration.",
},
{
week: 2,
title: "Control Flow & Structures",
desc: "Loops, conditionals, lists, and dictionaries.",
},
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curriculum weeks missing descriptions

Medium Severity

BOOTCAMP_CURRICULUM weeks now omit desc and the type marks it optional, so public /bootcamp cards render titles only. Each week is required to have both a title and a description.

Additional Locations (2)
Fix in Cursor Fix in Web

Triggered by learned rule: Bootcamp syllabus shape and null TBA fields

Reviewed by Cursor Bugbot for commit d09b345. Configure here.

week: 3,
title: "Functions & Modules",
desc: "Writing reusable code and organizing projects.",
},
{
week: 4,
title: "Object-Oriented Programming",
desc: "Classes, inheritance, and Pythonic design.",
},
{
week: 5,
title: "File Handling & APIs",
desc: "Reading files, writing data, and making web requests.",
},
{
week: 6,
title: "Pandas & NumPy",
desc: "Introduction to fast numerical computing and DataFrames.",
},
{
week: 7,
title: "Data Cleaning",
desc: "Handling missing values, merging, and data transformations.",
},
{
week: 8,
title: "Exploratory Data Analysis",
desc: "Extracting insights and statistical summaries from data.",
},
{
week: 9,
title: "Data Visualization",
desc: "Creating stunning charts using Matplotlib and Seaborn.",
},
{
week: 10,
title: "Intro to Machine Learning",
desc: "Core concepts, train/test splits, and Scikit-Learn.",
},
{
week: 11,
title: "Supervised Learning",
desc: "Linear regression, logistic regression, and decision trees.",
},
{
week: 12,
title: "Capstone Project",
desc: "Build an end-to-end data science portfolio piece.",
},
{ week: 1, title: "Core Library & Data Wrangling" },
{ week: 2, title: "Linear Regression" },
{ week: 3, title: "Logistic Regression" },
{ week: 4, title: "Decision Trees / Ensemble Methods" },
{ week: 5, title: "Clustering" },
{ week: 6, title: "Deep Learning" },
{ week: 7, title: "Creative Projects" },
{ week: 8, title: "Creative Projects" },
{ week: 9, title: "Creative Projects" },
];

/** The curriculum entry a session's week number refers to, if there is one. */
Expand Down
Loading