Skip to content

Commit

Permalink
fix: update lesson sidebar to use lessonId and fetch course data dyna…
Browse files Browse the repository at this point in the history
…mically (#353)

* fix: update lesson sidebar to use lessonId and fetch course data dynamically

* fix: pass handleNext prop to Quiz component for improved navigation after quiz submission
  • Loading branch information
typeWolffo authored Jan 10, 2025
1 parent ad1424a commit 6023948
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 37 deletions.
1 change: 0 additions & 1 deletion apps/web/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ Sentry.init({
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,

environment: import.meta.env.MODE,
beforeSend(event) {
if (import.meta.env.DEV) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/modules/Courses/Lesson/Lesson.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default function LessonPage() {
isLastLesson={isLast}
/>
</div>
<LessonSidebar course={course} courseId={courseId} />
<LessonSidebar courseId={courseId} lessonId={lessonId} />
</div>
</PageWrapper>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/modules/Courses/Lesson/LessonContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const LessonContent = ({
const Content = () =>
match(lesson.type)
.with("text", () => <Viewer variant="lesson" content={lesson?.description ?? ""} />)
.with("quiz", () => <Quiz lesson={lesson} />)
.with("quiz", () => <Quiz lesson={lesson} handleNext={handleNext} />)
.with("video", () => (
<Video
url={lesson.fileUrl}
Expand Down
45 changes: 30 additions & 15 deletions apps/web/app/modules/Courses/Lesson/LessonSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// TODO: Need to be fixed
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { Link, useLocation } from "@remix-run/react";
import { startCase } from "lodash-es";
import { last, startCase } from "lodash-es";
import { useEffect, useState } from "react";

import { useCourse } from "~/api/queries";
import CourseProgress from "~/components/CourseProgress";
import { Icon } from "~/components/Icon";
import {
Expand All @@ -18,11 +16,9 @@ import { CategoryChip } from "~/components/ui/CategoryChip";
import { cn } from "~/lib/utils";
import { LessonTypesIcons } from "~/modules/Courses/CourseView/lessonTypes";

import type { GetCourseResponse } from "~/api/generated-api";

type LessonSidebarProps = {
course: GetCourseResponse["data"];
courseId: string;
lessonId: string;
};

const progressBadge = {
Expand All @@ -31,7 +27,9 @@ const progressBadge = {
not_started: "NotStartedRounded",
} as const;

export const LessonSidebar = ({ course, courseId }: LessonSidebarProps) => {
export const LessonSidebar = ({ courseId, lessonId }: LessonSidebarProps) => {
const { data: course } = useCourse(courseId);

const { state } = useLocation();
const [activeChapter, setActiveChapter] = useState<string | undefined>(state?.chapterId);

Expand All @@ -43,6 +41,8 @@ export const LessonSidebar = ({ course, courseId }: LessonSidebarProps) => {
setActiveChapter(value);
};

if (!course) return null;

return (
<div className="w-full bg-white h-full rounded-lg">
<div className="flex flex-col gap-y-12">
Expand All @@ -57,7 +57,7 @@ export const LessonSidebar = ({ course, courseId }: LessonSidebarProps) => {
courseLessonCount={course.courseChapterCount ?? 0}
/>
</div>
<div className="flex flex-col px-4 gap-y-4">
<div className="flex flex-col px-4 gap-y-4 pb-4">
<p className="px-4 body-lg-md text-neutral-950">Table of content:</p>
<div className="flex flex-col">
<Accordion
Expand All @@ -69,7 +69,15 @@ export const LessonSidebar = ({ course, courseId }: LessonSidebarProps) => {
{course?.chapters?.map(({ id, title, lessons, chapterProgress }) => {
return (
<AccordionItem value={id} key={id}>
<AccordionTrigger className="flex hover:bg-neutral-50 gap-x-4 px-6 py-4 text-start [&[data-state=open]>div>div>svg]:rotate-180 [&[data-state=open]>div>div>svg]:duration-200 [&[data-state=open]]:border-t [&[data-state=open]]:border-x [&[data-state=open]>div>div>svg]:ease-out data-[state=closed]:rounded-lg data-[state=open]:rounded-t-lg">
<AccordionTrigger
className={cn(
"flex hover:bg-neutral-50 gap-x-4 px-6 py-4 text-start border border-neutral-200 [&[data-state=open]>div>div>svg]:rotate-180 [&[data-state=open]>div>div>svg]:duration-200 [&[data-state=open]>div>div>svg]:ease-out data-[state=closed]:border-t-transparent data-[state=closed]:border-x-transparent data-[state=closed]:rounded-none data-[state=open]:rounded-t-lg",
{
"data-[state=closed]:border-b-0":
last(course?.chapters)?.id === id || activeChapter !== id,
},
)}
>
<Badge
variant="icon"
icon={progressBadge[chapterProgress ?? "not_started"]}
Expand All @@ -78,28 +86,35 @@ export const LessonSidebar = ({ course, courseId }: LessonSidebarProps) => {
<div className="body-base-md text-neutral-950 text-start w-full">{title}</div>
<Icon name="CarretDownLarge" className="size-6 text-primary-700" />
</AccordionTrigger>
<AccordionContent className="flex flex-col divide-y border-b">
<AccordionContent className="flex flex-col border border-t-0 rounded-b-lg">
{lessons?.map(({ id, title, status, type }) => {
return (
<Link
key={id}
to={status === "completed" ? `/course/${courseId}/lesson/${id}` : "#"}
className={cn("flex gap-x-4 py-2 px-6 border-x hover:bg-neutral-50", {
"bg-primary-50 border-l-2 border-l-primary-600":
status === "completed",
className={cn("flex gap-x-4 py-2 px-6 hover:bg-neutral-50", {
"cursor-not-allowed": status === "not_started",
"bg-primary-50 border-l-2 border-l-primary-600 last:rounded-es-lg":
lessonId === id,
})}
>
<Badge
variant="icon"
icon={progressBadge[status ?? "not_started"]}
icon={
progressBadge[
id === lessonId && status === "not_started"
? "in_progress"
: status
]
}
iconClasses="w-6 h-auto shrink-0"
/>{" "}
<div className="flex flex-col w-full">
<p className="body-sm-md text-neutral-950">{title}</p>
<p className="details text-neutral-800">{startCase(type)}</p>
</div>
<Icon
// @ts-expect-error - TODO: do we need the 'file' type?
name={LessonTypesIcons[type]}
className="size-6 text-primary-700"
/>
Expand Down
12 changes: 5 additions & 7 deletions apps/web/app/modules/Courses/Lesson/Quiz.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useNavigate, useParams } from "@remix-run/react";
import { useParams } from "@remix-run/react";
import { FormProvider, useForm } from "react-hook-form";

import { useSubmitQuiz } from "~/api/mutations";
Expand All @@ -12,6 +12,7 @@ import type { TQuestionsForm } from "~/modules/Courses/Lesson/types";

type QuizProps = {
lesson: GetLessonByIdResponse["data"];
handleNext: () => void;
};

function transformData(input: TQuestionsForm) {
Expand Down Expand Up @@ -178,9 +179,8 @@ function transformData(input: TQuestionsForm) {
return result;
}

export const Quiz = ({ lesson }: QuizProps) => {
const { lessonId = "", courseId = "" } = useParams();
const navigate = useNavigate();
export const Quiz = ({ lesson, handleNext }: QuizProps) => {
const { lessonId = "" } = useParams();

const questions = lesson.quizDetails?.questions;

Expand All @@ -190,9 +190,7 @@ export const Quiz = ({ lesson }: QuizProps) => {
});

const submitQuiz = useSubmitQuiz({
handleOnSuccess: () => {
navigate(`/course/${courseId}/lesson/${lesson.nextLessonId}`);
},
handleOnSuccess: handleNext,
});

if (!questions?.length) return null;
Expand Down
32 changes: 31 additions & 1 deletion apps/web/app/modules/Global/Providers.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,42 @@
import { QueryClientProvider } from "@tanstack/react-query";
import { lazy, Suspense, useEffect, useState } from "react";
import { createPortal } from "react-dom";

import { queryClient } from "../../api/queryClient";
import { ThemeProvider } from "../Theme/ThemeProvider";

const ReactQueryDevtools = lazy(() =>
import("@tanstack/react-query-devtools").then(({ ReactQueryDevtools }) => ({
default: ReactQueryDevtools,
})),
);

export function Providers({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false);

/*
* awful hack but this is the only way (I found) to make react-query devtools work in our remix
*/
useEffect(() => {
setMounted(true);
}, []);

return (
<ThemeProvider>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
<QueryClientProvider client={queryClient}>
{children}

{mounted && process.env.NODE_ENV === "development" && (
<>
{createPortal(
<Suspense fallback={null}>
<ReactQueryDevtools initialIsOpen={false} />
</Suspense>,
document.body,
)}
</>
)}
</QueryClientProvider>
</ThemeProvider>
);
}
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@stripe/react-stripe-js": "2.8.0",
"@stripe/stripe-js": "4.6.0",
"@tanstack/react-query": "5.40.1",
"@tanstack/react-query-devtools": "5",
"@tanstack/react-query-devtools": "5.40.1",
"@tanstack/react-table": "8.20.1",
"@tiptap/extension-color": "2.10.3",
"@tiptap/extension-document": "2.10.3",
Expand Down
20 changes: 10 additions & 10 deletions pnpm-lock.yaml

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

0 comments on commit 6023948

Please sign in to comment.