From e6f95642482a1b7743638b1740970b353b908218 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 5 Jul 2022 14:00:56 -0700 Subject: [PATCH 01/69] Unifying courses header into one single layout --- frontend/src/App.tsx | 3 +- .../components/Difficulty/DifficultyIndex.tsx | 56 +++++++++---------- frontend/src/components/Hub/HubIndex.tsx | 38 +++++-------- .../src/components/Layouts/CourseLayout.tsx | 17 ++++++ 4 files changed, 60 insertions(+), 54 deletions(-) create mode 100644 frontend/src/components/Layouts/CourseLayout.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a0a9774..373ebae 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,6 +3,7 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; import DifficultyIndex from "./components/Difficulty/DifficultyIndex"; import Home from "./components/Home"; import HubIndex from "./components/Hub/HubIndex"; +import CourseLayout from "./components/Layouts/CourseLayout"; import Navbar from "./components/Navbar"; function App() { @@ -12,7 +13,7 @@ function App() { Not found} /> } /> - + }> } /> } /> diff --git a/frontend/src/components/Difficulty/DifficultyIndex.tsx b/frontend/src/components/Difficulty/DifficultyIndex.tsx index 4f5d35f..b0fd7d7 100644 --- a/frontend/src/components/Difficulty/DifficultyIndex.tsx +++ b/frontend/src/components/Difficulty/DifficultyIndex.tsx @@ -1,39 +1,35 @@ -import { Container, Box, Heading } from "@chakra-ui/react"; +import { Box, Heading } from "@chakra-ui/react"; import React from "react"; -import Banner from "../Hub/Banner"; import DifficultyCard from "./DifficultyCard"; const DifficultyIndex = () => { return ( <> - - - - React - - - - - - - + + React + + + + + + ); }; diff --git a/frontend/src/components/Hub/HubIndex.tsx b/frontend/src/components/Hub/HubIndex.tsx index 1e8d947..538875d 100644 --- a/frontend/src/components/Hub/HubIndex.tsx +++ b/frontend/src/components/Hub/HubIndex.tsx @@ -1,5 +1,4 @@ import { - Container, Box, Heading, Badge, @@ -7,34 +6,27 @@ import { CircularProgressLabel, } from "@chakra-ui/react"; import React from "react"; -import Banner from "./Banner"; import ResourceGroup from "./ResourceGroup"; const HubIndex = () => { return ( <> - - - - - - React - - Beginner - - - 30% - + + + + React + + Beginner - - - - - - + + 30% + + + + + + + ); }; diff --git a/frontend/src/components/Layouts/CourseLayout.tsx b/frontend/src/components/Layouts/CourseLayout.tsx new file mode 100644 index 0000000..3225ad3 --- /dev/null +++ b/frontend/src/components/Layouts/CourseLayout.tsx @@ -0,0 +1,17 @@ +import { Container, Heading } from "@chakra-ui/react"; +import React from "react"; +import { Outlet } from "react-router-dom"; +import Banner from "../Hub/Banner"; + +const CourseLayout = () => { + return ( + <> + + + + + + ); +}; + +export default CourseLayout; From 4f7c5bb18c66fa84d8d79543c41d5e5a0b47ed6e Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 09:44:55 -0700 Subject: [PATCH 02/69] Adding formik to login form --- .../components/Registration/LoginIndex.tsx | 91 ++++++++++++------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/Registration/LoginIndex.tsx b/frontend/src/components/Registration/LoginIndex.tsx index e00d81d..3290b01 100644 --- a/frontend/src/components/Registration/LoginIndex.tsx +++ b/frontend/src/components/Registration/LoginIndex.tsx @@ -3,50 +3,75 @@ import { Center, Flex, FormControl, + FormErrorMessage, FormLabel, Heading, Input, } from "@chakra-ui/react"; +import { Field, Form, Formik } from "formik"; import React from "react"; import { Link } from "react-router-dom"; import useThemeColor from "../../hooks/useThemeColor"; +interface ILoginForm { + username: string; + password: string; +} + const LoginIndex = () => { const { backgroundColor, borderColor } = useThemeColor(); + + const handleOnSubmit = (values: ILoginForm) => { + console.log(values); + }; + return ( -
- - - Welcome back! - - Username or email - - - - Password - - - - - - - - - -
+ + {({ errors, touched }) => ( +
+
+ + + Welcome back! + + Username or email + + {errors.username} + + + Password + + {errors.password} + + + + + + + + +
+
+ )} +
); }; From cf421b353d85138d90f31f3cc032ca10421c67d1 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 09:55:56 -0700 Subject: [PATCH 03/69] Adding handle login mutation --- backend/routes/auth.ts | 2 +- .../components/Registration/LoginIndex.tsx | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/backend/routes/auth.ts b/backend/routes/auth.ts index d19803b..361ff69 100644 --- a/backend/routes/auth.ts +++ b/backend/routes/auth.ts @@ -5,7 +5,7 @@ import type { IUser } from "../types/user"; const auth = express.Router(); -auth.get("/login", async (req, res, next) => { +auth.post("/login", async (req, res, next) => { const { username, password } = req.body; if (!username || !password) { diff --git a/frontend/src/components/Registration/LoginIndex.tsx b/frontend/src/components/Registration/LoginIndex.tsx index 3290b01..429c928 100644 --- a/frontend/src/components/Registration/LoginIndex.tsx +++ b/frontend/src/components/Registration/LoginIndex.tsx @@ -8,10 +8,13 @@ import { Heading, Input, } from "@chakra-ui/react"; +import axios from "axios"; import { Field, Form, Formik } from "formik"; import React from "react"; +import { useMutation } from "react-query"; import { Link } from "react-router-dom"; import useThemeColor from "../../hooks/useThemeColor"; +import { baseURL } from "../../utils/constants"; interface ILoginForm { username: string; @@ -22,9 +25,24 @@ const LoginIndex = () => { const { backgroundColor, borderColor } = useThemeColor(); const handleOnSubmit = (values: ILoginForm) => { - console.log(values); + handleLogin.mutate(values); }; + const handleLogin = useMutation( + async (values: ILoginForm) => { + const res = await axios.post(`${baseURL}/auth/login`, values); + return res.data; + }, + { + onSuccess: (res) => { + console.log("Success ", res); + }, + onError: () => { + console.log("Error"); + }, + } + ); + return ( { Don{"'"}t have an account yet? Register here! - + From 3eb3bd1fed79749b3eb8d443738bea8cb6f73891 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:05:14 -0700 Subject: [PATCH 04/69] Adding frontend auth helper functions --- frontend/src/types/global.d.ts | 5 +++++ frontend/src/types/user.d.ts | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 frontend/src/types/global.d.ts create mode 100644 frontend/src/types/user.d.ts diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts new file mode 100644 index 0000000..de11dc2 --- /dev/null +++ b/frontend/src/types/global.d.ts @@ -0,0 +1,5 @@ +export interface DbObject { + createdAt: string; + updatedAt: string; + objectId: string; +} diff --git a/frontend/src/types/user.d.ts b/frontend/src/types/user.d.ts new file mode 100644 index 0000000..d6d62c0 --- /dev/null +++ b/frontend/src/types/user.d.ts @@ -0,0 +1,8 @@ +import { DbObject } from "./global"; + +interface IUser extends DbObject { + username: string; + email: string; + sessionToken: string; + ACL: object; +} From 782d7e23602fea0a60eee08d3ba4cf4e9c01c3d6 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:05:21 -0700 Subject: [PATCH 05/69] Adding auth helper function --- frontend/src/utils/auth.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 frontend/src/utils/auth.ts diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts new file mode 100644 index 0000000..4197192 --- /dev/null +++ b/frontend/src/utils/auth.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from "react"; +import { IUser } from "../types/user"; + +export const persistUser = (user: IUser) => { + localStorage.setItem("user", JSON.stringify(user)); +}; + +export const getCachedUser = (): IUser | null => { + const userString = localStorage.get("user"); + if (!userString) return null; + return JSON.parse(userString) as IUser; +}; + +export const deleteCachedUser = () => { + localStorage.removeItem("user"); +}; + +export const useSession = () => { + const [user, setUser] = useState(null); + + useEffect(() => { + fetchUser(); + }, []); + + const fetchUser = () => { + const cachedUser = getCachedUser(); + if (!cachedUser) return; + setUser(cachedUser); + }; + + return { user, fetchUser }; +}; From a4c0c053496fcb32892989bc9fe21c7c4632df81 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:11:22 -0700 Subject: [PATCH 06/69] Adding error handler to login --- .../components/Registration/LoginIndex.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Registration/LoginIndex.tsx b/frontend/src/components/Registration/LoginIndex.tsx index 429c928..c535e0f 100644 --- a/frontend/src/components/Registration/LoginIndex.tsx +++ b/frontend/src/components/Registration/LoginIndex.tsx @@ -7,13 +7,17 @@ import { FormLabel, Heading, Input, + useToast, } from "@chakra-ui/react"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; import { Field, Form, Formik } from "formik"; import React from "react"; import { useMutation } from "react-query"; import { Link } from "react-router-dom"; import useThemeColor from "../../hooks/useThemeColor"; +import { ErrorType } from "../../types/requests"; +import { IUser } from "../../types/user"; +import { persistUser } from "../../utils/auth"; import { baseURL } from "../../utils/constants"; interface ILoginForm { @@ -24,21 +28,29 @@ interface ILoginForm { const LoginIndex = () => { const { backgroundColor, borderColor } = useThemeColor(); + const toast = useToast(); const handleOnSubmit = (values: ILoginForm) => { handleLogin.mutate(values); }; const handleLogin = useMutation( async (values: ILoginForm) => { - const res = await axios.post(`${baseURL}/auth/login`, values); + const res = await axios.post(`${baseURL}/auth/login`, values); return res.data; }, { - onSuccess: (res) => { - console.log("Success ", res); + onSuccess: (user) => { + persistUser(user); + window.location.reload(); }, - onError: () => { - console.log("Error"); + onError: (error: AxiosError) => { + toast({ + title: "An error ocurred", + description: error.response?.data.message, + status: "error", + duration: 5000, + isClosable: true, + }); }, } ); From 7415f197e7b5532e838aa2f191ff358bcbae7a4b Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:11:30 -0700 Subject: [PATCH 07/69] Adding is fetching to login hook --- frontend/src/utils/auth.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 4197192..5b8b2f2 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -17,16 +17,19 @@ export const deleteCachedUser = () => { export const useSession = () => { const [user, setUser] = useState(null); + const [isFetching, setIsFetching] = useState(true); useEffect(() => { fetchUser(); }, []); const fetchUser = () => { + setIsFetching(true); const cachedUser = getCachedUser(); if (!cachedUser) return; setUser(cachedUser); + setIsFetching(false); }; - return { user, fetchUser }; + return { user, fetchUser, isFetching }; }; From 979ffdd7513dec7ea767f214f070fba8b56d3f8d Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:12:54 -0700 Subject: [PATCH 08/69] Adding validation schema to login form --- frontend/src/components/Registration/LoginIndex.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/components/Registration/LoginIndex.tsx b/frontend/src/components/Registration/LoginIndex.tsx index c535e0f..b393d3b 100644 --- a/frontend/src/components/Registration/LoginIndex.tsx +++ b/frontend/src/components/Registration/LoginIndex.tsx @@ -19,12 +19,18 @@ import { ErrorType } from "../../types/requests"; import { IUser } from "../../types/user"; import { persistUser } from "../../utils/auth"; import { baseURL } from "../../utils/constants"; +import * as Yup from "yup"; interface ILoginForm { username: string; password: string; } +const schema = Yup.object({ + username: Yup.string().required("Username is required"), + password: Yup.string().required("Password is required"), +}); + const LoginIndex = () => { const { backgroundColor, borderColor } = useThemeColor(); @@ -59,6 +65,7 @@ const LoginIndex = () => { {({ errors, touched }) => (
From 4d7ee719318d87646e6149562907380cdcc18082 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:22:30 -0700 Subject: [PATCH 09/69] Adding requireAuth component --- frontend/src/App.tsx | 1 + frontend/src/components/Navbar.tsx | 11 +++++++++++ .../components/Registration/RequireAuth.tsx | 19 +++++++++++++++++++ frontend/src/utils/auth.ts | 2 +- 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/Registration/RequireAuth.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 64ef37f..4fadd9c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,6 +6,7 @@ import HubIndex from "./components/Hub/HubIndex"; import Navbar from "./components/Navbar"; import LoginIndex from "./components/Registration/LoginIndex"; import RegisterIndex from "./components/Registration/RegisterIndex"; +import RequireAuth from "./components/Registration/RequireAuth"; function App() { return ( diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 25f3a95..dd7827e 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -9,13 +9,19 @@ import { } from "@chakra-ui/react"; import { MoonIcon, SunIcon } from "@chakra-ui/icons"; import useThemeColor from "../hooks/useThemeColor"; +import { deleteCachedUser, useSession } from "../utils/auth"; const Navbar = () => { const { toggleColorMode, colorMode } = useColorMode(); const colorToggleIcon = colorMode === "dark" ? : ; const { backgroundColor, borderColor } = useThemeColor(); + const { user } = useSession(); + const handleLogout = () => { + deleteCachedUser(); + window.location.reload(); + }; return ( { aria-label="Toggle Color Icon" onClick={() => toggleColorMode()} /> + {user && ( + + )} diff --git a/frontend/src/components/Registration/RequireAuth.tsx b/frontend/src/components/Registration/RequireAuth.tsx new file mode 100644 index 0000000..8903363 --- /dev/null +++ b/frontend/src/components/Registration/RequireAuth.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { Navigate, useLocation, useNavigate } from "react-router-dom"; +import { useSession } from "../../utils/auth"; + +interface Props { + children: JSX.Element; +} +const RequireAuth = ({ children }: Props) => { + const { isFetching, user } = useSession(); + const location = useLocation(); + + if (!isFetching && !user) { + return ; + } + + return children; +}; + +export default RequireAuth; diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 5b8b2f2..07f3f7e 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -6,7 +6,7 @@ export const persistUser = (user: IUser) => { }; export const getCachedUser = (): IUser | null => { - const userString = localStorage.get("user"); + const userString = localStorage.getItem("user"); if (!userString) return null; return JSON.parse(userString) as IUser; }; From 12ea174aa9a7e79b8d583321f1dfce3c025f106f Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:24:35 -0700 Subject: [PATCH 10/69] Removing isFetching as it is unnecesary --- frontend/src/App.tsx | 9 ++++++++- frontend/src/components/Registration/RequireAuth.tsx | 4 ++-- frontend/src/utils/auth.ts | 5 +---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 92f43a2..9eedd3e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,7 +18,14 @@ function App() { } /> } /> } /> - }> + + + + } + > } /> } /> diff --git a/frontend/src/components/Registration/RequireAuth.tsx b/frontend/src/components/Registration/RequireAuth.tsx index 8903363..e70b50e 100644 --- a/frontend/src/components/Registration/RequireAuth.tsx +++ b/frontend/src/components/Registration/RequireAuth.tsx @@ -6,10 +6,10 @@ interface Props { children: JSX.Element; } const RequireAuth = ({ children }: Props) => { - const { isFetching, user } = useSession(); + const { user } = useSession(); const location = useLocation(); - if (!isFetching && !user) { + if (!user) { return ; } diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 07f3f7e..2521680 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -17,19 +17,16 @@ export const deleteCachedUser = () => { export const useSession = () => { const [user, setUser] = useState(null); - const [isFetching, setIsFetching] = useState(true); useEffect(() => { fetchUser(); }, []); const fetchUser = () => { - setIsFetching(true); const cachedUser = getCachedUser(); if (!cachedUser) return; setUser(cachedUser); - setIsFetching(false); }; - return { user, fetchUser, isFetching }; + return { user, fetchUser }; }; From b58bf61c6c0b1ad76dcf114aa96f26a7b3b78d7a Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:32:26 -0700 Subject: [PATCH 11/69] Readding isFetching because it has a purpose --- frontend/src/utils/auth.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 2521680..5c47aef 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -17,16 +17,22 @@ export const deleteCachedUser = () => { export const useSession = () => { const [user, setUser] = useState(null); + const [isFetching, setIsFetching] = useState(true); useEffect(() => { fetchUser(); }, []); const fetchUser = () => { + setIsFetching(true); const cachedUser = getCachedUser(); - if (!cachedUser) return; + if (!cachedUser) { + setIsFetching(false); + return; + } setUser(cachedUser); + setIsFetching(false); }; - return { user, fetchUser }; + return { user, fetchUser, isFetching }; }; From f2e4187c57d1587548cde17152e0eddd4add8c09 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:32:39 -0700 Subject: [PATCH 12/69] Adding loading spinner --- .../components/Registration/RequireAuth.tsx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Registration/RequireAuth.tsx b/frontend/src/components/Registration/RequireAuth.tsx index e70b50e..e19570e 100644 --- a/frontend/src/components/Registration/RequireAuth.tsx +++ b/frontend/src/components/Registration/RequireAuth.tsx @@ -1,3 +1,4 @@ +import { Box, Center, Spinner } from "@chakra-ui/react"; import React from "react"; import { Navigate, useLocation, useNavigate } from "react-router-dom"; import { useSession } from "../../utils/auth"; @@ -5,15 +6,27 @@ import { useSession } from "../../utils/auth"; interface Props { children: JSX.Element; } + const RequireAuth = ({ children }: Props) => { - const { user } = useSession(); + const { user, isFetching } = useSession(); const location = useLocation(); - if (!user) { - return ; - } + console.log(user, isFetching); + const handleRender = () => { + if (!isFetching) + return ( +
+
+ +
+
+ ); + if (!user) + return ; + return children; + }; - return children; + return <>{handleRender()}; }; export default RequireAuth; From 98eea8c733fa5c1180f069811b24c2605c17de83 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:33:51 -0700 Subject: [PATCH 13/69] Fixing infinite loading --- frontend/src/components/Registration/RequireAuth.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Registration/RequireAuth.tsx b/frontend/src/components/Registration/RequireAuth.tsx index e19570e..4613084 100644 --- a/frontend/src/components/Registration/RequireAuth.tsx +++ b/frontend/src/components/Registration/RequireAuth.tsx @@ -13,7 +13,7 @@ const RequireAuth = ({ children }: Props) => { console.log(user, isFetching); const handleRender = () => { - if (!isFetching) + if (isFetching) return (
From 6370c58caccca4ea16490c75743ada9eaeb36747 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:38:45 -0700 Subject: [PATCH 14/69] removing console log --- frontend/src/components/Registration/RequireAuth.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/Registration/RequireAuth.tsx b/frontend/src/components/Registration/RequireAuth.tsx index 4613084..1ec659e 100644 --- a/frontend/src/components/Registration/RequireAuth.tsx +++ b/frontend/src/components/Registration/RequireAuth.tsx @@ -11,7 +11,6 @@ const RequireAuth = ({ children }: Props) => { const { user, isFetching } = useSession(); const location = useLocation(); - console.log(user, isFetching); const handleRender = () => { if (isFetching) return ( From 2d8a5f6cf3c7a607d5de9cf44ec9a0f2438b6171 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:48:04 -0700 Subject: [PATCH 15/69] Adding dashboard route --- backend/pnpm-lock.yaml | 15 +++------------ frontend/src/App.tsx | 13 ++++++++++++- .../src/components/Dashboard/DashboardIndex.tsx | 7 +++++++ 3 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/Dashboard/DashboardIndex.tsx diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 3bf47f9..692d006 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -10,8 +10,8 @@ specifiers: '@types/nodemailer': ^6.4.4 '@types/parse': ^2.18.16 babel-cli: ^6.26.0 - date-fns: ^2.28.0 cors: ^2.8.5 + date-fns: ^2.28.0 dotenv: ^16.0.1 express: ^4.18.1 googleapis: ^105.0.0 @@ -24,8 +24,8 @@ specifiers: typescript: ^4.7.4 dependencies: - date-fns: 2.28.0 cors: 2.8.5 + date-fns: 2.28.0 dotenv: 16.0.1 express: 4.18.1 googleapis: 105.0.0 @@ -2616,6 +2616,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true optional: true + /cors/2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -4748,7 +4749,6 @@ packages: /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true /object-copy/0.1.0: resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} @@ -4804,15 +4804,6 @@ packages: dev: true optional: true - /object-assign/4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: false - - /object-inspect/1.12.2: - resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} - dev: false - /on-finished/2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9eedd3e..4847d8f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom"; +import DashboardIndex from "./components/Dashboard/DashboardIndex"; import DifficultyIndex from "./components/Difficulty/DifficultyIndex"; import Home from "./components/Home"; import HubIndex from "./components/Hub/HubIndex"; @@ -18,6 +19,16 @@ function App() { } /> } /> } /> + + + + } + > + } /> + { + return
DashboardIndex
; +}; + +export default DashboardIndex; From 81f4e157970b3ae9ae340d62bfa387eb6fe43ecc Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:50:56 -0700 Subject: [PATCH 16/69] Adding initial dashboard header --- frontend/src/components/Dashboard/DashboardIndex.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index 236ccfa..e28289e 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -1,7 +1,17 @@ +import { Button, Container, Flex, Heading, VStack } from "@chakra-ui/react"; import React from "react"; const DashboardIndex = () => { - return
DashboardIndex
; + return ( + + + + Learning Dashboard + + + + + ); }; export default DashboardIndex; From 2dfaaf4adfde89815ba8e03c79d9a261e2f0daaa Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:58:04 -0700 Subject: [PATCH 17/69] Adding grid --- .../components/Dashboard/DashboardIndex.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index e28289e..ac2cdba 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -1,5 +1,13 @@ -import { Button, Container, Flex, Heading, VStack } from "@chakra-ui/react"; +import { + Button, + Container, + Flex, + Grid, + Heading, + VStack, +} from "@chakra-ui/react"; import React from "react"; +import CourseCard from "./CourseCard"; const DashboardIndex = () => { return ( @@ -9,6 +17,16 @@ const DashboardIndex = () => { Learning Dashboard + + + + + + ); From 927588c0f88981b5f77581351484159bf0671c96 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:58:17 -0700 Subject: [PATCH 18/69] Adding course dashboard card --- .../src/components/Dashboard/CourseCard.tsx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 frontend/src/components/Dashboard/CourseCard.tsx diff --git a/frontend/src/components/Dashboard/CourseCard.tsx b/frontend/src/components/Dashboard/CourseCard.tsx new file mode 100644 index 0000000..7cd0d71 --- /dev/null +++ b/frontend/src/components/Dashboard/CourseCard.tsx @@ -0,0 +1,58 @@ +import { ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons"; +import { + Box, + Button, + Flex, + Heading, + IconButton, + Image, + Menu, + MenuButton, + MenuItem, + MenuList, + Text, +} from "@chakra-ui/react"; +import React from "react"; +import useThemeColor from "../../hooks/useThemeColor"; + +const CourseCard = () => { + const { backgroundColor, borderColor } = useThemeColor(); + return ( + + + + + + React Course + + + } + > + + Download + Create a Copy + Mark as Draft + Delete + Attend a Workshop + + + + + + ); +}; + +export default CourseCard; From 464e37a443ca8e691d241ec5ae0d9321c92ae7e9 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 10:59:15 -0700 Subject: [PATCH 19/69] Adding correct menu items --- frontend/src/components/Dashboard/CourseCard.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Dashboard/CourseCard.tsx b/frontend/src/components/Dashboard/CourseCard.tsx index 7cd0d71..95c2fc4 100644 --- a/frontend/src/components/Dashboard/CourseCard.tsx +++ b/frontend/src/components/Dashboard/CourseCard.tsx @@ -24,6 +24,7 @@ const CourseCard = () => { borderWidth={1} borderRadius={4} w="100%" + cursor={"pointer"} > @@ -42,11 +43,8 @@ const CourseCard = () => { icon={} > - Download - Create a Copy - Mark as Draft - Delete - Attend a Workshop + Share course + Delete course From a4e88cf8f2978a083d7cdcd8cfbc0b2d43e79ec5 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:08:19 -0700 Subject: [PATCH 20/69] Adding progress detail to popover --- .../components/Dashboard/DashboardIndex.tsx | 2 + frontend/src/components/Popover/Popover.tsx | 50 +++++++++++++++++++ .../src/components/Popover/PopoverPortal.tsx | 7 +++ 3 files changed, 59 insertions(+) create mode 100644 frontend/src/components/Popover/Popover.tsx create mode 100644 frontend/src/components/Popover/PopoverPortal.tsx diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index ac2cdba..63d051a 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -7,6 +7,7 @@ import { VStack, } from "@chakra-ui/react"; import React from "react"; +import Popover from "../Popover/Popover"; import CourseCard from "./CourseCard"; const DashboardIndex = () => { @@ -18,6 +19,7 @@ const DashboardIndex = () => { + { + const { backgroundColor, borderColor } = useThemeColor(); + return ( + + + React Course + + + Beginner + + 50% + + + + Intermediate + + 70% + + + + Advanced + + 20% + + + + ); +}; + +export default Popover; diff --git a/frontend/src/components/Popover/PopoverPortal.tsx b/frontend/src/components/Popover/PopoverPortal.tsx new file mode 100644 index 0000000..4d23e1f --- /dev/null +++ b/frontend/src/components/Popover/PopoverPortal.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const PopoverPortal = () => { + return
PopoverPortal
; +}; + +export default PopoverPortal; From 51e83263628f5f5ca4f225e6401b7a3a642cf6c9 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:11:03 -0700 Subject: [PATCH 21/69] Adding props to popover --- .../components/Dashboard/DashboardIndex.tsx | 7 ++++- frontend/src/components/Popover/Popover.tsx | 28 +++++++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index 63d051a..1b025b5 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -19,7 +19,12 @@ const DashboardIndex = () => { - + { +interface Props { + courseName: string; + beginnerProgress: number; + intermediateProgress: number; + advancedProgress: number; +} + +const Popover = ({ + courseName, + beginnerProgress, + intermediateProgress, + advancedProgress, +}: Props) => { const { backgroundColor, borderColor } = useThemeColor(); return ( { gap={4} > - React Course + {courseName} Beginner - - 50% + + {beginnerProgress}% Intermediate - - 70% + + {intermediateProgress}% Advanced - - 20% + + {advancedProgress}% From 9af71385806544472a26750f6f089184aa5cddf6 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:17:31 -0700 Subject: [PATCH 22/69] Adding portal component --- frontend/src/components/Popover/PopoverPortal.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Popover/PopoverPortal.tsx b/frontend/src/components/Popover/PopoverPortal.tsx index 4d23e1f..c03d99f 100644 --- a/frontend/src/components/Popover/PopoverPortal.tsx +++ b/frontend/src/components/Popover/PopoverPortal.tsx @@ -1,7 +1,11 @@ import React from "react"; +import ReactDom from "react-dom"; -const PopoverPortal = () => { - return
PopoverPortal
; +interface Props { + children: JSX.Element; +} +const PopoverPortal = ({ children }: Props) => { + return ReactDom.createPortal(children, document.body); }; export default PopoverPortal; From 0df2e402e661425391bfcf4d998e0450c37cf656 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:31:24 -0700 Subject: [PATCH 23/69] Adding tooltip logic --- frontend/src/components/Popover/Tooltip.tsx | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 frontend/src/components/Popover/Tooltip.tsx diff --git a/frontend/src/components/Popover/Tooltip.tsx b/frontend/src/components/Popover/Tooltip.tsx new file mode 100644 index 0000000..29f7800 --- /dev/null +++ b/frontend/src/components/Popover/Tooltip.tsx @@ -0,0 +1,36 @@ +import { Box } from "@chakra-ui/react"; +import React, { useState } from "react"; +import PopoverPortal from "./PopoverPortal"; + +interface Props { + children: React.ReactElement; + render: React.ReactElement; +} + +const Tooltip = ({ children, render }: Props) => { + const [show, setShow] = useState(false); + + const handleMouseOver = () => { + setShow(true); + }; + + const handleMouseOut = () => { + setShow(false); + }; + + return ( + <> + {React.cloneElement(children, { + onMouseOver: handleMouseOver, + onMouseOut: handleMouseOut, + })} + + + {render} + + + + ); +}; + +export default Tooltip; From 85750c625021f78b87f78a6963b8f9f10048e3be Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:31:33 -0700 Subject: [PATCH 24/69] Reanming interface --- frontend/src/components/Popover/PopoverPortal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Popover/PopoverPortal.tsx b/frontend/src/components/Popover/PopoverPortal.tsx index c03d99f..caccf1a 100644 --- a/frontend/src/components/Popover/PopoverPortal.tsx +++ b/frontend/src/components/Popover/PopoverPortal.tsx @@ -2,7 +2,7 @@ import React from "react"; import ReactDom from "react-dom"; interface Props { - children: JSX.Element; + children: React.ReactNode; } const PopoverPortal = ({ children }: Props) => { return ReactDom.createPortal(children, document.body); From 5c0698335fa308ff89eee4daf672e85e9bcb5161 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:31:41 -0700 Subject: [PATCH 25/69] Adding test tooltip --- .../components/Dashboard/DashboardIndex.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index 1b025b5..2dc0836 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -8,6 +8,7 @@ import { } from "@chakra-ui/react"; import React from "react"; import Popover from "../Popover/Popover"; +import Tooltip from "../Popover/Tooltip"; import CourseCard from "./CourseCard"; const DashboardIndex = () => { @@ -19,12 +20,18 @@ const DashboardIndex = () => { - + + } + > +
hi
+
Date: Tue, 12 Jul 2022 11:55:13 -0700 Subject: [PATCH 26/69] Adding position logic to tooltip --- frontend/src/components/Popover/Tooltip.tsx | 26 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Popover/Tooltip.tsx b/frontend/src/components/Popover/Tooltip.tsx index 29f7800..1f6c76c 100644 --- a/frontend/src/components/Popover/Tooltip.tsx +++ b/frontend/src/components/Popover/Tooltip.tsx @@ -1,5 +1,5 @@ import { Box } from "@chakra-ui/react"; -import React, { useState } from "react"; +import React, { useRef, useState } from "react"; import PopoverPortal from "./PopoverPortal"; interface Props { @@ -7,11 +7,24 @@ interface Props { render: React.ReactElement; } +const getPoint = (element: HTMLElement, ref: HTMLElement) => { + const pt = { x: 0, y: 0 }; + const rectangle = element.getBoundingClientRect(); + const refRectangle = ref.getBoundingClientRect(); + pt.x = rectangle.left + (rectangle.width - refRectangle.width) / 2; + pt.y = rectangle.top - refRectangle.height / 2; + return pt; +}; + const Tooltip = ({ children, render }: Props) => { const [show, setShow] = useState(false); - const handleMouseOver = () => { + const posRef = useRef({ x: 0, y: 0 }); + const tooltipRef = useRef(); + + const handleMouseOver = (e: React.MouseEvent) => { setShow(true); + posRef.current = getPoint(e.currentTarget, tooltipRef.current); }; const handleMouseOut = () => { @@ -25,7 +38,14 @@ const Tooltip = ({ children, render }: Props) => { onMouseOut: handleMouseOut, })} - + {render} From 3440288724515b11c0aea537bd3ab11640767ec1 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:55:25 -0700 Subject: [PATCH 27/69] Restyling popover --- frontend/src/components/Popover/Popover.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Popover/Popover.tsx b/frontend/src/components/Popover/Popover.tsx index c7f03bf..c56f864 100644 --- a/frontend/src/components/Popover/Popover.tsx +++ b/frontend/src/components/Popover/Popover.tsx @@ -29,31 +29,31 @@ const Popover = ({ backgroundColor={backgroundColor} borderColor={borderColor} borderWidth={1} + borderRadius={4} w={"20vw"} p={4} flexDir="column" gap={4} + boxShadow={"0px 10px 15px -3px rgba(0,0,0,0.3)"} > {courseName} Beginner - - {beginnerProgress}% - + Intermediate - - {intermediateProgress}% - + Advanced - - {advancedProgress}% - + ); From dc91b5e1471eca128a0c8683564b1d2fa3dc7da0 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 11:55:37 -0700 Subject: [PATCH 28/69] Adding test popover to one of the courses --- .../components/Dashboard/DashboardIndex.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index 2dc0836..ff2fc7f 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -19,25 +19,25 @@ const DashboardIndex = () => { Learning Dashboard - - - } - > -
hi
-
- + + } + > +
+ +
+
From c3fa42fc04a729f5810c18657db24b155fff25d6 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 14:41:06 -0700 Subject: [PATCH 29/69] Fixing typescript errors --- frontend/src/components/Popover/Tooltip.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Popover/Tooltip.tsx b/frontend/src/components/Popover/Tooltip.tsx index 1f6c76c..eece653 100644 --- a/frontend/src/components/Popover/Tooltip.tsx +++ b/frontend/src/components/Popover/Tooltip.tsx @@ -12,7 +12,7 @@ const getPoint = (element: HTMLElement, ref: HTMLElement) => { const rectangle = element.getBoundingClientRect(); const refRectangle = ref.getBoundingClientRect(); pt.x = rectangle.left + (rectangle.width - refRectangle.width) / 2; - pt.y = rectangle.top - refRectangle.height / 2; + pt.y = rectangle.top - refRectangle.height / 1.2; return pt; }; @@ -20,10 +20,11 @@ const Tooltip = ({ children, render }: Props) => { const [show, setShow] = useState(false); const posRef = useRef({ x: 0, y: 0 }); - const tooltipRef = useRef(); + const tooltipRef = useRef(null); const handleMouseOver = (e: React.MouseEvent) => { setShow(true); + if (!tooltipRef.current) return; posRef.current = getPoint(e.currentTarget, tooltipRef.current); }; From 6f718f1b666ab5fa29d775b55ff566aadc41cebc Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 14:41:15 -0700 Subject: [PATCH 30/69] Adding tooltip to the image --- .../components/Dashboard/DashboardIndex.tsx | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index ff2fc7f..fced630 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -1,14 +1,5 @@ -import { - Button, - Container, - Flex, - Grid, - Heading, - VStack, -} from "@chakra-ui/react"; +import { Button, Container, Flex, Grid, Heading } from "@chakra-ui/react"; import React from "react"; -import Popover from "../Popover/Popover"; -import Tooltip from "../Popover/Tooltip"; import CourseCard from "./CourseCard"; const DashboardIndex = () => { @@ -24,20 +15,7 @@ const DashboardIndex = () => { gap="1em" mt={10} > - - } - > -
- -
-
+
From 74eab9e21cf78d5b64498a6798dc387691276777 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 14:41:27 -0700 Subject: [PATCH 31/69] Adding tooltip to the image --- frontend/src/components/Dashboard/CourseCard.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Dashboard/CourseCard.tsx b/frontend/src/components/Dashboard/CourseCard.tsx index 95c2fc4..6552b84 100644 --- a/frontend/src/components/Dashboard/CourseCard.tsx +++ b/frontend/src/components/Dashboard/CourseCard.tsx @@ -14,6 +14,8 @@ import { } from "@chakra-ui/react"; import React from "react"; import useThemeColor from "../../hooks/useThemeColor"; +import Popover from "../Popover/Popover"; +import Tooltip from "../Popover/Tooltip"; const CourseCard = () => { const { backgroundColor, borderColor } = useThemeColor(); @@ -27,7 +29,18 @@ const CourseCard = () => { cursor={"pointer"} > - + + } + > + + Date: Tue, 12 Jul 2022 14:46:09 -0700 Subject: [PATCH 32/69] Adding props to course card --- .../src/components/Dashboard/CourseCard.tsx | 30 +++++++++++++------ .../components/Dashboard/DashboardIndex.tsx | 24 +++++++++++++-- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/Dashboard/CourseCard.tsx b/frontend/src/components/Dashboard/CourseCard.tsx index 6552b84..dfffced 100644 --- a/frontend/src/components/Dashboard/CourseCard.tsx +++ b/frontend/src/components/Dashboard/CourseCard.tsx @@ -1,7 +1,6 @@ import { ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons"; import { Box, - Button, Flex, Heading, IconButton, @@ -10,14 +9,27 @@ import { MenuButton, MenuItem, MenuList, - Text, } from "@chakra-ui/react"; import React from "react"; import useThemeColor from "../../hooks/useThemeColor"; import Popover from "../Popover/Popover"; import Tooltip from "../Popover/Tooltip"; -const CourseCard = () => { +interface Props { + title: string; + src: string; + beginnerProgress: number; + intermediateProgress: number; + advancedProgress: number; +} + +const CourseCard = ({ + title, + src, + beginnerProgress, + intermediateProgress, + advancedProgress, +}: Props) => { const { backgroundColor, borderColor } = useThemeColor(); return ( { } > - + { alignItems={"center"} > - React Course + {title} { gap="1em" mt={10} > - - - + + + From bf2b0e5834c420613b6673978a4dfd3f64749c16 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 14:53:05 -0700 Subject: [PATCH 33/69] Adding link between pages --- .../src/components/Dashboard/CourseCard.tsx | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/Dashboard/CourseCard.tsx b/frontend/src/components/Dashboard/CourseCard.tsx index dfffced..05637bc 100644 --- a/frontend/src/components/Dashboard/CourseCard.tsx +++ b/frontend/src/components/Dashboard/CourseCard.tsx @@ -1,6 +1,7 @@ import { ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons"; import { Box, + Button, Flex, Heading, IconButton, @@ -11,6 +12,7 @@ import { MenuList, } from "@chakra-ui/react"; import React from "react"; +import { Link } from "react-router-dom"; import useThemeColor from "../../hooks/useThemeColor"; import Popover from "../Popover/Popover"; import Tooltip from "../Popover/Tooltip"; @@ -38,7 +40,6 @@ const CourseCard = ({ borderWidth={1} borderRadius={4} w="100%" - cursor={"pointer"} > } > - + {title} - - } - > - - Share course - Delete course - - + + + + + + } + > + + Share course + Delete course + + + From 463ed0619f6f34d1f3cdf505383551dbb0b5c229 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 14:56:35 -0700 Subject: [PATCH 34/69] Fixing style issue --- frontend/src/components/Popover/Tooltip.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Popover/Tooltip.tsx b/frontend/src/components/Popover/Tooltip.tsx index eece653..6d81e94 100644 --- a/frontend/src/components/Popover/Tooltip.tsx +++ b/frontend/src/components/Popover/Tooltip.tsx @@ -12,7 +12,7 @@ const getPoint = (element: HTMLElement, ref: HTMLElement) => { const rectangle = element.getBoundingClientRect(); const refRectangle = ref.getBoundingClientRect(); pt.x = rectangle.left + (rectangle.width - refRectangle.width) / 2; - pt.y = rectangle.top - refRectangle.height / 1.2; + pt.y = rectangle.top - refRectangle.height + 10; return pt; }; @@ -43,7 +43,7 @@ const Tooltip = ({ children, render }: Props) => { position="fixed" top={posRef.current.y} left={posRef.current.x} - opacity={show ? 1 : 0} + visibility={show ? "visible" : "hidden"} zIndex={999} ref={tooltipRef} > From 52330e341d45b26ea6ad241ba0f6184a0fcf31ed Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 15:00:57 -0700 Subject: [PATCH 35/69] Adding new course route --- frontend/src/App.tsx | 2 ++ frontend/src/components/Dashboard/DashboardIndex.tsx | 7 ++++++- frontend/src/components/NewCourse/NewCourseIndex.tsx | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/NewCourse/NewCourseIndex.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4847d8f..c95dbcb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,6 +9,7 @@ import Navbar from "./components/Navbar"; import LoginIndex from "./components/Registration/LoginIndex"; import RegisterIndex from "./components/Registration/RegisterIndex"; import RequireAuth from "./components/Registration/RequireAuth"; +import NewCourseIndex from "./components/NewCourse/NewCourseIndex"; function App() { return ( @@ -28,6 +29,7 @@ function App() { } > } /> + } /> { + const navigate = useNavigate(); + return ( Learning Dashboard - + { + return
NewCourseIndex
; +}; + +export default NewCourseIndex; From e917a3f527ff8f60a8a09247ab1bb03b4f5964e0 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 15:14:56 -0700 Subject: [PATCH 36/69] Adding form componet --- .../components/NewCourse/NewCourseIndex.tsx | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/NewCourse/NewCourseIndex.tsx b/frontend/src/components/NewCourse/NewCourseIndex.tsx index 3d19e36..ab48050 100644 --- a/frontend/src/components/NewCourse/NewCourseIndex.tsx +++ b/frontend/src/components/NewCourse/NewCourseIndex.tsx @@ -1,7 +1,35 @@ +import { + Button, + Container, + Flex, + FormControl, + FormLabel, + Heading, + Input, +} from "@chakra-ui/react"; import React from "react"; +import Banner from "../Hub/Banner"; const NewCourseIndex = () => { - return
NewCourseIndex
; + return ( + <> + + + + Let{"'"}s learn something new! + + + + What do you want to learn? + + + + + + + ); }; export default NewCourseIndex; From 4f25b73c00890a5b22b31251f6d23c78c6abb5ab Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Tue, 12 Jul 2022 15:29:23 -0700 Subject: [PATCH 37/69] adding course route in backend --- backend/app.ts | 2 ++ backend/src/routes/course.ts | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 backend/src/routes/course.ts diff --git a/backend/app.ts b/backend/app.ts index 60fb0dd..6822f45 100644 --- a/backend/app.ts +++ b/backend/app.ts @@ -6,6 +6,7 @@ import auth from "./src/routes/auth"; import { NotFoundError } from "./src/utils/errors"; import debug from "./src/routes/debug"; import cors from "cors"; +import course from "./src/routes/course"; dotenv.config(); @@ -19,6 +20,7 @@ app.use(cors()); app.use("/auth", auth); app.use("/debug", debug); +app.use("/course", course); app.get("/", async (req, res) => { const testObject = new Parse.Object("test"); diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts new file mode 100644 index 0000000..8b3e632 --- /dev/null +++ b/backend/src/routes/course.ts @@ -0,0 +1,5 @@ +import express from "express"; + +const course = express.Router(); + +export default course; From 60c534f7ffd0b1135d993c401dabecf05cfc4af5 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 09:53:36 -0700 Subject: [PATCH 38/69] Adding more fields to backend user type --- backend/src/types/course.d.ts | 6 ++++++ backend/src/types/global.d.ts | 1 + backend/src/types/user.d.ts | 5 +++-- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 backend/src/types/course.d.ts diff --git a/backend/src/types/course.d.ts b/backend/src/types/course.d.ts new file mode 100644 index 0000000..0053ba9 --- /dev/null +++ b/backend/src/types/course.d.ts @@ -0,0 +1,6 @@ +import { DbObject } from "./global"; + +export interface ICourse extends DbObject { + name: string; + resources: any[]; +} diff --git a/backend/src/types/global.d.ts b/backend/src/types/global.d.ts index 02c54c2..3b192c6 100644 --- a/backend/src/types/global.d.ts +++ b/backend/src/types/global.d.ts @@ -2,4 +2,5 @@ export interface DbObject { objectId: string; updatedAt: string; createdAt: string; + ACL: object; } diff --git a/backend/src/types/user.d.ts b/backend/src/types/user.d.ts index a6b7a1e..f10ba13 100644 --- a/backend/src/types/user.d.ts +++ b/backend/src/types/user.d.ts @@ -1,7 +1,8 @@ import { DbObject } from "./global"; -export interface IUser extends DbObject { +export interface IUser extends Partial { username: string; email: string; - emailVerified: boolean; + password: string; + emailVerified?: boolean; } From a267b6ef5bf0153fa94ef0dbc3be0466d6856791 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 09:53:51 -0700 Subject: [PATCH 39/69] Adding type to user registration --- backend/src/routes/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 361ff69..0d6b7e2 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -29,7 +29,7 @@ auth.post("/register", async (req, res, next) => { return; } - const user = new Parse.User({ username, password, email }); + const user = new Parse.User({ username, password, email }); try { await user.signUp(); From 5cfa99400d8ad5d9b0f3631ed5175e3cb903905c Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 09:54:10 -0700 Subject: [PATCH 40/69] creating functions to get resources based in a course name --- backend/src/course/course.ts | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 backend/src/course/course.ts diff --git a/backend/src/course/course.ts b/backend/src/course/course.ts new file mode 100644 index 0000000..549a68b --- /dev/null +++ b/backend/src/course/course.ts @@ -0,0 +1,40 @@ +import { getExternalRanking } from "../rating/ranking"; +import { getVideoDetailByIds, getVideosByQuery } from "../rating/youtube"; + +const VIDEOS_PER_QUERY = 100; + +export const createCourse = (name: string) => { + const Course: Parse.Object = new Parse.Object("Course"); + Course.set("name", name); + return Course; +}; + +export const generateResources = async (name: string) => { + const rankedBeginner = await getTop3ByDifficulty(name, "beginner"); + const rankedIntermediate = await getTop3ByDifficulty(name, "intermediate"); + const rankedAdvanced = await getTop3ByDifficulty(name, "advanced"); +}; + +const getTop3ByDifficulty = async (query: string, difficulty: string) => { + const rankedVideos = await getRankedVideos(`${difficulty} ${query} tutorial`); + const splicedRankedVideos = rankedVideos.splice(0, 3); + return splicedRankedVideos; +}; + +const getRankedVideos = async (query: string) => { + const videos = await getVideosByQuery(query, VIDEOS_PER_QUERY); + + const ids = []; + for (const video of videos.data.items) { + ids.push(video.id.videoId); + } + + const videosDetailed = await getVideoDetailByIds(ids, VIDEOS_PER_QUERY); + + const rankedVideos = getExternalRanking(videosDetailed.data.items); + const sortedRankedVideos = rankedVideos.sort( + (a, b) => b.final_score - a.final_score + ); + + return sortedRankedVideos; +}; From ea5de153fa749bb126440e114fa5f15c9ed1781c Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 09:54:17 -0700 Subject: [PATCH 41/69] adding new course route --- backend/src/routes/course.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts index 8b3e632..1d6aaf2 100644 --- a/backend/src/routes/course.ts +++ b/backend/src/routes/course.ts @@ -1,5 +1,19 @@ import express from "express"; +import { createCourse } from "../course/course"; +import { ICourse } from "../types/course"; +import { BadRequestError } from "../utils/errors"; const course = express.Router(); +course.post("/new", (req, res, next) => { + const { name } = req.body; + + if (!name) { + next(new BadRequestError("Missing attributes")); + return; + } + + const course = createCourse(name); +}); + export default course; From 6dcca6a37314feeeacd3bd4675c2463343f1b9e4 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 11:06:06 -0700 Subject: [PATCH 42/69] Adding resource type --- backend/src/types/resource.d.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 backend/src/types/resource.d.ts diff --git a/backend/src/types/resource.d.ts b/backend/src/types/resource.d.ts new file mode 100644 index 0000000..b7402b2 --- /dev/null +++ b/backend/src/types/resource.d.ts @@ -0,0 +1,25 @@ +import { ICourse } from "./course"; +import Parse from "parse/node"; + +export type IResource = + | { + type: "video"; + status: "not started" | "in progress" | "completed"; + level: 1 | 2 | 3; + feedback: number; + title: string; + description: string; + url: string; + thumbnail: string; + channel: string; + course: Parse.Object; + } + | { + type: "website"; + status: "not started" | "in progress" | "completed"; + level: 1 | 2 | 3; + feedback: number; + title: string; + url: string; + course: Parse.Object; + }; From 18b1cbd79c2f5664242c49cab2ee97a4fe4f398d Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 11:06:29 -0700 Subject: [PATCH 43/69] createResource and saveResource function --- backend/src/course/course.ts | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/backend/src/course/course.ts b/backend/src/course/course.ts index 549a68b..e543489 100644 --- a/backend/src/course/course.ts +++ b/backend/src/course/course.ts @@ -1,5 +1,8 @@ import { getExternalRanking } from "../rating/ranking"; import { getVideoDetailByIds, getVideosByQuery } from "../rating/youtube"; +import Parse from "parse/node"; +import { IResource } from "../types/resource"; +import { IWeightedYoutubeVideo } from "../types/youtube"; const VIDEOS_PER_QUERY = 100; @@ -9,10 +12,49 @@ export const createCourse = (name: string) => { return Course; }; +export const createResource = (resource: IResource) => { + const Resource: Parse.Object = new Parse.Object("Resource"); + + Object.keys(resource).forEach((resourceAttribute) => { + Resource.set(resourceAttribute, resource[resourceAttribute]); + }); + + return Resource; +}; + +export const saveResources = async ( + resources: IWeightedYoutubeVideo[], + course: Parse.Object, + level: 1 | 2 | 3 +) => { + for (const resource of resources) { + const video = createResource({ + type: "video", + level, + status: "not started", + title: resource.snippet.title, + description: resource.snippet.description, + url: `https://youtube.com/video/${resource.id}`, + thumbnail: resource.snippet.thumbnails.default.url, + channel: resource.snippet.channelTitle, + feedback: 0, + course, + }); + + await video.save(); + } +}; + export const generateResources = async (name: string) => { const rankedBeginner = await getTop3ByDifficulty(name, "beginner"); const rankedIntermediate = await getTop3ByDifficulty(name, "intermediate"); const rankedAdvanced = await getTop3ByDifficulty(name, "advanced"); + + return { + beginner: rankedBeginner, + intermediate: rankedIntermediate, + advanced: rankedAdvanced, + }; }; const getTop3ByDifficulty = async (query: string, difficulty: string) => { From f190240b1aaba95b6e20222644a2025d90821eef Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 11:06:48 -0700 Subject: [PATCH 44/69] saving resources for different difficulties --- backend/src/routes/course.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts index 1d6aaf2..7658371 100644 --- a/backend/src/routes/course.ts +++ b/backend/src/routes/course.ts @@ -1,11 +1,16 @@ import express from "express"; -import { createCourse } from "../course/course"; +import { + createCourse, + createResource, + generateResources, + saveResources, +} from "../course/course"; import { ICourse } from "../types/course"; import { BadRequestError } from "../utils/errors"; const course = express.Router(); -course.post("/new", (req, res, next) => { +course.post("/new", async (req, res, next) => { const { name } = req.body; if (!name) { @@ -14,6 +19,20 @@ course.post("/new", (req, res, next) => { } const course = createCourse(name); + const { beginner, intermediate, advanced } = await generateResources(name); + + try { + // Save course first + await course.save(); + + await saveResources(beginner, course, 1); + await saveResources(intermediate, course, 2); + await saveResources(advanced, course, 3); + + res.status(201).send("Generated Course!"); + } catch (error) { + next(new BadRequestError(error)); + } }); export default course; From 565a27d1655c73e73b788dfe24751d4921dfe47d Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 11:12:38 -0700 Subject: [PATCH 45/69] Removing intermediate courses --- backend/src/course/course.ts | 4 +--- backend/src/routes/course.ts | 5 ++--- backend/src/types/resource.d.ts | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/src/course/course.ts b/backend/src/course/course.ts index e543489..df9190f 100644 --- a/backend/src/course/course.ts +++ b/backend/src/course/course.ts @@ -25,7 +25,7 @@ export const createResource = (resource: IResource) => { export const saveResources = async ( resources: IWeightedYoutubeVideo[], course: Parse.Object, - level: 1 | 2 | 3 + level: 1 | 2 ) => { for (const resource of resources) { const video = createResource({ @@ -47,12 +47,10 @@ export const saveResources = async ( export const generateResources = async (name: string) => { const rankedBeginner = await getTop3ByDifficulty(name, "beginner"); - const rankedIntermediate = await getTop3ByDifficulty(name, "intermediate"); const rankedAdvanced = await getTop3ByDifficulty(name, "advanced"); return { beginner: rankedBeginner, - intermediate: rankedIntermediate, advanced: rankedAdvanced, }; }; diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts index 7658371..b27dce0 100644 --- a/backend/src/routes/course.ts +++ b/backend/src/routes/course.ts @@ -19,15 +19,14 @@ course.post("/new", async (req, res, next) => { } const course = createCourse(name); - const { beginner, intermediate, advanced } = await generateResources(name); + const { beginner, advanced } = await generateResources(name); try { // Save course first await course.save(); await saveResources(beginner, course, 1); - await saveResources(intermediate, course, 2); - await saveResources(advanced, course, 3); + await saveResources(advanced, course, 2); res.status(201).send("Generated Course!"); } catch (error) { diff --git a/backend/src/types/resource.d.ts b/backend/src/types/resource.d.ts index b7402b2..e8b2772 100644 --- a/backend/src/types/resource.d.ts +++ b/backend/src/types/resource.d.ts @@ -5,7 +5,7 @@ export type IResource = | { type: "video"; status: "not started" | "in progress" | "completed"; - level: 1 | 2 | 3; + level: 1 | 2; feedback: number; title: string; description: string; @@ -17,7 +17,7 @@ export type IResource = | { type: "website"; status: "not started" | "in progress" | "completed"; - level: 1 | 2 | 3; + level: 1 | 2; feedback: number; title: string; url: string; From 18afe2afaa3b53d3e91f193b79774b6c46da7428 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 13:46:18 -0700 Subject: [PATCH 46/69] Adding formik to new course form --- .../components/NewCourse/NewCourseIndex.tsx | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/NewCourse/NewCourseIndex.tsx b/frontend/src/components/NewCourse/NewCourseIndex.tsx index ab48050..13dbab6 100644 --- a/frontend/src/components/NewCourse/NewCourseIndex.tsx +++ b/frontend/src/components/NewCourse/NewCourseIndex.tsx @@ -7,10 +7,19 @@ import { Heading, Input, } from "@chakra-ui/react"; +import { Field, Form, Formik } from "formik"; import React from "react"; import Banner from "../Hub/Banner"; +interface LearnForm { + name: string; +} + const NewCourseIndex = () => { + const handleSubmit = (values: LearnForm) => { + console.log(values); + }; + return ( <> @@ -18,15 +27,21 @@ const NewCourseIndex = () => { Let{"'"}s learn something new! - - - What do you want to learn? - - - - + + {() => ( + + + + What do you want to learn? + + + + + + )} +
); From fa7f2c64f2abe934e55d28e002436d9fdab3086a Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 13:49:15 -0700 Subject: [PATCH 47/69] Adding form error validation and helper --- .../components/NewCourse/NewCourseIndex.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/NewCourse/NewCourseIndex.tsx b/frontend/src/components/NewCourse/NewCourseIndex.tsx index 13dbab6..f490ef8 100644 --- a/frontend/src/components/NewCourse/NewCourseIndex.tsx +++ b/frontend/src/components/NewCourse/NewCourseIndex.tsx @@ -3,6 +3,8 @@ import { Container, Flex, FormControl, + FormErrorMessage, + FormHelperText, FormLabel, Heading, Input, @@ -10,11 +12,16 @@ import { import { Field, Form, Formik } from "formik"; import React from "react"; import Banner from "../Hub/Banner"; +import * as Yup from "yup"; interface LearnForm { name: string; } +const schema = Yup.object({ + name: Yup.string().required("Topic of the course is required"), +}); + const NewCourseIndex = () => { const handleSubmit = (values: LearnForm) => { console.log(values); @@ -27,13 +34,22 @@ const NewCourseIndex = () => { Let{"'"}s learn something new! - - {() => ( + + {({ errors, touched }) => (
- + What do you want to learn? + + Write the topic you would like to learn (E.g. React, NextJs, + Node) + + {errors.name} From fa03a82a7576f4abd79850fa2fcabc016e793e6c Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 14:11:22 -0700 Subject: [PATCH 50/69] Adding types from backend --- frontend/src/types/course.d.ts | 6 ++++++ frontend/src/types/resource.d.ts | 25 +++++++++++++++++++++++++ frontend/src/types/youtube.d.ts | 21 +++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 frontend/src/types/course.d.ts create mode 100644 frontend/src/types/resource.d.ts create mode 100644 frontend/src/types/youtube.d.ts diff --git a/frontend/src/types/course.d.ts b/frontend/src/types/course.d.ts new file mode 100644 index 0000000..0053ba9 --- /dev/null +++ b/frontend/src/types/course.d.ts @@ -0,0 +1,6 @@ +import { DbObject } from "./global"; + +export interface ICourse extends DbObject { + name: string; + resources: any[]; +} diff --git a/frontend/src/types/resource.d.ts b/frontend/src/types/resource.d.ts new file mode 100644 index 0000000..e8b2772 --- /dev/null +++ b/frontend/src/types/resource.d.ts @@ -0,0 +1,25 @@ +import { ICourse } from "./course"; +import Parse from "parse/node"; + +export type IResource = + | { + type: "video"; + status: "not started" | "in progress" | "completed"; + level: 1 | 2; + feedback: number; + title: string; + description: string; + url: string; + thumbnail: string; + channel: string; + course: Parse.Object; + } + | { + type: "website"; + status: "not started" | "in progress" | "completed"; + level: 1 | 2; + feedback: number; + title: string; + url: string; + course: Parse.Object; + }; diff --git a/frontend/src/types/youtube.d.ts b/frontend/src/types/youtube.d.ts new file mode 100644 index 0000000..0febe51 --- /dev/null +++ b/frontend/src/types/youtube.d.ts @@ -0,0 +1,21 @@ +import { youtube_v3 } from "googleapis"; + +export interface IExternalRankingScore { + date: number; + dateXViews: number; + dateXLikes: number; + useOfChapters: number; +} + +export interface IRawYoutubeVideo extends youtube_v3.Schema$Video { + raw_score: IExternalRankingScore; +} + +export interface INormalizedYoutubeVideo extends IRawYoutubeVideo { + normalized_score: IExternalRankingScore; +} + +export interface IWeightedYoutubeVideo extends INormalizedYoutubeVideo { + weighted_score: IExternalRankingScore; + final_score: number; +} From b3a92827d848e239de70f2f64d09c68ed733a52b Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Wed, 13 Jul 2022 14:11:34 -0700 Subject: [PATCH 51/69] Adding toasts --- backend/src/routes/course.ts | 4 +-- .../components/NewCourse/NewCourseIndex.tsx | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts index b27dce0..6510d6c 100644 --- a/backend/src/routes/course.ts +++ b/backend/src/routes/course.ts @@ -23,12 +23,12 @@ course.post("/new", async (req, res, next) => { try { // Save course first - await course.save(); + const courseData = await course.save(); await saveResources(beginner, course, 1); await saveResources(advanced, course, 2); - res.status(201).send("Generated Course!"); + res.status(201).send(courseData); } catch (error) { next(new BadRequestError(error)); } diff --git a/frontend/src/components/NewCourse/NewCourseIndex.tsx b/frontend/src/components/NewCourse/NewCourseIndex.tsx index 508fc2d..b60e21c 100644 --- a/frontend/src/components/NewCourse/NewCourseIndex.tsx +++ b/frontend/src/components/NewCourse/NewCourseIndex.tsx @@ -8,14 +8,18 @@ import { FormLabel, Heading, Input, + useToast, } from "@chakra-ui/react"; import { Field, Form, Formik } from "formik"; import React from "react"; import Banner from "../Hub/Banner"; import * as Yup from "yup"; import { useMutation } from "react-query"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; import { baseURL } from "../../utils/constants"; +import { ErrorType } from "../../types/requests"; +import { useNavigate } from "react-router-dom"; +import { ICourse } from "../../../../types/course"; interface LearnForm { name: string; @@ -26,21 +30,35 @@ const schema = Yup.object({ }); const NewCourseIndex = () => { + const toast = useToast(); + const navigate = useNavigate(); + const handleSubmit = (values: LearnForm) => { createCourse.mutate(values.name); }; const createCourse = useMutation( async (name: string) => { - const res = await axios.post(`${baseURL}/course/new`, { name }); + const res = await axios.post(`${baseURL}/course/new`, { name }); return res.data; }, { - onSuccess: () => { - alert("done"); + onSuccess: (course) => { + toast({ + title: "Course created!", + description: "Your custom course was created successfully!", + status: "success", + }); + navigate(`/course/difficulty/${course.objectId}`); }, - onError: () => { - alert("error"); + onError: (error: AxiosError) => { + toast({ + title: "An error ocurred", + description: error.response?.data.message, + status: "error", + duration: 5000, + isClosable: true, + }); }, } ); From 1d469126fdfe83bb34bb729474631ee0fd577400 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 13:24:09 -0700 Subject: [PATCH 52/69] Adding getConfig function to generate the auth headers --- frontend/src/utils/auth.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 5c47aef..5976c83 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -5,10 +5,10 @@ export const persistUser = (user: IUser) => { localStorage.setItem("user", JSON.stringify(user)); }; -export const getCachedUser = (): IUser | null => { +export const getCachedUser = (): { user: IUser } | null => { const userString = localStorage.getItem("user"); if (!userString) return null; - return JSON.parse(userString) as IUser; + return JSON.parse(userString); }; export const deleteCachedUser = () => { @@ -30,9 +30,15 @@ export const useSession = () => { setIsFetching(false); return; } - setUser(cachedUser); + setUser(cachedUser.user); setIsFetching(false); }; return { user, fetchUser, isFetching }; }; + +export const getConfig = (token: string) => { + return { + headers: { Authorization: token }, + }; +}; From d0a4adf055d31612b41d7346ee517d127d9f7e61 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 13:24:24 -0700 Subject: [PATCH 53/69] Adding auth headers to create course post --- frontend/src/components/NewCourse/NewCourseIndex.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/NewCourse/NewCourseIndex.tsx b/frontend/src/components/NewCourse/NewCourseIndex.tsx index b60e21c..6476da7 100644 --- a/frontend/src/components/NewCourse/NewCourseIndex.tsx +++ b/frontend/src/components/NewCourse/NewCourseIndex.tsx @@ -20,6 +20,7 @@ import { baseURL } from "../../utils/constants"; import { ErrorType } from "../../types/requests"; import { useNavigate } from "react-router-dom"; import { ICourse } from "../../../../types/course"; +import { getConfig, useSession } from "../../utils/auth"; interface LearnForm { name: string; @@ -32,6 +33,7 @@ const schema = Yup.object({ const NewCourseIndex = () => { const toast = useToast(); const navigate = useNavigate(); + const { user } = useSession(); const handleSubmit = (values: LearnForm) => { createCourse.mutate(values.name); @@ -39,7 +41,13 @@ const NewCourseIndex = () => { const createCourse = useMutation( async (name: string) => { - const res = await axios.post(`${baseURL}/course/new`, { name }); + if (!user) throw new Error("User is not logged in"); + + const res = await axios.post( + `${baseURL}/course/new`, + { name }, + getConfig(user.sessionToken) + ); return res.data; }, { From 1a9cc6daf0260940fca16c506d23b4c8c4eb10eb Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 13:24:36 -0700 Subject: [PATCH 54/69] Adding User Typescript type --- backend/src/types/resource.d.ts | 2 ++ backend/src/types/user.d.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/backend/src/types/resource.d.ts b/backend/src/types/resource.d.ts index e8b2772..7e9cf16 100644 --- a/backend/src/types/resource.d.ts +++ b/backend/src/types/resource.d.ts @@ -13,6 +13,7 @@ export type IResource = thumbnail: string; channel: string; course: Parse.Object; + user: Parse.User; } | { type: "website"; @@ -22,4 +23,5 @@ export type IResource = title: string; url: string; course: Parse.Object; + user: Parse.User; }; diff --git a/backend/src/types/user.d.ts b/backend/src/types/user.d.ts index f10ba13..1cc68eb 100644 --- a/backend/src/types/user.d.ts +++ b/backend/src/types/user.d.ts @@ -1,4 +1,6 @@ +import { Request } from "express"; import { DbObject } from "./global"; +import Parse from "parse/node"; export interface IUser extends Partial { username: string; @@ -6,3 +8,5 @@ export interface IUser extends Partial { password: string; emailVerified?: boolean; } + +export type RequestWUser = Request & { user: Parse.User }; From d9ff822e58be2d2c2a661e55fb69cada0b7d9bef Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 13:24:58 -0700 Subject: [PATCH 55/69] Adding user middleware --- backend/src/middleware/getAuthUser.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 backend/src/middleware/getAuthUser.ts diff --git a/backend/src/middleware/getAuthUser.ts b/backend/src/middleware/getAuthUser.ts new file mode 100644 index 0000000..d8e1afa --- /dev/null +++ b/backend/src/middleware/getAuthUser.ts @@ -0,0 +1,24 @@ +import Parse from "parse/node"; +import { Express, Request, Response, NextFunction } from "express"; +import { BadRequestError } from "../utils/errors"; +import { IUser, RequestWUser } from "../types/user"; + +export const getAuthUser = async ( + req: RequestWUser, + res: Response, + next: NextFunction +) => { + const token = req.headers.authorization; + if (!token) { + next(new BadRequestError("Missing authorization header")); + return; + } + try { + Parse.User.enableUnsafeCurrentUser(); + const user = await Parse.User.become(token); + req.user = user; + next(); + } catch (error) { + next(new BadRequestError("Invalid token")); + } +}; From 6c743f885f418ca93c900bcb63c13ed295b8bd27 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 13:25:54 -0700 Subject: [PATCH 56/69] Adding middleware to course route --- backend/src/course/course.ts | 10 ++++++++-- backend/src/routes/course.ts | 15 ++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/backend/src/course/course.ts b/backend/src/course/course.ts index df9190f..c1fdec1 100644 --- a/backend/src/course/course.ts +++ b/backend/src/course/course.ts @@ -6,9 +6,13 @@ import { IWeightedYoutubeVideo } from "../types/youtube"; const VIDEOS_PER_QUERY = 100; -export const createCourse = (name: string) => { +export const createCourse = ( + name: string, + user: Parse.Object +) => { const Course: Parse.Object = new Parse.Object("Course"); Course.set("name", name); + Course.set("user", user); return Course; }; @@ -25,7 +29,8 @@ export const createResource = (resource: IResource) => { export const saveResources = async ( resources: IWeightedYoutubeVideo[], course: Parse.Object, - level: 1 | 2 + level: 1 | 2, + user: Parse.User ) => { for (const resource of resources) { const video = createResource({ @@ -39,6 +44,7 @@ export const saveResources = async ( channel: resource.snippet.channelTitle, feedback: 0, course, + user, }); await video.save(); diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts index 6510d6c..e4033c7 100644 --- a/backend/src/routes/course.ts +++ b/backend/src/routes/course.ts @@ -5,28 +5,33 @@ import { generateResources, saveResources, } from "../course/course"; +import { getAuthUser } from "../middleware/getAuthUser"; import { ICourse } from "../types/course"; +import { RequestWUser } from "../types/user"; import { BadRequestError } from "../utils/errors"; const course = express.Router(); -course.post("/new", async (req, res, next) => { +course.use(getAuthUser); + +course.post("/new", async (req: RequestWUser, res, next) => { const { name } = req.body; + const { user } = req; - if (!name) { + if (!name || !user) { next(new BadRequestError("Missing attributes")); return; } - const course = createCourse(name); + const course = createCourse(name, user); const { beginner, advanced } = await generateResources(name); try { // Save course first const courseData = await course.save(); - await saveResources(beginner, course, 1); - await saveResources(advanced, course, 2); + await saveResources(beginner, course, 1, user); + await saveResources(advanced, course, 2, user); res.status(201).send(courseData); } catch (error) { From e07bb55ff500d381c176894fe42c56f874285b91 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 14:02:50 -0700 Subject: [PATCH 57/69] Adding get courses function --- backend/src/course/course.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/src/course/course.ts b/backend/src/course/course.ts index c1fdec1..0702168 100644 --- a/backend/src/course/course.ts +++ b/backend/src/course/course.ts @@ -6,6 +6,17 @@ import { IWeightedYoutubeVideo } from "../types/youtube"; const VIDEOS_PER_QUERY = 100; +export const getUserCourses = async (user: Parse.Object) => { + const Course = Parse.Object.extend("Course"); + const query = new Parse.Query(Course); + + query.equalTo("user", user); + + const courses = await query.findAll(); + + return courses; +}; + export const createCourse = ( name: string, user: Parse.Object From e3bcf2fb76f696ae072e57d2270de642fd4ef5fa Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 14:06:35 -0700 Subject: [PATCH 58/69] Adding get my courses route --- backend/src/routes/course.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts index e4033c7..2e7a4bc 100644 --- a/backend/src/routes/course.ts +++ b/backend/src/routes/course.ts @@ -3,6 +3,7 @@ import { createCourse, createResource, generateResources, + getUserCourses, saveResources, } from "../course/course"; import { getAuthUser } from "../middleware/getAuthUser"; @@ -39,4 +40,11 @@ course.post("/new", async (req: RequestWUser, res, next) => { } }); +course.get("/me", async (req: RequestWUser, res, next) => { + const { user } = req; + + const courses = await getUserCourses(user); + res.send(courses); +}); + export default course; From 938e137fcafa17e9ae74df7a3c29a5584d8c7f6d Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 14:13:19 -0700 Subject: [PATCH 59/69] Showing cards in react --- .../components/Dashboard/DashboardIndex.tsx | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index 7e141ce..ff0defa 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -1,10 +1,33 @@ import { Button, Container, Flex, Grid, Heading } from "@chakra-ui/react"; import React from "react"; +import { useQuery } from "react-query"; import { useNavigate } from "react-router-dom"; +import { getConfig, useSession } from "../../utils/auth"; import CourseCard from "./CourseCard"; +import axios from "axios"; +import { baseURL } from "../../utils/constants"; +import { ICourse } from "../../types/course"; const DashboardIndex = () => { const navigate = useNavigate(); + const { isFetching, user } = useSession(); + + const { data: courses, isLoading } = useQuery( + "courses", + async () => { + if (!user) throw new Error(); + + const res = await axios.get( + `${baseURL}/course/me`, + getConfig(user?.sessionToken) + ); + + return res.data; + }, + { + enabled: !isFetching, + } + ); return ( @@ -20,27 +43,17 @@ const DashboardIndex = () => { gap="1em" mt={10} > - - - + {courses && + courses.map((course) => ( + + ))} From 9e9ddf2e6bfdae79b1a2b5d537566062979d1d23 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 14:15:20 -0700 Subject: [PATCH 60/69] Adding objectId as link between pages --- frontend/src/components/Dashboard/CourseCard.tsx | 4 +++- frontend/src/components/Dashboard/DashboardIndex.tsx | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Dashboard/CourseCard.tsx b/frontend/src/components/Dashboard/CourseCard.tsx index 05637bc..f653c2f 100644 --- a/frontend/src/components/Dashboard/CourseCard.tsx +++ b/frontend/src/components/Dashboard/CourseCard.tsx @@ -18,6 +18,7 @@ import Popover from "../Popover/Popover"; import Tooltip from "../Popover/Tooltip"; interface Props { + link: string; title: string; src: string; beginnerProgress: number; @@ -27,6 +28,7 @@ interface Props { const CourseCard = ({ title, + link, src, beginnerProgress, intermediateProgress, @@ -63,7 +65,7 @@ const CourseCard = ({ {title} - + diff --git a/frontend/src/components/Dashboard/DashboardIndex.tsx b/frontend/src/components/Dashboard/DashboardIndex.tsx index ff0defa..22687c6 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.tsx +++ b/frontend/src/components/Dashboard/DashboardIndex.tsx @@ -12,7 +12,7 @@ const DashboardIndex = () => { const navigate = useNavigate(); const { isFetching, user } = useSession(); - const { data: courses, isLoading } = useQuery( + const { data: courses } = useQuery( "courses", async () => { if (!user) throw new Error(); @@ -47,6 +47,7 @@ const DashboardIndex = () => { courses.map((course) => ( Date: Thu, 14 Jul 2022 14:29:45 -0700 Subject: [PATCH 61/69] Adding link between category and --- .../src/components/Dashboard/CourseCard.tsx | 9 +++++++-- .../components/Difficulty/DifficultyCard.tsx | 4 +++- .../components/Difficulty/DifficultyIndex.tsx | 18 ++++++++++-------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Dashboard/CourseCard.tsx b/frontend/src/components/Dashboard/CourseCard.tsx index f653c2f..3a9620b 100644 --- a/frontend/src/components/Dashboard/CourseCard.tsx +++ b/frontend/src/components/Dashboard/CourseCard.tsx @@ -12,7 +12,7 @@ import { MenuList, } from "@chakra-ui/react"; import React from "react"; -import { Link } from "react-router-dom"; +import { createSearchParams, Link } from "react-router-dom"; import useThemeColor from "../../hooks/useThemeColor"; import Popover from "../Popover/Popover"; import Tooltip from "../Popover/Tooltip"; @@ -65,7 +65,12 @@ const CourseCard = ({ {title} - + diff --git a/frontend/src/components/Difficulty/DifficultyCard.tsx b/frontend/src/components/Difficulty/DifficultyCard.tsx index 159cf0c..61d89eb 100644 --- a/frontend/src/components/Difficulty/DifficultyCard.tsx +++ b/frontend/src/components/Difficulty/DifficultyCard.tsx @@ -18,6 +18,7 @@ interface Props { title: string; progress: number; src: string; + courseId: string; started?: boolean; phrase: string; } @@ -25,6 +26,7 @@ interface Props { const DifficultyCard = ({ title, progress, + courseId, src, started = false, phrase, @@ -71,7 +73,7 @@ const DifficultyCard = ({ scrollWithOffset(el)} > diff --git a/frontend/src/components/Difficulty/DifficultyIndex.tsx b/frontend/src/components/Difficulty/DifficultyIndex.tsx index b0fd7d7..d363306 100644 --- a/frontend/src/components/Difficulty/DifficultyIndex.tsx +++ b/frontend/src/components/Difficulty/DifficultyIndex.tsx @@ -1,12 +1,19 @@ import { Box, Heading } from "@chakra-ui/react"; import React from "react"; +import { useQuery } from "react-query"; +import { useParams, useSearchParams } from "react-router-dom"; +import { ICourse } from "../../types/course"; import DifficultyCard from "./DifficultyCard"; const DifficultyIndex = () => { + const [searchParams] = useSearchParams(); + const { id } = useParams(); + const name = searchParams.get("name"); + return ( <> - React + {name} { progress={20} src="https://images.unsplash.com/photo-1633356122102-3fe601e05bd2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" phrase="You can do it!" - started - /> - From c708c75e1f9067730782addcaf56f67a7b48ed0a Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 14:36:34 -0700 Subject: [PATCH 62/69] Adding resources route --- backend/app.ts | 2 ++ backend/src/routes/resources.ts | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 backend/src/routes/resources.ts diff --git a/backend/app.ts b/backend/app.ts index 6822f45..dac2070 100644 --- a/backend/app.ts +++ b/backend/app.ts @@ -7,6 +7,7 @@ import { NotFoundError } from "./src/utils/errors"; import debug from "./src/routes/debug"; import cors from "cors"; import course from "./src/routes/course"; +import resources from "./src/routes/resources"; dotenv.config(); @@ -21,6 +22,7 @@ app.use(cors()); app.use("/auth", auth); app.use("/debug", debug); app.use("/course", course); +app.use("/resources", resources); app.get("/", async (req, res) => { const testObject = new Parse.Object("test"); diff --git a/backend/src/routes/resources.ts b/backend/src/routes/resources.ts new file mode 100644 index 0000000..3af1c5a --- /dev/null +++ b/backend/src/routes/resources.ts @@ -0,0 +1,5 @@ +import express from "express"; + +const resources = express.Router(); + +export default resources; From e787f69d38094bd68a67d838ac5034b8dcfa474a Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 14:39:38 -0700 Subject: [PATCH 63/69] Creater resources.ts and moved some functions there --- backend/src/course/course.ts | 11 +---------- backend/src/resources/resources.ts | 26 ++++++++++++++++++++++++++ backend/src/routes/course.ts | 1 - 3 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 backend/src/resources/resources.ts diff --git a/backend/src/course/course.ts b/backend/src/course/course.ts index 0702168..dd7ad42 100644 --- a/backend/src/course/course.ts +++ b/backend/src/course/course.ts @@ -3,6 +3,7 @@ import { getVideoDetailByIds, getVideosByQuery } from "../rating/youtube"; import Parse from "parse/node"; import { IResource } from "../types/resource"; import { IWeightedYoutubeVideo } from "../types/youtube"; +import { createResource } from "../resources/resources"; const VIDEOS_PER_QUERY = 100; @@ -27,16 +28,6 @@ export const createCourse = ( return Course; }; -export const createResource = (resource: IResource) => { - const Resource: Parse.Object = new Parse.Object("Resource"); - - Object.keys(resource).forEach((resourceAttribute) => { - Resource.set(resourceAttribute, resource[resourceAttribute]); - }); - - return Resource; -}; - export const saveResources = async ( resources: IWeightedYoutubeVideo[], course: Parse.Object, diff --git a/backend/src/resources/resources.ts b/backend/src/resources/resources.ts new file mode 100644 index 0000000..3a179c3 --- /dev/null +++ b/backend/src/resources/resources.ts @@ -0,0 +1,26 @@ +import { IResource } from "../types/resource"; + +export const createResource = (resource: IResource) => { + const Resource: Parse.Object = new Parse.Object("Resource"); + + Object.keys(resource).forEach((resourceAttribute) => { + Resource.set(resourceAttribute, resource[resourceAttribute]); + }); + + return Resource; +}; + +export const getResourcesFromCourse = async (courseId: string) => { + const Resource = Parse.Object.extend("Resource"); + const Course = Parse.Object.extend("Course"); + + const query = new Parse.Query(Resource); + + const course = new Course(); + course.id = courseId; + + query.equalTo("course", course); + + const resources = await query.findAll(); + return resources; +}; diff --git a/backend/src/routes/course.ts b/backend/src/routes/course.ts index 2e7a4bc..22f9d94 100644 --- a/backend/src/routes/course.ts +++ b/backend/src/routes/course.ts @@ -1,7 +1,6 @@ import express from "express"; import { createCourse, - createResource, generateResources, getUserCourses, saveResources, From 5d6fd4e7aac90bc091f6ac0e3749fbaf423833a9 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Thu, 14 Jul 2022 14:43:48 -0700 Subject: [PATCH 64/69] adding get resources by courseId --- backend/src/resources/resources.ts | 1 + backend/src/routes/resources.ts | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/backend/src/resources/resources.ts b/backend/src/resources/resources.ts index 3a179c3..69600b4 100644 --- a/backend/src/resources/resources.ts +++ b/backend/src/resources/resources.ts @@ -1,4 +1,5 @@ import { IResource } from "../types/resource"; +import Parse from "parse/node"; export const createResource = (resource: IResource) => { const Resource: Parse.Object = new Parse.Object("Resource"); diff --git a/backend/src/routes/resources.ts b/backend/src/routes/resources.ts index 3af1c5a..9e0cdc5 100644 --- a/backend/src/routes/resources.ts +++ b/backend/src/routes/resources.ts @@ -1,5 +1,16 @@ import express from "express"; +import { getAuthUser } from "../middleware/getAuthUser"; +import { getResourcesFromCourse } from "../resources/resources"; +import { RequestWUser } from "../types/user"; const resources = express.Router(); +resources.use(getAuthUser); + +resources.get("/byCourse/:courseId", async (req: RequestWUser, res, next) => { + const { courseId } = req.params; + const courses = await getResourcesFromCourse(courseId); + res.send(courses); +}); + export default resources; From 538ddfcb9ee9a9837bf05dcaf6970a362f0428cd Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Fri, 15 Jul 2022 09:48:24 -0700 Subject: [PATCH 65/69] adding objectId to resource type --- frontend/src/components/Hub/ResourceGroup.tsx | 31 +++++++------------ frontend/src/types/resource.d.ts | 2 ++ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/Hub/ResourceGroup.tsx b/frontend/src/components/Hub/ResourceGroup.tsx index 584d0da..2174b35 100644 --- a/frontend/src/components/Hub/ResourceGroup.tsx +++ b/frontend/src/components/Hub/ResourceGroup.tsx @@ -1,14 +1,16 @@ import { Box, Grid, Heading } from "@chakra-ui/react"; import React from "react"; +import { IResource } from "../../types/resource"; import DocsCard from "./DocsCard"; import VideoCard from "./VideoCard"; interface Props { title: string; kind: "video" | "documentation" | "both"; + data: IResource[]; } -const ResourceGroup = ({ title, kind }: Props) => { +const ResourceGroup = ({ title, kind, data }: Props) => { return ( @@ -19,28 +21,17 @@ const ResourceGroup = ({ title, kind }: Props) => { templateColumns={["1fr", "1fr", "repeat(2, 1fr)", "repeat(3, 1fr)"]} gap="1em" > - {kind === "video" && ( - <> + {kind === "video" && + data && + data.map((resource) => ( - - - - )} + ))} {kind === "documentation" && ( <> diff --git a/frontend/src/types/resource.d.ts b/frontend/src/types/resource.d.ts index e8b2772..3dae21e 100644 --- a/frontend/src/types/resource.d.ts +++ b/frontend/src/types/resource.d.ts @@ -3,6 +3,7 @@ import Parse from "parse/node"; export type IResource = | { + objectId: string; type: "video"; status: "not started" | "in progress" | "completed"; level: 1 | 2; @@ -15,6 +16,7 @@ export type IResource = course: Parse.Object; } | { + objectId: string; type: "website"; status: "not started" | "in progress" | "completed"; level: 1 | 2; From ce26e67cb318dad1c60965995b5cce023c7d514f Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Fri, 15 Jul 2022 09:51:07 -0700 Subject: [PATCH 66/69] Adding fetching courses from database --- frontend/src/components/Hub/HubIndex.tsx | 42 +++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Hub/HubIndex.tsx b/frontend/src/components/Hub/HubIndex.tsx index 538875d..8651716 100644 --- a/frontend/src/components/Hub/HubIndex.tsx +++ b/frontend/src/components/Hub/HubIndex.tsx @@ -6,9 +6,37 @@ import { CircularProgressLabel, } from "@chakra-ui/react"; import React from "react"; +import { useQuery } from "react-query"; +import { useParams, useSearchParams } from "react-router-dom"; +import { getConfig, useSession } from "../../utils/auth"; import ResourceGroup from "./ResourceGroup"; +import axios from "axios"; +import { baseURL } from "../../utils/constants"; +import { IResource } from "../../types/resource"; const HubIndex = () => { + const { isFetching, user } = useSession(); + const [searchParams] = useSearchParams(); + + const { id } = useParams(); + const difficulty = searchParams.get("difficulty"); + + const { data } = useQuery( + `hub-${id}-${difficulty}`, + async () => { + if (!user) throw new Error("User is not defined"); + + const res = await axios.get( + `${baseURL}/resources/byCourse/${id}/${difficulty}`, + getConfig(user.sessionToken) + ); + return res.data; + }, + { + enabled: !isFetching && !!id && !!difficulty, + } + ); + return ( <> @@ -16,16 +44,22 @@ const HubIndex = () => { React - Beginner + {difficulty === "1" ? "Beginner" : "Advanced"} 30% - - - + {data && ( + <> + + + )} ); From b60de40a621191dda1aa559ffaa5a0fab5170b34 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Fri, 15 Jul 2022 09:51:18 -0700 Subject: [PATCH 67/69] adding difficulty propr --- frontend/src/components/Difficulty/DifficultyCard.tsx | 5 ++++- frontend/src/components/Difficulty/DifficultyIndex.tsx | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Difficulty/DifficultyCard.tsx b/frontend/src/components/Difficulty/DifficultyCard.tsx index 61d89eb..597b76e 100644 --- a/frontend/src/components/Difficulty/DifficultyCard.tsx +++ b/frontend/src/components/Difficulty/DifficultyCard.tsx @@ -10,6 +10,7 @@ import { Text, } from "@chakra-ui/react"; import React from "react"; +import { createSearchParams } from "react-router-dom"; import { HashLink } from "react-router-hash-link"; import useThemeColor from "../../hooks/useThemeColor"; import { scrollWithOffset } from "../../utils/scrollWithOffset"; @@ -21,6 +22,7 @@ interface Props { courseId: string; started?: boolean; phrase: string; + difficulty: 1 | 2; } const DifficultyCard = ({ @@ -30,6 +32,7 @@ const DifficultyCard = ({ src, started = false, phrase, + difficulty, }: Props) => { const { backgroundColor, borderColor } = useThemeColor(); @@ -73,7 +76,7 @@ const DifficultyCard = ({ scrollWithOffset(el)} > diff --git a/frontend/src/components/Difficulty/DifficultyIndex.tsx b/frontend/src/components/Difficulty/DifficultyIndex.tsx index d363306..bb9b6e5 100644 --- a/frontend/src/components/Difficulty/DifficultyIndex.tsx +++ b/frontend/src/components/Difficulty/DifficultyIndex.tsx @@ -23,6 +23,7 @@ const DifficultyIndex = () => { phrase="You can do it!" courseId={id || ""} started + difficulty={1} /> { src="https://images.unsplash.com/photo-1569748130764-3fed0c102c59?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" courseId={id || ""} phrase="Let's go" + difficulty={2} /> From 6229ab45da8124fdf3ed3a4e77c95fb2eda3d3d1 Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Fri, 15 Jul 2022 09:51:33 -0700 Subject: [PATCH 68/69] Adding function to get resources from course and difficulty --- backend/src/resources/resources.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/backend/src/resources/resources.ts b/backend/src/resources/resources.ts index 69600b4..a61956f 100644 --- a/backend/src/resources/resources.ts +++ b/backend/src/resources/resources.ts @@ -25,3 +25,22 @@ export const getResourcesFromCourse = async (courseId: string) => { const resources = await query.findAll(); return resources; }; + +export const getResourcesFromCourseAndDifficulty = async ( + courseId: string, + level: number +) => { + const Resource = Parse.Object.extend("Resource"); + const Course = Parse.Object.extend("Course"); + + const query = new Parse.Query(Resource); + + const course = new Course(); + course.id = courseId; + + query.equalTo("course", course); + query.equalTo("level", level); + + const resources = await query.findAll(); + return resources; +}; From be9fc621a9a03c26449c8b95964bf0991b505e6b Mon Sep 17 00:00:00 2001 From: Mauricio Munoz Date: Fri, 15 Jul 2022 09:51:46 -0700 Subject: [PATCH 69/69] adding route to get resources from courses and difficulty --- backend/src/routes/resources.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/src/routes/resources.ts b/backend/src/routes/resources.ts index 9e0cdc5..bc61704 100644 --- a/backend/src/routes/resources.ts +++ b/backend/src/routes/resources.ts @@ -1,16 +1,25 @@ import express from "express"; import { getAuthUser } from "../middleware/getAuthUser"; -import { getResourcesFromCourse } from "../resources/resources"; +import { + getResourcesFromCourse, + getResourcesFromCourseAndDifficulty, +} from "../resources/resources"; import { RequestWUser } from "../types/user"; const resources = express.Router(); resources.use(getAuthUser); -resources.get("/byCourse/:courseId", async (req: RequestWUser, res, next) => { - const { courseId } = req.params; - const courses = await getResourcesFromCourse(courseId); - res.send(courses); -}); +resources.get( + "/byCourse/:courseId/:level", + async (req: RequestWUser, res, next) => { + const { courseId, level } = req.params; + const courses = await getResourcesFromCourseAndDifficulty( + courseId, + parseInt(level) + ); + res.send(courses); + } +); export default resources;