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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"firebase": "^10.13.0",
"firebase-admin": "^12.3.1",
"highlight.js": "^11.10.0",
"katex": "^0.16.11",
"katex": "^0.16.47",
"lucide-react": "^0.525.0",
"marked": "^18.0.5",
"next": "^14.2.5",
Expand Down
Binary file added public/team/lermonade.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/team/stealth.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/team/venkata.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions src/app/admin/subject/[slug]/[unit]/frq/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use client";

import FRQEditorRenderer from "@/components/frq/editorRenderer";
import { db } from "@/lib/firebase";
import { doc, getDoc } from "firebase/firestore";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";

const Page = () => {
const pathname = usePathname() ?? "";

const pathParts = pathname.split("/").slice(-4);
const subject = pathParts[0] ?? "";
const unitId = pathParts[1] ?? "";
const frqId = pathParts[3] ?? "";

const [frqFound, setFrqFound] = useState<boolean | null>(null);

useEffect(() => {
if (!subject || !unitId || !frqId) {
setFrqFound(false);
return;
}

(async () => {
const docRef = doc(
db,
"subjects",
subject,
"units",
unitId,
"frqs",
frqId,
);

const docSnap = await getDoc(docRef);
setFrqFound(docSnap.exists());
})().catch((error) => {
console.error("Error fetching FRQ:", error);
setFrqFound(false);
});
}, [subject, unitId, frqId]);

if (frqFound === null) {
return <div>Loading...</div>;
}

return <FRQEditorRenderer frqFound={frqFound} />;
};

export default Page;
4 changes: 3 additions & 1 deletion src/app/admin/subject/[slug]/_components/chapterContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface ChapterComponentProps {
setChapterVisibility: (chapterId: string, isPublic: boolean) => void;
moveChapterUp: (chapterId: string) => void;
moveChapterDown: (chapterId: string) => void;
hasUnit0?: boolean;
}

/**
Expand All @@ -36,6 +37,7 @@ function ChapterComponent({
setChapterVisibility,
moveChapterUp,
moveChapterDown,
hasUnit0,
}: ChapterComponentProps) {
const [editing, setEditing] = useState<boolean>(false);
const [localTitle, setLocalTitle] = useState<string>(chapter.title);
Expand Down Expand Up @@ -81,7 +83,7 @@ function ChapterComponent({

<Link
className="whitespace-nowrap rounded-full border border-input bg-background px-4 py-2 text-sm font-medium ring-offset-background transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
href={`/admin/subject/${subjectSlug}/${unitId}/chapter/${chapter.id}?subject=${subjectTitle}&unitIndex=${unitIndex + 1}&chapter=${chapter.title}`}
href={`/admin/subject/${subjectSlug}/${unitId}/chapter/${chapter.id}?subject=${subjectTitle}&unitIndex=${hasUnit0 ? unitIndex : unitIndex + 1}&chapter=${chapter.title}`}
>
Edit Chapter {index + 1}
</Link>
Expand Down
3 changes: 3 additions & 0 deletions src/app/admin/subject/[slug]/_components/unit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface UnitComponentProps {
onMoveDown: (index: number) => void;
subjectSlug: string;
subjectTitle: string;
hasUnit0?: boolean;
}

/**
Expand All @@ -50,6 +51,7 @@ function UnitComponent({
onMoveDown,
subjectSlug,
subjectTitle,
hasUnit0,
}: UnitComponentProps) {
const [expanded, setExpanded] = useState<boolean>(false);

Expand Down Expand Up @@ -369,6 +371,7 @@ function UnitComponent({
setChapterVisibility={setChapterVisibility}
moveChapterUp={moveChapterUp}
moveChapterDown={moveChapterDown}
hasUnit0={hasUnit0}
/>
))}
</div>
Expand Down
43 changes: 31 additions & 12 deletions src/app/admin/subject/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default function Page({ params }: { params: { slug: string } }) {
const { user, error, setError, setLoading } = useUser();
const [subjectTitle, setSubjectTitle] = useState<string>("");
const [units, setUnits] = useState<Unit[]>([]);
const [hasUnit0, setHasUnit0] = useState<boolean>(false);
const [resetting, setResetting] = useState<boolean>(false);

const [subjectLoading, setSubjectLoading] = useState<boolean>(true);
Expand All @@ -88,6 +89,7 @@ export default function Page({ params }: { params: { slug: string } }) {
const fetched = docSnap.data() as Subject;
setSubjectTitle(fetched.title);
setUnits(fetched.units || []);
setHasUnit0(fetched.hasUnit0 ?? false);
} else {
// Subject not found -> create new with default template
// Also try to fill in subject title from `apClasses` if found
Expand Down Expand Up @@ -259,6 +261,7 @@ export default function Page({ params }: { params: { slug: string } }) {
const subjectToSave: Subject = {
title: subjectTitle,
units: units,
hasUnit0: hasUnit0,
};

try {
Expand Down Expand Up @@ -463,21 +466,37 @@ export default function Page({ params }: { params: { slug: string } }) {
{subjectTitle || "Untitled Subject"}
</h1>

{/* hasUnit0 Toggle */}
<label className="mb-4 flex items-center gap-2 text-sm font-medium">
<input
id="has-unit0"
type="checkbox"
checked={hasUnit0}
onChange={(e) => {
setHasUnit0(e.target.checked);
setUnsavedChanges(true);
}}
className="h-4 w-4"
/>
This subject has a Unit 0 (foundational unit numbered starting from 0)
</label>

{/* Render each Unit */}
<div className="my-4 space-y-4">
{units.map((unit, index) => (
<UnitComponent
key={unit.id}
unit={unit}
index={index}
subjectTitle={subjectTitle}
onChange={handleUnitChange}
onDelete={handleDeleteUnit}
onMoveUp={handleMoveUnitUp}
onMoveDown={handleMoveUnitDown}
subjectSlug={params.slug}
/>
))}
<UnitComponent
key={unit.id}
unit={unit}
index={index}
subjectTitle={subjectTitle}
onChange={handleUnitChange}
onDelete={handleDeleteUnit}
onMoveUp={handleMoveUnitUp}
onMoveDown={handleMoveUnitDown}
subjectSlug={params.slug}
hasUnit0={hasUnit0}
/>
))}
</div>

{/* Add new Unit */}
Expand Down
3 changes: 2 additions & 1 deletion src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
});

subject.units.forEach((unit, unitIndex) => {
const unitSegment = `unit-${unitIndex + 1}-${unit.id}`;
const uNum = subject.hasUnit0 ? unitIndex : unitIndex + 1;
const unitSegment = `unit-${uNum}-${unit.id}`;
for (const chapter of unit.chapters ?? []) {
// Only list publicly readable chapters — gated content can't be
// crawled or cited anyway, and listing it invites thin-page penalties.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ const ChapterClient = ({

useEffect(() => {
if (subject && content) {
const unitIndex = Number(params.unit.split("-")[1]) - 1;
const unitNum = Number(params.unit.split("-")[1]);
const unitIndex = subject.hasUnit0 ? unitNum : unitNum - 1;
const chapterIndex = subject.units[unitIndex]!.chapters.findIndex(
(ch) => ch.id === params.id,
);
const chapter = subject.units[unitIndex]!.chapters[chapterIndex];
const displayUnit = subject.hasUnit0 ? unitIndex : unitIndex + 1;

document.title = `FiveHive - ${subject.title} ${unitIndex + 1}.${chapterIndex + 1} - ${chapter?.title}`;
document.title = `FiveHive - ${subject.title} ${displayUnit}.${chapterIndex + 1} - ${chapter?.title}`;
}
}, [subject, content, params.unit, params.id]);

Expand Down Expand Up @@ -64,7 +66,8 @@ const ChapterClient = ({
}

if (subject && content) {
const unitIndex = Number(params.unit.split("-")[1]) - 1;
const unitNum = Number(params.unit.split("-")[1]);
const unitIndex = subject.hasUnit0 ? unitNum : unitNum - 1;
const chapterIndex = subject.units[unitIndex]!.chapters.findIndex(
(ch) => ch.id === params.id,
);
Expand All @@ -85,6 +88,7 @@ const ChapterClient = ({
chapterTitle={chapter.title}
chapterId={params.id}
author={content.author}
hasUnit0={subject.hasUnit0}
>
<Renderer content={content.data} />
</ChapterScaffold>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function ChapterScaffold({
chapterId,
author,
children,
hasUnit0,
}: {
subjectTitle: string;
units: Unit[];
Expand All @@ -34,6 +35,7 @@ export default function ChapterScaffold({
chapterId: string;
author: string;
children: React.ReactNode;
hasUnit0?: boolean;
}) {
return (
<div className="relative flex grow flex-col">
Expand All @@ -46,7 +48,7 @@ export default function ChapterScaffold({
/>

<h1 className="my-2 text-balance text-center text-5xl font-extrabold">
{unitIndex + 1}.{chapterIndex + 1} - {chapterTitle}
{hasUnit0 ? unitIndex : unitIndex + 1}.{chapterIndex + 1} - {chapterTitle}
</h1>
<p>{author}</p>
<div className="my-4">
Expand All @@ -63,12 +65,14 @@ export default function ChapterScaffold({
units={units}
unitIndex={unitIndex}
chapterIndex={chapterIndex}
hasUnit0={hasUnit0}
/>
<NextArticle
subjectTitle={subjectTitle}
units={units}
unitIndex={unitIndex}
chapterIndex={chapterIndex}
hasUnit0={hasUnit0}
/>
</div>
</div>
Expand All @@ -83,11 +87,13 @@ function PreviousArticle({
units,
unitIndex,
chapterIndex,
hasUnit0,
}: {
subjectTitle: string;
units: Unit[];
unitIndex: number;
chapterIndex: number;
hasUnit0?: boolean;
}) {
let unit = units[unitIndex];
let newUnitIndex = unitIndex;
Expand All @@ -106,10 +112,11 @@ function PreviousArticle({
if (!unit) return null;

const subjectSlug = formatSlug(subjectTitle.replace(/AP /g, ""));
const uNum = hasUnit0 ? newUnitIndex : newUnitIndex + 1;

return (
<Link
href={`/subject/${subjectSlug}/unit-${newUnitIndex + 1}-${unit.id}/chapter/${unit.chapters[newChapterIndex]?.id}/${formatSlug(unit.chapters[newChapterIndex]?.title ?? "")}`}
href={`/subject/${subjectSlug}/unit-${uNum}-${unit.id}/chapter/${unit.chapters[newChapterIndex]?.id}/${formatSlug(unit.chapters[newChapterIndex]?.title ?? "")}`}
className={cn(buttonVariants({ variant: "outline" }), "gap-2")}
>
<ArrowLeft />
Expand All @@ -123,11 +130,13 @@ function NextArticle({
units,
unitIndex,
chapterIndex,
hasUnit0,
}: {
subjectTitle: string;
units: Unit[];
unitIndex: number;
chapterIndex: number;
hasUnit0?: boolean;
}) {
let unit = units[unitIndex];
let newUnitIndex = unitIndex;
Expand All @@ -147,10 +156,11 @@ function NextArticle({
if (!unit) return null;

const subjectSlug = formatSlug(subjectTitle.replace(/AP /g, ""));
const uNum = hasUnit0 ? newUnitIndex : newUnitIndex + 1;

return (
<Link
href={`/subject/${subjectSlug}/unit-${newUnitIndex + 1}-${unit.id}/chapter/${unit.chapters[newChapterIndex]?.id}/${formatSlug(unit.chapters[newChapterIndex]?.title ?? "")}`}
href={`/subject/${subjectSlug}/unit-${uNum}-${unit.id}/chapter/${unit.chapters[newChapterIndex]?.id}/${formatSlug(unit.chapters[newChapterIndex]?.title ?? "")}`}
className={cn(buttonVariants({ variant: "outline" }), "ml-auto gap-2")}
>
Next Chapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type PageParams = { slug: string; unit: string; id: string; title: string };
*/
export default async function Page({ params }: { params: PageParams }) {
const subject = await getSubject(params.slug);
const unitIndex = Number(params.unit.split("-")[1]) - 1;
const unitNum = Number(params.unit.split("-")[1]);
const unitIndex = subject?.hasUnit0 ? unitNum : unitNum - 1;
const unit = subject?.units[unitIndex];
const chapterIndex =
unit?.chapters.findIndex((ch) => ch.id === params.id) ?? -1;
Expand Down Expand Up @@ -49,6 +50,7 @@ export default async function Page({ params }: { params: PageParams }) {
chapterTitle={chapter.title}
chapterId={params.id}
author={content.author}
hasUnit0={subject.hasUnit0}
>
<ChapterArticle data={content.data} />
</ChapterScaffold>
Expand Down
1 change: 1 addition & 0 deletions src/app/subject/[slug]/(sidebar)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const Page = ({ params }: { params: { slug: string } }) => {
key={unitIndex}
pathname={pathname}
preview={user?.access === "member" || user?.access === "admin"}
hasUnit0={subject.hasUnit0}
/>
))}
</Accordion>
Expand Down
17 changes: 17 additions & 0 deletions src/components/frq/editorRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface FRQEditorRendererProps {
frqFound: boolean;
}

const FRQEditorRenderer = ({
frqFound,
}: FRQEditorRendererProps) => {
return (
<div>
{frqFound
? "FRQ loaded successfully."
: "Failed to load FRQ."}
</div>
);
};

export default FRQEditorRenderer;
Loading
Loading