Skip to content

Commit f2ae0ac

Browse files
committed
feat: db refact and context creation
1 parent 4fd1de3 commit f2ae0ac

File tree

9 files changed

+302
-281
lines changed

9 files changed

+302
-281
lines changed

.env.example

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,9 @@
1717
# You can generate a new secret on the command line with:
1818
# openssl rand -base64 32
1919
# https://next-auth.js.org/configuration/options#secret
20-
# NEXTAUTH_SECRET=""
20+
NEXTAUTH_SECRET=""
2121
NEXTAUTH_URL="http://localhost:3000"
2222

23-
POSTGRES_HOST=127.0.0.1
24-
POSTGRES_PORT=6500
25-
POSTGRES_USER=admin
26-
POSTGRES_PASSWORD=password123
27-
POSTGRES_DATABASE=nextauth_prisma
28-
29-
DATABASE_URL=postgresql://admin:password123@localhost:6500/nextauth_prisma?schema=public
30-
3123
POSTGRES_URL=
3224
POSTGRES_PRISMA_URL=postgresql://admin:password123@localhost:6500/nextauth_prisma?schema=public
3325
POSTGRES_URL_NON_POOLING=postgresql://admin:password123@localhost:6500/nextauth_prisma?schema=public

.eslintrc.cjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ const config = {
2929
},
3030
],
3131
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
32-
"@typescript-eslint/no-unsafe-assignment": "off", // or "off" if you want to disable it
33-
"@typescript-eslint/no-unsafe-call": "off", // or "off" if you want to disable it
34-
"@typescript-eslint/no-unsafe-return": "off",
35-
"@typescript-eslint/no-unsafe-member-access": "off",
32+
"@typescript-eslint/no-unsafe-assignment": 0, // or "off" if you want to disable it
33+
"@typescript-eslint/no-unsafe-call": 0, // or "off" if you want to disable it
34+
"@typescript-eslint/no-unsafe-return": 0,
35+
"@typescript-eslint/no-unsafe-member-access": 0,
3636
},
3737
};
3838

src/contexts/AppContext.tsx

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,24 @@
1-
import { api } from "@/utils/api";
2-
import { useSession } from "next-auth/react";
3-
import { createContext, useState } from "react";
1+
import { type IFormatedLessons } from "@/interfaces";
2+
import { createContext, useContext } from "react";
43

5-
type ContextProps = {
6-
message: string;
7-
setMessage: (message: string) => void;
8-
children: React.ReactElement;
9-
};
10-
11-
const initialState = {
12-
message: "",
13-
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
14-
setMessage: (message: string) => {},
15-
};
16-
17-
export const AppContext = createContext(initialState);
18-
19-
function Context({ children }: ContextProps) {
20-
const [message, setMessage] = useState("");
21-
const { data: sessionData } = useSession();
22-
const [fetchNow, setFetchNow] = useState(true);
23-
24-
console.log({ sessionData });
25-
26-
// Requests
27-
// - All
28-
const {
29-
// data: completedQuizzesAllData,
30-
// isLoading: completedQuizzesAllIsLoading,
31-
// refetch: refetchCompletedQuizzesAll,
32-
} = api.completedQuizzes.all.useQuery(
33-
undefined, // no input
34-
{
35-
// Disable request if no session data
36-
enabled: sessionData?.user !== undefined && fetchNow,
37-
},
38-
);
39-
40-
return (
41-
<AppContext.Provider value={{ message, setMessage }}>
42-
{children}
43-
</AppContext.Provider>
44-
);
4+
interface IAppContext {
5+
formattedLessons: IFormatedLessons;
6+
completedQuizzesSlugs: string[];
7+
lessonsWithStatus: IFormatedLessons;
458
}
469

47-
export default Context;
10+
export const AppContext = createContext<IAppContext>({
11+
formattedLessons: {
12+
projects: [],
13+
fundamentals: [],
14+
},
15+
completedQuizzesSlugs: [],
16+
lessonsWithStatus: {
17+
projects: [],
18+
fundamentals: [],
19+
},
20+
});
21+
22+
AppContext.displayName = "AcademyAppContext";
23+
24+
export const useAppContext = () => useContext(AppContext);

src/contexts/AppContextProvider.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/* eslint-disable @typescript-eslint/no-unsafe-return */
3+
/* eslint-disable @typescript-eslint/no-unsafe-call */
4+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
5+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
6+
import { type ReactNode, useEffect, useState } from "react";
7+
import { AppContext } from "./AppContext";
8+
import { type IFormatedLessons } from "@/interfaces";
9+
import { useSession } from "next-auth/react";
10+
import { api } from "@/utils/api";
11+
12+
interface IProps {
13+
children: ReactNode;
14+
}
15+
16+
export function AppContextProvider({ children }: IProps) {
17+
const [formattedLessons, setFormattedLessons] = useState<IFormatedLessons>({
18+
projects: [],
19+
fundamentals: [],
20+
});
21+
const [completedQuizzesSlugs, setCompletedQuizzesSlugs] = useState<string[]>(
22+
[],
23+
);
24+
const [lessonsWithStatus, setLessonsWithStatus] = useState<IFormatedLessons>({
25+
projects: [],
26+
fundamentals: [],
27+
});
28+
29+
const { data: sessionData } = useSession();
30+
31+
// Requests
32+
// - All
33+
const {
34+
data: completedQuizzesAllData,
35+
// isLoading: completedQuizzesAllIsLoading,
36+
// refetch: refetchCompletedQuizzesAll,
37+
} = api.completedQuizzes.all.useQuery(
38+
undefined, // no input
39+
{
40+
// Disable request if no session data
41+
enabled: sessionData?.user !== undefined,
42+
},
43+
);
44+
45+
useEffect(() => {
46+
if (completedQuizzesAllData) {
47+
const slugs = completedQuizzesAllData.map((quiz: any) => quiz.lesson);
48+
if (slugs !== completedQuizzesSlugs) setCompletedQuizzesSlugs(slugs);
49+
}
50+
// eslint-disable-next-line react-hooks/exhaustive-deps
51+
}, [completedQuizzesAllData]);
52+
53+
const fetchFromDirs = async () => {
54+
const lessonsData = await fetch("/api/readfiles").then((res) => res.json());
55+
56+
const lessonsFormatResult: IFormatedLessons = lessonsData.reduce(
57+
(acc: any, curr: any) => {
58+
if (!acc[curr.path]) acc[curr.path] = [];
59+
60+
acc[curr.path].push(curr);
61+
return acc;
62+
},
63+
{},
64+
);
65+
66+
setFormattedLessons(lessonsFormatResult);
67+
};
68+
69+
useEffect(() => {
70+
void fetchFromDirs();
71+
}, []);
72+
73+
useEffect(() => {
74+
if (
75+
sessionData?.user &&
76+
formattedLessons.fundamentals &&
77+
formattedLessons.projects &&
78+
completedQuizzesSlugs.length !== 0
79+
) {
80+
const projectsWithCompleteStatus = formattedLessons.projects.map(
81+
(lesson) => {
82+
const completed = completedQuizzesSlugs.includes(lesson.slug);
83+
return { ...lesson, completed };
84+
},
85+
);
86+
87+
const obj = {
88+
projects: projectsWithCompleteStatus,
89+
fundamentals: formattedLessons.fundamentals,
90+
};
91+
92+
setLessonsWithStatus(obj);
93+
}
94+
// eslint-disable-next-line react-hooks/exhaustive-deps
95+
}, [completedQuizzesSlugs, formattedLessons]);
96+
97+
return (
98+
<AppContext.Provider
99+
value={{ formattedLessons, completedQuizzesSlugs, lessonsWithStatus }}
100+
>
101+
{children}
102+
</AppContext.Provider>
103+
);
104+
}

src/interfaces/index.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
export interface Lesson {
3+
frontMatter: any;
4+
slug: string;
5+
path: string;
6+
completed?: boolean;
7+
}
8+
export interface Lessons {
9+
lessons: {
10+
frontMatter: any;
11+
slug: string;
12+
}[];
13+
}
14+
15+
export interface IFormatedLessons {
16+
projects: Project[];
17+
fundamentals: Fundamental[];
18+
}
19+
20+
export interface Fundamental {
21+
path: Path;
22+
frontMatter: FundamentalFrontMatter;
23+
slug: string;
24+
}
25+
26+
export interface FundamentalFrontMatter {
27+
title: string;
28+
description: string;
29+
icons: string[];
30+
authors?: string[];
31+
i18n?: string;
32+
author?: string[] | string;
33+
}
34+
35+
export enum Path {
36+
Fundamentals = "fundamentals",
37+
}
38+
39+
export interface Project {
40+
path: string;
41+
frontMatter: ProjectFrontMatter;
42+
slug: string;
43+
completed?: boolean;
44+
}
45+
46+
export interface ProjectFrontMatter {
47+
title: string;
48+
description: string;
49+
icons: string[];
50+
i18n?: string;
51+
author?: string;
52+
}

src/pages/_app.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { theme } from "@/theme";
3232
import { MDXProvider } from "@mdx-js/react";
3333
import Components from "@/components/mdx/Components";
3434
import { env } from "@/env.mjs";
35-
35+
import { AppContextProvider } from "@/contexts/AppContextProvider";
3636
// Config
3737
// ========================================================
3838
/**
@@ -93,7 +93,9 @@ const MyApp = ({
9393
<RainbowKitSiweNextAuthProvider>
9494
<RainbowKitProvider chains={chains} initialChain={polygonMumbai}>
9595
<MDXProvider components={Components}>
96-
{getLayout(<Component {...pageProps} />)}
96+
<AppContextProvider>
97+
{getLayout(<Component {...pageProps} />)}
98+
</AppContextProvider>
9799
</MDXProvider>
98100
</RainbowKitProvider>
99101
</RainbowKitSiweNextAuthProvider>

src/pages/api/readfiles.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { CONTENT_PATH } from "@/lib/constants";
2+
import fs from "fs";
3+
import matter from "gray-matter";
4+
import { type NextApiRequest, type NextApiResponse } from "next";
5+
import path from "path";
6+
7+
const handler = (req: NextApiRequest, res: NextApiResponse) => {
8+
const contentDir = path.join(CONTENT_PATH);
9+
const directories = fs.readdirSync(path.join(contentDir));
10+
const lessons: object[] = [];
11+
directories.reverse().map((folder) => {
12+
if (fs.lstatSync(path.join(contentDir, folder)).isDirectory()) {
13+
fs.readdirSync(path.join(contentDir, folder)).map((file) => {
14+
if (!fs.lstatSync(path.join(contentDir, folder, file)).isDirectory()) {
15+
const markdownWithMeta = fs.readFileSync(
16+
path.join(contentDir, folder, file),
17+
"utf-8",
18+
);
19+
20+
const { data: frontMatter } = matter(markdownWithMeta);
21+
lessons.push({
22+
path: folder,
23+
frontMatter,
24+
slug: `${file.replace(".mdx", "")}`,
25+
});
26+
}
27+
});
28+
}
29+
});
30+
res.statusCode = 200;
31+
res.json(lessons);
32+
};
33+
34+
export default handler;

0 commit comments

Comments
 (0)