Skip to content

Commit 8cf3ec9

Browse files
committed
feat: showing completed badge on completed lessons
1 parent 6e047a3 commit 8cf3ec9

File tree

4 files changed

+242
-59
lines changed

4 files changed

+242
-59
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ yarn-error.log*
4040

4141
# typescript
4242
*.tsbuildinfo
43+
44+
todo.md

src/contexts/AppContext.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useSession } from "next-auth/react";
2+
import { createContext, useState } from "react";
3+
4+
type ContextProps = {
5+
message: string;
6+
setMessage: (message: string) => void;
7+
children: React.ReactElement;
8+
};
9+
10+
const initialState = {
11+
message: "",
12+
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
13+
setMessage: (message: string) => {},
14+
};
15+
16+
export const AppContext = createContext(initialState);
17+
18+
function Context({ children }: ContextProps) {
19+
const [message, setMessage] = useState("");
20+
const { data: sessionData } = useSession();
21+
22+
console.log({ sessionData });
23+
24+
return (
25+
<AppContext.Provider value={{ message, setMessage }}>
26+
{children}
27+
</AppContext.Provider>
28+
);
29+
}
30+
31+
export default Context;

src/pages/getting-started.tsx

Lines changed: 197 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,147 @@ import {
1616
Link,
1717
Divider,
1818
Box,
19+
Badge,
1920
} from "@chakra-ui/react";
2021
import fs from "fs";
2122
import path from "path";
2223
import matter from "gray-matter";
2324
import { CONTENT_PATH } from "@/lib/constants";
24-
import { useEffect, useState } from "react";
25+
import { useMemo, useState } from "react";
26+
import { api } from "@/utils/api";
27+
import { useSession } from "next-auth/react";
2528

26-
interface Lesson {
29+
export interface Lesson {
2730
frontMatter: any;
2831
slug: string;
2932
path: string;
33+
completed?: boolean;
3034
}
31-
interface LessonProps {
35+
export interface Lessons {
3236
lessons: {
3337
frontMatter: any;
3438
slug: string;
3539
}[];
3640
}
3741

38-
const GettingStarted: React.FC<LessonProps> = ({ lessons }) => {
39-
const [formattedLessons, setFormattedLessons] = useState<LessonProps>({
40-
lessons: [],
41-
});
42+
export interface LessonProps {
43+
projects: Project[];
44+
fundamentals: Fundamental[];
45+
}
46+
47+
export interface Fundamental {
48+
path: Path;
49+
frontMatter: FundamentalFrontMatter;
50+
slug: string;
51+
}
52+
53+
export interface FundamentalFrontMatter {
54+
title: string;
55+
description: string;
56+
icons: string[];
57+
authors?: string[];
58+
i18n?: string;
59+
author?: string[] | string;
60+
}
61+
62+
export enum Path {
63+
Fundamentals = "fundamentals",
64+
}
65+
66+
export interface Project {
67+
path: string;
68+
frontMatter: ProjectFrontMatter;
69+
slug: string;
70+
completed: boolean;
71+
}
72+
73+
export interface ProjectFrontMatter {
74+
title: string;
75+
description: string;
76+
icons: string[];
77+
i18n?: string;
78+
author?: string;
79+
}
80+
81+
const GettingStarted: React.FC<Lessons> = ({ lessons }) => {
82+
const [formattedLessons, setFormattedLessons] = useState<LessonProps>();
83+
const [completedQuizzesSlugs, setCompletedQuizzesSlugs] = useState<string[]>(
84+
[]
85+
);
4286

43-
useEffect(() => {
44-
const result: LessonProps = lessons.reduce((acc: any, curr: any) => {
45-
if (!acc[curr.path]) acc[curr.path] = [];
87+
const [fetchNow, setFetchNow] = useState<boolean>(true);
88+
const { data: sessionData } = useSession();
4689

47-
acc[curr.path].push(curr);
48-
return acc;
49-
}, {});
90+
// Requests
91+
// - All
92+
const {
93+
data: completedQuizzesAllData,
94+
isLoading: completedQuizzesAllIsLoading,
95+
// refetch: completedQuizzesAllRefetch,
96+
} = api.completedQuizzes.all.useQuery(
97+
undefined, // no input
98+
{
99+
// Disable request if no session data
100+
enabled: sessionData?.user !== undefined && fetchNow,
101+
onSuccess: () => {
102+
// setNewTodo(""); // reset input form
103+
},
104+
}
105+
);
106+
107+
useMemo(() => {
108+
if (completedQuizzesAllData?.length && fetchNow) {
109+
const result: LessonProps = lessons.reduce((acc: any, curr: any) => {
110+
if (!acc[curr.path]) acc[curr.path] = [];
111+
112+
acc[curr.path].push(curr);
113+
return acc;
114+
}, {});
115+
116+
let completedQuizzes: Project[] = [];
117+
const completedSlugs: string[] = completedQuizzesAllData?.map(
118+
(quiz: any) => quiz.lesson.replace("quiz-lesson-", "") || []
119+
);
120+
completedQuizzes = result?.projects?.map((project: Project) => {
121+
if (completedSlugs.includes(project.slug)) project.completed = true;
122+
else project.completed = false;
123+
return project;
124+
});
125+
// setFormattedLessons({ ...result, projects: completedQuizzes });
126+
127+
console.log({ completedQuizzes });
128+
129+
setFormattedLessons({ ...result, projects: completedQuizzes });
130+
setFetchNow(false);
131+
}
132+
}, [completedQuizzesAllData, fetchNow, lessons]);
50133

51-
setFormattedLessons(result);
52-
}, [lessons]);
134+
// useMemo(() => {
135+
// // get the slug of each completedQuizzesAllData from name
136+
// if (!completedQuizzesAllIsLoading) {
137+
// const completedQuizzesSlugsResult =
138+
// completedQuizzesAllData?.map((quiz: any) =>
139+
// quiz.lesson.replace("quiz-lesson-", "")
140+
// ) || [];
141+
// setCompletedQuizzesSlugs(completedQuizzesSlugsResult);
142+
// }
143+
// }, [completedQuizzesAllData, completedQuizzesAllIsLoading]);
144+
145+
// useMemo(() => {
146+
// if (formattedLessons?.projects.length && completedQuizzesSlugs.length > 0) {
147+
// const completedQuizzes = formattedLessons?.projects?.map(
148+
// (project: Project) => {
149+
// if (completedQuizzesSlugs.includes(project.slug))
150+
// project.completed = true;
151+
// else project.completed = false;
152+
// return project;
153+
// }
154+
// );
155+
// setFormattedLessons({ ...formattedLessons, projects: completedQuizzes });
156+
// }
157+
// }, [completedQuizzesSlugs, formattedLessons]);
158+
159+
// console.log({ f: formattedLessons, completedQuizzesAllData });
53160

54161
return (
55162
<Flex
@@ -96,44 +203,65 @@ const GettingStarted: React.FC<LessonProps> = ({ lessons }) => {
96203
>
97204
Current Lessons
98205
</Heading>
99-
{Object.entries(formattedLessons).map((track: any, idx: number) => {
100-
return (
101-
<UnorderedList
102-
listStyleType="none"
103-
textAlign="center"
104-
as="div"
105-
key={idx}
106-
>
107-
<Heading size="md" color="yellow.300">
108-
{track[0].toUpperCase()}
109-
</Heading>
110-
<>
111-
{track[1].map((lesson: Lesson, idx: number) => (
112-
<ListItem key={idx} my="2" py="2" maxW="40vw" margin="0 auto">
113-
<Link
114-
as={NextLink}
115-
href={`/lessons/${lesson.path}/${lesson.slug}`}
116-
passHref
117-
>
118-
<Button
119-
height="auto"
120-
style={{
121-
whiteSpace: "normal",
122-
wordWrap: "break-word",
123-
padding: "0.5rem",
124-
width: "100%",
125-
fontSize: "xl",
126-
}}
206+
{formattedLessons
207+
? Object.entries(formattedLessons).map((track: any, idx: number) => {
208+
return (
209+
<UnorderedList
210+
listStyleType="none"
211+
textAlign="center"
212+
as="div"
213+
key={idx}
214+
>
215+
<Heading size="md" color="yellow.300">
216+
{track[0].toUpperCase()}
217+
</Heading>
218+
<>
219+
{track[1].map((lesson: Lesson, idx: number) => (
220+
<ListItem
221+
key={idx}
222+
my="2"
223+
py="2"
224+
maxW="40vw"
225+
margin="0 auto"
127226
>
128-
{lesson.frontMatter.title}
129-
</Button>
130-
</Link>
131-
</ListItem>
132-
))}
133-
</>
134-
</UnorderedList>
135-
);
136-
})}
227+
<Link
228+
as={NextLink}
229+
href={`/lessons/${lesson.path}/${lesson.slug}`}
230+
passHref
231+
>
232+
<Button
233+
height="auto"
234+
style={{
235+
whiteSpace: "normal",
236+
wordWrap: "break-word",
237+
padding: "0.5rem",
238+
width: "100%",
239+
fontSize: "xl",
240+
}}
241+
>
242+
{lesson.frontMatter.title}
243+
{lesson &&
244+
lesson.completed &&
245+
lesson.completed === true ? (
246+
<Badge
247+
ml="1"
248+
alignItems={"flex-end"}
249+
colorScheme="green"
250+
position="absolute"
251+
right={3}
252+
>
253+
Completed
254+
</Badge>
255+
) : null}
256+
</Button>
257+
</Link>
258+
</ListItem>
259+
))}
260+
</>
261+
</UnorderedList>
262+
);
263+
})
264+
: null}
137265
<Divider />
138266

139267
<Heading apply="mdx.h3" as="h3" fontSize="2xl" textAlign="center" p={5}>
@@ -205,8 +333,6 @@ const GettingStarted: React.FC<LessonProps> = ({ lessons }) => {
205333
);
206334
};
207335

208-
export default GettingStarted;
209-
210336
export const getStaticProps = () => {
211337
const contentDir = path.join(CONTENT_PATH);
212338
const directories = fs.readdirSync(path.resolve(contentDir));
@@ -221,18 +347,30 @@ export const getStaticProps = () => {
221347
);
222348

223349
const { data: frontMatter } = matter(markdownWithMeta);
224-
lessons.push({
225-
path: folder,
226-
frontMatter,
227-
slug: file.replace(".mdx", ""),
228-
});
350+
if (folder === "fundamentals") {
351+
lessons.push({
352+
path: folder,
353+
frontMatter,
354+
slug: file.replace(".mdx", ""),
355+
});
356+
} else {
357+
lessons.push({
358+
path: folder,
359+
frontMatter,
360+
slug: file.replace(".mdx", ""),
361+
completed: false,
362+
});
363+
}
229364
}
230365
});
231366
}
232367
});
368+
233369
return {
234370
props: {
235371
lessons,
236372
},
237373
};
238374
};
375+
376+
export default GettingStarted;

src/server/api/routers/completedquizzes.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,16 @@ export const completedQuizzesRouter = createTRPCRouter({
2020
},
2121
});
2222
}),
23+
24+
/**
25+
* All completed quizzes belonging to the session user
26+
*/
27+
all: protectedProcedure.query(async ({ ctx }) => {
28+
const completedQuizzes = await ctx.prisma.completedQuizzes.findMany({
29+
where: {
30+
userId: ctx.session.user.id,
31+
},
32+
});
33+
return completedQuizzes;
34+
}),
2335
});

0 commit comments

Comments
 (0)