diff --git a/flourish-client/src/App.tsx b/flourish-client/src/App.tsx index 808f72f..fb8aac6 100644 --- a/flourish-client/src/App.tsx +++ b/flourish-client/src/App.tsx @@ -1,18 +1,23 @@ import { Box } from "@chakra-ui/react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -// import { customLayoutPaths } from "./assets/data/app"; +import { setupInterceptors } from "./api/config/apiConfig"; +import { nav } from "./assets/data/routes"; +import { useAppSelector } from "./hooks/useStore"; import Auth from "./pages/auth"; import SignIn from "./pages/auth/signin"; import SignUp from "./pages/auth/signup"; +import Dashboard from "./pages/dashboard"; +import Questionnaire from "./pages/dashboard/questionnaire"; import Homepage from "./pages/homepage"; -import { useAppSelector } from "./hooks/useStore"; +import QuestionnaireDetails from "./pages/dashboard/questionnaire/QuestionnaireDetails"; +import QuestionnaireList from "./pages/dashboard/questionnaire/QuestionnaireList"; const App = () => { - // const currentRoute = window.location.pathname; - // const isLayout = !customLayoutPaths.includes(currentRoute); - const isSignedIn = useAppSelector((state) => state.flags.isSignedIn); + const token = useAppSelector((state) => state?.user?.token); + + if (token) setupInterceptors(token); return ( { } /> {!isSignedIn && ( - }> + }> } /> - } /> - } /> + } /> + } /> + + )} + + {isSignedIn && ( + }> + }> + } /> + } /> + + Members} /> + Members} /> + sessionRequest} + /> + Overview} /> )} + 404} /> diff --git a/flourish-client/src/api/apiAuth.ts b/flourish-client/src/api/apiAuth.ts new file mode 100644 index 0000000..10331ee --- /dev/null +++ b/flourish-client/src/api/apiAuth.ts @@ -0,0 +1,18 @@ +import { api as apiData } from "../assets/data/server"; +import { SignIn, SignUp } from "../types/Form"; +import { User } from "../types/User"; +import { api } from "./config/apiConfig"; + +export const apiSignIn = async (data: SignIn): Promise => { + const response = await api.post(apiData.signIn, { + ...data, + }); + + return response.data; +}; + +export const apiSignUp = async (data: SignUp): Promise => { + await api.post(apiData.signUp[data.type], { + ...(data as SignIn), + }); +}; diff --git a/flourish-client/src/api/apiQuestionnaire.ts b/flourish-client/src/api/apiQuestionnaire.ts new file mode 100644 index 0000000..a94a1b0 --- /dev/null +++ b/flourish-client/src/api/apiQuestionnaire.ts @@ -0,0 +1,9 @@ +import { api as apiData } from "../assets/data/server"; +import { Questionnaire } from "../types/Questionnaire"; +import { api } from "./config/apiConfig"; + +export const getQuestionnaire = async (): Promise => { + const response = await api.get(apiData.questionnaire.adminCounselor, {}); + + return response.data; +}; diff --git a/flourish-client/src/api/apiSignIn.ts b/flourish-client/src/api/apiSignIn.ts deleted file mode 100644 index c9d6675..0000000 --- a/flourish-client/src/api/apiSignIn.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { api as apiData } from "../assets/data/server"; -import { SignIn } from "../types/Form"; -import { api } from "./config/apiConfig"; - -export const apiSignIn = async (data: SignIn) => { - const response = await api.post(apiData.signIn, { - ...data, - }); - - return response.data.token; -}; diff --git a/flourish-client/src/api/apiSignUp.ts b/flourish-client/src/api/apiSignUp.ts deleted file mode 100644 index bc5642e..0000000 --- a/flourish-client/src/api/apiSignUp.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { api as apiData } from "../assets/data/server"; -import { SignIn, SignUp } from "../types/Form"; -import { api } from "./config/apiConfig"; - -export const apiSignUp = async (data: SignUp) => { - const response = await api.post(apiData.signUp[data.type], { - ...(data as SignIn), - }); - - return response.data; -}; diff --git a/flourish-client/src/api/config/apiConfig.ts b/flourish-client/src/api/config/apiConfig.ts index cdb6adb..1346b85 100644 --- a/flourish-client/src/api/config/apiConfig.ts +++ b/flourish-client/src/api/config/apiConfig.ts @@ -1,9 +1,13 @@ import axios, { AxiosError, AxiosInstance } from "axios"; + import { api as apiData } from "../../assets/data/server"; export const api: AxiosInstance = axios.create({ baseURL: apiData.base, timeout: 30000, // 30 seconds + headers: { + "Content-Type": "application/json", + }, }); const errorHandler = (error: AxiosError) => { @@ -22,3 +26,10 @@ const errorHandler = (error: AxiosError) => { api.interceptors.response.use(undefined, (error) => { return errorHandler(error); }); + +export const setupInterceptors = (token: string) => { + api.interceptors.request.use((config) => { + config.headers.Authorization = `token ${token}`; + return config; + }); +}; diff --git a/flourish-client/src/assets/data/auth.ts b/flourish-client/src/assets/data/auth.ts index 1da2680..b62f7f9 100644 --- a/flourish-client/src/assets/data/auth.ts +++ b/flourish-client/src/assets/data/auth.ts @@ -1,4 +1,4 @@ -import { nav } from "./nav"; +import { routes } from "./routes"; export const headerContent = { signIn: { @@ -16,12 +16,12 @@ export const headerContent = { export const footerContent = { signIn: { title: "Sign Up", - href: nav.signUp, + href: routes.signUp, description: "Don't have an account?", }, signUp: { title: "Sign In", - href: nav.signIn, + href: routes.signIn, description: "Already have an account?", }, }; diff --git a/flourish-client/src/assets/data/dashboard/dashboard.tsx b/flourish-client/src/assets/data/dashboard/dashboard.tsx new file mode 100644 index 0000000..fdcfb52 --- /dev/null +++ b/flourish-client/src/assets/data/dashboard/dashboard.tsx @@ -0,0 +1,28 @@ +import { RxDashboard, RxQuestionMarkCircled } from "react-icons/rx"; + +import { userTypes } from "../../../types/User"; +import { nav, routes } from "../routes"; + +export const menu = { + [userTypes.ADMIN]: [ + { + title: "Dashboard", + href: routes.dashboard, + icon: , + exclude: [nav.questionnaire], + }, + { + title: "Questionnaires", + href: routes.questionnaire, + icon: , + }, + ], +}; + +export const navBar = { + [userTypes.ADMIN]: [ + { title: "Members", href: routes.members }, + { title: "Session Request", href: routes.sessionRequest }, + { title: "Overview", href: routes.overview }, + ], +}; diff --git a/flourish-client/src/assets/data/dashboard/questionnaire/questionnaire.tsx b/flourish-client/src/assets/data/dashboard/questionnaire/questionnaire.tsx new file mode 100644 index 0000000..62fce5c --- /dev/null +++ b/flourish-client/src/assets/data/dashboard/questionnaire/questionnaire.tsx @@ -0,0 +1,38 @@ +import { IoAddCircleOutline, IoRemoveCircleOutline } from "react-icons/io5"; + +export const questionnaireData = { + name: { title: "Questionnaire Group", placeholder: "Enter a name" }, + questionnaires: "All Questionnaires", + questionnaireField: { + title: "Question", + placeholder: "Enter a question", + }, + options: "Options and Points", + optionField: { + option: "Option", + points: "Points", + }, + + button: { + questionnaire: { + add: { + title: "Add a Questionnaire", + icon: , + }, + remove: { + title: "", + icon: , + }, + }, + option: { + title: "Add a option", + icon: , + }, + save: { + title: "Save", + }, + reset: { + title: "Reset", + }, + }, +}; diff --git a/flourish-client/src/assets/data/nav.ts b/flourish-client/src/assets/data/nav.ts deleted file mode 100644 index 3036b49..0000000 --- a/flourish-client/src/assets/data/nav.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const nav = { - home: "/", - auth: "/auth", - signIn: "/auth/signin", - signUp: "/auth/signup", - dashboard: "/dashboard", -}; - -export const header = [ - { title: "Resources", href: "/resources" }, - { - title: "Support", - href: "/support", - }, -]; - -export const headerNav = { - signIn: { title: "Sign In", href: nav.signIn }, - dashboard: { - title: "Dashboard", - href: "/dashboard", - }, -}; diff --git a/flourish-client/src/assets/data/routes.ts b/flourish-client/src/assets/data/routes.ts new file mode 100644 index 0000000..271899e --- /dev/null +++ b/flourish-client/src/assets/data/routes.ts @@ -0,0 +1,48 @@ +export const nav = { + home: "", + auth: "auth", + dashboard: "dashboard", + signIn: "signin", + signUp: "signup", + questionnaire: "questionnaires", + resource: "resource", + support: "support", + members: "members", + sessionRequest: "session-request", + overview: "overview", +}; + +export const routes = { + home: `/${nav.home}`, + auth: `/${nav.auth}`, + signIn: `/${nav.auth}/${nav.signIn}`, + signUp: `/${nav.auth}/${nav.signUp}`, + dashboard: `/${nav.dashboard}`, + questionnaire: `/${nav.dashboard}/${nav.questionnaire}`, + resource: `/${nav.resource}`, + support: `/${nav.support}`, + members: `/${nav.dashboard}/${nav.members}`, + sessionRequest: `/${nav.dashboard}/${nav.sessionRequest}`, + overview: `/${nav.dashboard}/${nav.overview}`, +}; + +export const header = [ + { title: "Resources", href: routes.resource }, + { + title: "Support", + href: routes.support, + }, +]; + +export const headerNav = { + signIn: { title: "Sign In", href: routes.signIn }, + dashboard: { + title: "Dashboard", + href: routes.dashboard, + }, +}; + +export const signOut = { + title: "Sign Out", + href: routes.home, +}; diff --git a/flourish-client/src/assets/data/server.ts b/flourish-client/src/assets/data/server.ts index 7d99db7..987168e 100644 --- a/flourish-client/src/assets/data/server.ts +++ b/flourish-client/src/assets/data/server.ts @@ -1,11 +1,14 @@ import { userTypes } from "../../types/User"; export const api = { - base: "https://flourish.onrender.com", + base: "https://flourish.onrender.com/", signUp: { - [userTypes.CLIENT]: "/client/signup", - [userTypes.ADMIN]: "/adminCounselor/signup", - [userTypes.COUNSELOR]: "counselor/signup", + [userTypes.CLIENT]: "client/signup/", + [userTypes.ADMIN]: "adminCounselor/signup/", + [userTypes.COUNSELOR]: "counselor/signup/", + }, + signIn: "login/", + questionnaire: { + [userTypes.ADMIN]: "adminCounselor/questionnaire/", }, - signIn: "/login", }; diff --git a/flourish-client/src/assets/img/logo.png b/flourish-client/src/assets/img/logo.png index bb1d53b..1d67172 100644 Binary files a/flourish-client/src/assets/img/logo.png and b/flourish-client/src/assets/img/logo.png differ diff --git a/flourish-client/src/assets/svg/Auth.tsx b/flourish-client/src/assets/svg/Auth.tsx index 1c8147d..0522efc 100644 --- a/flourish-client/src/assets/svg/Auth.tsx +++ b/flourish-client/src/assets/svg/Auth.tsx @@ -1,10 +1,10 @@ import { SVGProps } from "react"; import { useLocation } from "react-router-dom"; -import { nav } from "../data/nav"; +import { routes } from "../data/routes"; const SignIn = (props: SVGProps) => { const { pathname } = useLocation(); - const isSignUp = pathname !== nav.signUp; + const isSignUp = pathname !== routes.signUp; return ( { const isSignedIn = useAppSelector((state) => state.flags.isSignedIn); - const userName = useAppSelector((state) => state.user.name); + const name = useAppSelector((state) => state.user.name); const { signOut } = useSignOut(); const renderLink = ({ title, href }: { title: string; href: string }) => ( @@ -32,7 +36,7 @@ const Header = () => { letterSpacing={"-.25px"} textDecoration={"none"} whiteSpace={"nowrap"} - color={"secondary"} + color={"font.secondary"} fontSize={"2xl"} fontWeight={"medium"} pt={"0.6rem"} @@ -40,10 +44,9 @@ const Header = () => { borderBottom={"1px solid transparent"} transition={"all .3s"} _hover={{ - borderColor: "primary", color: "font.heroLight", textShadow: "0 0.4rem 0.8rem rgba(28, 126, 214, 0.25)", - boxShadow: "0 0.6rem 0.4rem -0.4rem rgba(28, 126, 214, 0.5)", + boxShadow: "0 0.6rem 0 -0.4rem rgba(28, 126, 214, 0.5)", }} > {title} @@ -104,7 +107,7 @@ const Header = () => { bg: "transparent", }} > - + @@ -154,11 +157,9 @@ const Header = () => { color: "font.heroLight", textShadow: "0 0.4rem 0.8rem rgba(28, 126, 214, 0.25)", }} - onClick={() => { - signOut(); - }} + onClick={signOut} > - Sign Out + {signOutTitle.title} diff --git a/flourish-client/src/components/common/button/ButtonFull.tsx b/flourish-client/src/components/common/button/ButtonFull.tsx index d9c529c..c848a10 100644 --- a/flourish-client/src/components/common/button/ButtonFull.tsx +++ b/flourish-client/src/components/common/button/ButtonFull.tsx @@ -4,8 +4,8 @@ import { Button } from "@chakra-ui/react"; const ButtonFull = (props: any) => { const { children, - px, - py, + px = "16", + py = "20", bg, fontSize, fontWeight, diff --git a/flourish-client/src/hooks/useSignOut.ts b/flourish-client/src/hooks/useSignOut.ts index 5aaf7e6..83b672e 100644 --- a/flourish-client/src/hooks/useSignOut.ts +++ b/flourish-client/src/hooks/useSignOut.ts @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; -import { nav } from "../assets/data/nav"; +import { routes } from "../assets/data/routes"; import { setIsSignedIn } from "../store/slices/flagSlice"; import { purgeUser } from "../store/slices/userSlice"; import { useAppDispatch } from "./useStore"; @@ -10,9 +10,9 @@ export const useSignOut = () => { const navigate = useNavigate(); const signOut = () => { - dispatch(setIsSignedIn(false)); dispatch(purgeUser()); - navigate(nav.home); + dispatch(setIsSignedIn(false)); + navigate(routes.home); }; return { signOut }; diff --git a/flourish-client/src/pages/auth/component/Layout.tsx b/flourish-client/src/pages/auth/component/Layout.tsx index d4d6fba..d6cfdc7 100644 --- a/flourish-client/src/pages/auth/component/Layout.tsx +++ b/flourish-client/src/pages/auth/component/Layout.tsx @@ -3,6 +3,7 @@ import { Box, Flex, Heading, Text, keyframes } from "@chakra-ui/react"; import { Link, useLocation } from "react-router-dom"; import { footerContent, headerContent } from "../../../assets/data/auth"; +import { useAppSelector } from "../../../hooks/useStore"; const Layout = ({ header, @@ -16,6 +17,8 @@ const Layout = ({ const [keyIndex, setKeyIndex] = useState(1); const { pathname } = useLocation(); + const token = useAppSelector((state) => state.user.token); + useEffect(() => { setKeyIndex((e) => e + 1); }, [pathname]); @@ -63,6 +66,7 @@ to { opacity: 1; } alignItems={"baseline"} justifyContent={"center"} justifySelf={"flex-end"} + opacity={token ? 0 : 1} > {footer.description} diff --git a/flourish-client/src/pages/auth/index.tsx b/flourish-client/src/pages/auth/index.tsx index 130d1a6..607d762 100644 --- a/flourish-client/src/pages/auth/index.tsx +++ b/flourish-client/src/pages/auth/index.tsx @@ -4,8 +4,11 @@ import { Link, Outlet } from "react-router-dom"; import logo from "../../assets/img/logo.png"; import SignInImage from "../../assets/svg/Auth"; import Container from "../../components/common/Container"; +import { useAppSelector } from "../../hooks/useStore"; const Auth = () => { + const token = useAppSelector((state) => state.user.token); + return ( { }} mb={"16"} > - + diff --git a/flourish-client/src/pages/auth/signin/index.tsx b/flourish-client/src/pages/auth/signin/index.tsx index a1007ce..7f6dcae 100644 --- a/flourish-client/src/pages/auth/signin/index.tsx +++ b/flourish-client/src/pages/auth/signin/index.tsx @@ -13,7 +13,7 @@ import { headerContent, successMessage, } from "../../../assets/data/auth"; -import { nav } from "../../../assets/data/nav"; +import { routes } from "../../../assets/data/routes"; import ButtonFull from "../../../components/common/button/ButtonFull"; import { useAppDispatch } from "../../../hooks/useStore"; import { signIn as signInAction } from "../../../store/actions/authActions"; @@ -48,7 +48,7 @@ const SignInForm = () => { setIsFulfilled(true); setTimeout(() => { dispatch(setIsSignedIn(true)); - navigate(nav.dashboard); + navigate(routes.dashboard); }, 3000); break; @@ -158,7 +158,6 @@ const SignInForm = () => { py={"24"} fontSize={"xl"} type="submit" - borderWidth={"2px"} > {footerContent.signUp.title} diff --git a/flourish-client/src/pages/auth/signup/index.tsx b/flourish-client/src/pages/auth/signup/index.tsx index 52b3346..2cc90ec 100644 --- a/flourish-client/src/pages/auth/signup/index.tsx +++ b/flourish-client/src/pages/auth/signup/index.tsx @@ -16,7 +16,7 @@ import { headerContent, successMessage, } from "../../../assets/data/auth"; -import { nav } from "../../../assets/data/nav"; +import { routes } from "../../../assets/data/routes"; import ButtonFull from "../../../components/common/button/ButtonFull"; import { useAppDispatch } from "../../../hooks/useStore"; import { signUp as signUpAction } from "../../../store/actions/authActions"; @@ -66,7 +66,7 @@ const SignUpForm = () => { case Status.FULFILLED: setIsFulfilled(true); setTimeout(() => { - navigate(nav.signIn); + navigate(routes.signIn); }, 3000); break; case Status.REJECTED: @@ -211,7 +211,6 @@ const SignUpForm = () => { py={"24"} fontSize={"xl"} type="submit" - borderWidth={"2px"} > Sign Up diff --git a/flourish-client/src/pages/dashboard/components/Menu.tsx b/flourish-client/src/pages/dashboard/components/Menu.tsx new file mode 100644 index 0000000..a95bc78 --- /dev/null +++ b/flourish-client/src/pages/dashboard/components/Menu.tsx @@ -0,0 +1,90 @@ +import { Box, ButtonGroup, Flex, Text } from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { IoLogOutOutline } from "react-icons/io5"; +import { Link, useLocation } from "react-router-dom"; + +import { menu } from "../../../assets/data/dashboard/dashboard"; +import { signOut as signOutTitle } from "../../../assets/data/routes"; +import { useSignOut } from "../../../hooks/useSignOut"; + +const Menu = () => { + const { signOut } = useSignOut(); + const { pathname } = useLocation(); + const allPaths = pathname.split("/").filter((path) => path !== ""); + + const renderMenu = ({ + title, + href, + icon, + exclude, + activeEffect = true, + onclick, + }: { + title: string; + href: string; + icon?: ReactNode; + exclude?: string[]; + activeEffect?: boolean; + onclick?: () => void; + }): React.ReactNode => { + const isActive = + activeEffect && + allPaths.includes(href.split("/").at(-1) as string) && + !allPaths?.some((path) => exclude?.includes(path)); + + return ( + + + + {icon} + + {title} + + + ); + }; + + return ( + + + {menu.adminCounselor.map(renderMenu)} + + {renderMenu({ + ...signOutTitle, + icon: , + activeEffect: false, + onclick: () => signOut(), + })} + + ); +}; + +export default Menu; diff --git a/flourish-client/src/pages/dashboard/components/NavBar.tsx b/flourish-client/src/pages/dashboard/components/NavBar.tsx new file mode 100644 index 0000000..fd3db56 --- /dev/null +++ b/flourish-client/src/pages/dashboard/components/NavBar.tsx @@ -0,0 +1,74 @@ +import { ReactNode, useEffect, useState } from "react"; +import { Box, Flex } from "@chakra-ui/react"; +import { Link, useLocation } from "react-router-dom"; + +import { navBar } from "../../../assets/data/dashboard/dashboard"; +import { nav } from "../../../assets/data/routes"; + +const NavBar = () => { + const { pathname } = useLocation(); + const [currentRoute, setCurrentRoute] = useState("#"); + + useEffect(() => { + const newRoute = + (pathname.split("/").at(-1) as string) || + (pathname.split("/").at(-2) as string); + + setCurrentRoute(newRoute === nav.dashboard ? nav.members : newRoute); + }, [pathname]); + + console.log(currentRoute) + + const renderLink = ({ + title, + href, + }: { + title: string; + href: string; + }): ReactNode => ( + + + {title} + + + ); + + return ( + + {navBar.adminCounselor.map(renderLink)} + + ); +}; + +export default NavBar; diff --git a/flourish-client/src/pages/dashboard/index.tsx b/flourish-client/src/pages/dashboard/index.tsx new file mode 100644 index 0000000..21e7960 --- /dev/null +++ b/flourish-client/src/pages/dashboard/index.tsx @@ -0,0 +1,75 @@ +import { Avatar, Box, Flex, Grid, Image } from "@chakra-ui/react"; +import { Link, Outlet, useLocation } from "react-router-dom"; + +import { navBar } from "../../assets/data/dashboard/dashboard"; +import { nav, routes } from "../../assets/data/routes"; +import logo from "../../assets/img/logo.png"; +import Container from "../../components/common/Container"; +import { useAppSelector } from "../../hooks/useStore"; +import Menu from "./components/Menu"; +import NavBar from "./components/NavBar"; + +const Dashboard = () => { + const { pathname } = useLocation(); + const name = useAppSelector((state) => state.user.name); + + return ( + + + + + + + + + + {[...navBar.adminCounselor, { href: nav.dashboard }].some( + ({ href }) => + href.split("/").at(-1) === + ((pathname.split("/").at(-1) as string) || + (pathname.split("/").at(-2) as string)) + ) ? ( + + ) : ( + + )} + + + + + + + + + + + + + + ); +}; + +export default Dashboard; diff --git a/flourish-client/src/pages/dashboard/questionnaire/QuestionnaireDetails.tsx b/flourish-client/src/pages/dashboard/questionnaire/QuestionnaireDetails.tsx new file mode 100644 index 0000000..39cdc58 --- /dev/null +++ b/flourish-client/src/pages/dashboard/questionnaire/QuestionnaireDetails.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { useParams } from "react-router-dom"; + +const QuestionnaireDetails = () => { + const { id } = useParams(); + + return
{id}
; +}; + +export default QuestionnaireDetails; diff --git a/flourish-client/src/pages/dashboard/questionnaire/QuestionnaireList.tsx b/flourish-client/src/pages/dashboard/questionnaire/QuestionnaireList.tsx new file mode 100644 index 0000000..9821841 --- /dev/null +++ b/flourish-client/src/pages/dashboard/questionnaire/QuestionnaireList.tsx @@ -0,0 +1,31 @@ +import { Box, SimpleGrid } from "@chakra-ui/react"; +import { useEffect } from "react"; +import { useAppDispatch, useAppSelector } from "../../../hooks/useStore"; +import { fetchQuestionnaire } from "../../../store/actions/questionnaireActions"; +import QuestionnaireCard from "./components/QuestionnaireCard"; +import { Outlet } from "react-router-dom"; + +const QuestionnaireList = () => { + const dispatch = useAppDispatch(); + const questionnaires = useAppSelector( + (state) => state.questionnaire.questionnaires + ); + + useEffect(() => { + dispatch(fetchQuestionnaire()); + }, [dispatch]); + + return ( + + {questionnaires.map((questionnaire, index) => ( + + ))} + + ); +}; + +export default QuestionnaireList; diff --git a/flourish-client/src/pages/dashboard/questionnaire/components/Questionnaire.tsx b/flourish-client/src/pages/dashboard/questionnaire/components/Questionnaire.tsx new file mode 100644 index 0000000..6f41bc9 --- /dev/null +++ b/flourish-client/src/pages/dashboard/questionnaire/components/Questionnaire.tsx @@ -0,0 +1,193 @@ +import { + Box, + Divider, + Editable, + EditableInput, + EditablePreview, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + List, + Text, +} from "@chakra-ui/react"; +import { useEffect } from "react"; +import { useFieldArray, useForm } from "react-hook-form"; +import { questionnaireData } from "../../../../assets/data/dashboard/questionnaire/questionnaire"; +import Container from "../../../../components/common/Container"; +import ButtonFull from "../../../../components/common/button/ButtonFull"; +import { useAppDispatch, useAppSelector } from "../../../../hooks/useStore"; +import { fetchQuestionnaire } from "../../../../store/actions/questionnaireActions"; +import QuestionnaireField from "./QuestionnaireField"; + +const Questionnaire = () => { + const dispatch = useAppDispatch(); + const questionnaires = useAppSelector( + (state) => state.questionnaire.questionnaires[0] + ); + const status = useAppSelector((state) => state.questionnaire.status); + + const { + handleSubmit, + control, + register, + reset, + formState: { errors, isSubmitting }, + } = useForm({ defaultValues: questionnaires }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "questionnaireFields", + }); + + useEffect(() => { + dispatch(fetchQuestionnaire()); + }, [dispatch]); + + return ( + +
console.log(data))} + style={{ + display: "flex", + flexDirection: "column", + gap: "2.4rem", + }} + > + + {/* ------------------------------ Reset Button ------------------------------ */} + + + reset({ + ...questionnaires, + }) + } + > + {questionnaireData.button.reset.title} + + + {/* ------------------------------- Save Button ------------------------------ */} + + + {questionnaireData.button.save.title} + + + + {/* --------------------------- Questionnaire Name --------------------------- */} + + + + + {questionnaireData.name.title}{" "} + + : + + + + + + + + + {errors?.name && (errors?.name?.message as React.ReactNode)} + + + + {/* -------------------------- Questionnaire Fields -------------------------- */} + + + + {questionnaireData.questionnaires} + + + + + {/* ------------------------------ Questionnaire ----------------------------- */} + + + {fields.map((field, index) => ( + + ))} + + + { + append({ + question: "", + }); + }} + > + + + {questionnaireData.button.questionnaire.add.icon} + + {questionnaireData.button.questionnaire.add.title} + + +
+
+ ); +}; + +export default Questionnaire; diff --git a/flourish-client/src/pages/dashboard/questionnaire/components/QuestionnaireCard.tsx b/flourish-client/src/pages/dashboard/questionnaire/components/QuestionnaireCard.tsx new file mode 100644 index 0000000..e93017b --- /dev/null +++ b/flourish-client/src/pages/dashboard/questionnaire/components/QuestionnaireCard.tsx @@ -0,0 +1,70 @@ +import { + Card, + CardHeader, + Heading, + CardBody, + CardFooter, + Button, + Text, + VStack, +} from "@chakra-ui/react"; +import React from "react"; +import { Questionnaire } from "../../../../types/Questionnaire"; +import ButtonFull from "../../../../components/common/button/ButtonFull"; +import { Link } from "react-router-dom"; +import { routes } from "../../../../assets/data/routes"; + +const QuestionnaireCard = ({ + questionnaire, +}: { + questionnaire: Questionnaire; +}) => { + const renderedQuestionnaire = () => { + const questions = questionnaire.questionnaireFields.map( + ({ question }, index) => { + if (index >= 2) return null; + return ( + + {question} + + ); + } + ); + + return ( + + {questions} + + ); + }; + + return ( + + + + {questionnaire.name} + + + {renderedQuestionnaire()} + + + + View Details + + + + + ); +}; + +export default QuestionnaireCard; diff --git a/flourish-client/src/pages/dashboard/questionnaire/components/QuestionnaireField.tsx b/flourish-client/src/pages/dashboard/questionnaire/components/QuestionnaireField.tsx new file mode 100644 index 0000000..0cf235e --- /dev/null +++ b/flourish-client/src/pages/dashboard/questionnaire/components/QuestionnaireField.tsx @@ -0,0 +1,106 @@ +import { + Editable, + EditableInput, + EditablePreview, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Text, +} from "@chakra-ui/react"; +import { + FieldArrayWithId, + FieldErrors, + UseFieldArrayRemove, + UseFormRegister, +} from "react-hook-form"; + +import { questionnaireData } from "../../../../assets/data/dashboard/questionnaire/questionnaire"; +import ButtonFull from "../../../../components/common/button/ButtonFull"; +import { Questionnaire } from "../../../../types/Questionnaire"; + +const QuestionnaireField = ({ + data, + index, + errors, + register, + remove, +}: { + data: FieldArrayWithId; + errors: FieldErrors; + index: number; + remove: UseFieldArrayRemove; + register: UseFormRegister; +}) => ( + + + + {questionnaireData.questionnaireField.title} – {index + 1} + + : + + + + + + + + {errors?.questionnaireFields?.[index] && + (errors?.questionnaireFields?.[index]?.question + ?.message as React.ReactNode)} + + + + remove(index)} + bg={"transparent"} + _hover={{ bg: "transparent", transform: "scale(1.1)" }} + > + + + {questionnaireData.button.questionnaire.remove.icon} + + {questionnaireData.button.questionnaire.remove.title} + + + +); + +export default QuestionnaireField; diff --git a/flourish-client/src/pages/dashboard/questionnaire/index.tsx b/flourish-client/src/pages/dashboard/questionnaire/index.tsx new file mode 100644 index 0000000..8977c80 --- /dev/null +++ b/flourish-client/src/pages/dashboard/questionnaire/index.tsx @@ -0,0 +1,25 @@ +import { Box, SimpleGrid } from "@chakra-ui/react"; +import { useEffect } from "react"; +import { useAppDispatch, useAppSelector } from "../../../hooks/useStore"; +import { fetchQuestionnaire } from "../../../store/actions/questionnaireActions"; +import QuestionnaireCard from "./components/QuestionnaireCard"; +import { Outlet } from "react-router-dom"; + +const Questionnaire = () => { + const dispatch = useAppDispatch(); + const questionnaires = useAppSelector( + (state) => state.questionnaire.questionnaires + ); + + useEffect(() => { + dispatch(fetchQuestionnaire()); + }, [dispatch]); + + return ( + + + + ); +}; + +export default Questionnaire; diff --git a/flourish-client/src/store/actions/authActions.ts b/flourish-client/src/store/actions/authActions.ts index 02db660..cd62c81 100644 --- a/flourish-client/src/store/actions/authActions.ts +++ b/flourish-client/src/store/actions/authActions.ts @@ -1,18 +1,18 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import { SignIn, SignUp } from "../../types/Form"; -import { apiSignUp } from "../../api/apiSignUp"; -import { apiSignIn } from "../../api/apiSignIn"; +import { apiSignUp } from "../../api/apiAuth"; +import { apiSignIn } from "../../api/apiAuth"; export const signUp = createAsyncThunk( - "flourish/signUp", + "auth/signUp", async (data: SignUp) => { return await apiSignUp(data); } ); export const signIn = createAsyncThunk( - "flourish/signIn", + "auth/signIn", async (data: SignIn) => { return await apiSignIn(data); } diff --git a/flourish-client/src/store/actions/questionnaireActions.ts b/flourish-client/src/store/actions/questionnaireActions.ts new file mode 100644 index 0000000..8ab8598 --- /dev/null +++ b/flourish-client/src/store/actions/questionnaireActions.ts @@ -0,0 +1,11 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; + +import { getQuestionnaire } from "../../api/apiQuestionnaire"; +import { Questionnaire } from "../../types/Questionnaire"; + +export const fetchQuestionnaire = createAsyncThunk( + "flourish/questionnaire", + async (): Promise => { + return await getQuestionnaire(); + } +); diff --git a/flourish-client/src/store/actions/userAction.ts b/flourish-client/src/store/actions/userAction.ts deleted file mode 100644 index e69de29..0000000 diff --git a/flourish-client/src/store/slices/authSlice.ts b/flourish-client/src/store/slices/authSlice.ts deleted file mode 100644 index 8625f57..0000000 --- a/flourish-client/src/store/slices/authSlice.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; - -import { Status } from "../../types/Status"; -import { User } from "../../types/User"; -import { signIn, signUp } from "../actions/authActions"; - -const initialState: User = { - token: "", - name: "", - email: "", - status: Status.IDLE, -}; - -const apiSlice = createSlice({ - name: "auth", - initialState, - reducers: {}, - extraReducers: (builder) => { - /* --------------------------------- Sign Up -------------------------------- */ - - builder.addCase(signUp.pending, (state) => { - state.status = Status.PENDING; - }); - - builder.addCase(signUp.fulfilled, (state) => { - state.status = Status.FULFILLED; - }); - - builder.addCase(signUp.rejected, (state) => { - state.status = Status.REJECTED; - }); - - /* --------------------------------- Sign In -------------------------------- */ - - builder.addCase(signIn.pending, (state) => { - state.status = Status.PENDING; - }); - - builder.addCase(signIn.fulfilled, (state, action: PayloadAction) => { - state.status = Status.FULFILLED; - console.log(action); - state.token = action.payload; - }); - - builder.addCase(signIn.rejected, (state) => { - state.status = Status.REJECTED; - state.token = ""; - }); - }, -}); - -export default apiSlice.reducer; diff --git a/flourish-client/src/store/slices/questionnaireSlice.ts b/flourish-client/src/store/slices/questionnaireSlice.ts new file mode 100644 index 0000000..6e5b8ce --- /dev/null +++ b/flourish-client/src/store/slices/questionnaireSlice.ts @@ -0,0 +1,48 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { PURGE } from "redux-persist"; + +import { Questionnaire } from "../../types/Questionnaire"; +import { Status } from "../../types/Status"; +import { fetchQuestionnaire } from "../actions/questionnaireActions"; + +interface reducerType { + questionnaires: Questionnaire[]; + status: Status; +} + +const initialState: reducerType = { + questionnaires: [], + status: Status.IDLE, +}; + +const questionnaireSlice = createSlice({ + name: "questionnaire", + initialState, + reducers: { + purgeQuestionnaire: () => {}, + }, + extraReducers: (builder) => { + builder.addCase(fetchQuestionnaire.pending, (state) => { + state.status = Status.PENDING; + }); + builder.addCase(fetchQuestionnaire.fulfilled, (state, action) => { + state.questionnaires = action.payload; + state.status = Status.FULFILLED; + }); + builder.addCase(fetchQuestionnaire.rejected, (state) => { + state.questionnaires = []; + state.status = Status.REJECTED; + }); + + /* ---------------------------------- PURGE --------------------------------- */ + + builder.addCase(PURGE, (state) => { + state.questionnaires = []; + state.status = Status.IDLE; + }); + }, +}); + +export default questionnaireSlice.reducer; +export const { purgeQuestionnaire } = questionnaireSlice.actions; +export const { name } = questionnaireSlice; diff --git a/flourish-client/src/store/slices/userSlice.ts b/flourish-client/src/store/slices/userSlice.ts index 4072fd9..30c0a75 100644 --- a/flourish-client/src/store/slices/userSlice.ts +++ b/flourish-client/src/store/slices/userSlice.ts @@ -1,14 +1,21 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { createSlice } from "@reduxjs/toolkit"; import { PURGE } from "redux-persist"; import { Status } from "../../types/Status"; import { User } from "../../types/User"; import { signIn, signUp } from "../actions/authActions"; -const initialState: User = { +interface reducerType extends User { + status: Status; +} + +const initialState: reducerType = { token: "", - name: "", email: "", + name: "", + adminCounselor: "", + counselor: "", + client: "", status: Status.IDLE, }; @@ -17,7 +24,10 @@ const userSlice = createSlice({ initialState, reducers: { purgeUser: (state) => { - state = initialState; + state.email = ""; + state.token = ""; + state.name = ""; + state.status = Status.IDLE; }, }, extraReducers: (builder) => { @@ -41,23 +51,28 @@ const userSlice = createSlice({ state.status = Status.PENDING; }); - builder.addCase( - signIn.fulfilled, - (state, action: PayloadAction) => { - state.status = Status.FULFILLED; - state.token = action.payload; - } - ); + builder.addCase(signIn.fulfilled, (state, action) => { + state.email = action.payload.email; + state.token = action.payload.token; + state.name = action.payload.name; + state.adminCounselor = action.payload.adminCounselor; + state.counselor = action.payload.counselor; + state.client = action.payload.client; + state.status = Status.FULFILLED; + }); builder.addCase(signIn.rejected, (state) => { + state = { ...initialState }; state.status = Status.REJECTED; - state.token = ""; }); /* ---------------------------------- PURGE --------------------------------- */ builder.addCase(PURGE, (state) => { - state = initialState; + state.email = ""; + state.token = ""; + state.name = ""; + state.status = Status.IDLE; }); }, }); diff --git a/flourish-client/src/store/store.ts b/flourish-client/src/store/store.ts index 7027014..6fdd30f 100644 --- a/flourish-client/src/store/store.ts +++ b/flourish-client/src/store/store.ts @@ -11,8 +11,11 @@ import { } from "redux-persist"; import storage from "redux-persist/lib/storage"; -import userSlice, { name as userSliceName } from "./slices/userSlice"; import flagSlice, { name as flagSliceName } from "./slices/flagSlice"; +import questionnaireSlice, { + name as questionnaireSliceName, +} from "./slices/questionnaireSlice"; +import userSlice, { name as userSliceName } from "./slices/userSlice"; const persistConfig = { key: "flourish", @@ -22,6 +25,7 @@ const persistConfig = { const rootReducer = combineReducers({ [userSliceName]: userSlice, [flagSliceName]: flagSlice, + [questionnaireSliceName]: questionnaireSlice, }); const persistedReducer = persistReducer(persistConfig, rootReducer); diff --git a/flourish-client/src/theme/global.css b/flourish-client/src/theme/global.css index 8a1ed15..6c6c1b8 100644 --- a/flourish-client/src/theme/global.css +++ b/flourish-client/src/theme/global.css @@ -38,7 +38,7 @@ body { /* Scroll Bar */ ::-webkit-scrollbar { - width: 1.4rem; + width: 1rem; } ::-webkit-scrollbar-track { diff --git a/flourish-client/src/theme/theme.tsx b/flourish-client/src/theme/theme.tsx index 308bfef..2601668 100644 --- a/flourish-client/src/theme/theme.tsx +++ b/flourish-client/src/theme/theme.tsx @@ -49,6 +49,7 @@ const theme = extendTheme({ }, success: "#40c057", + error: "#f03e3e", font: { // hero: "#204289", diff --git a/flourish-client/src/types/Questionnaire.ts b/flourish-client/src/types/Questionnaire.ts new file mode 100644 index 0000000..8f59e1d --- /dev/null +++ b/flourish-client/src/types/Questionnaire.ts @@ -0,0 +1,15 @@ +import { userTypes } from "./User"; + +export interface Questionnaire { + id: string | null; + name: string; + questionnaireFields: QuestionnaireFields[]; + options: string; + evaluation_range: string; + [userTypes.ADMIN]: string; +} + +export interface QuestionnaireFields { + id?: string; + question: string; +} diff --git a/flourish-client/src/types/User.ts b/flourish-client/src/types/User.ts index 03aacef..ffc328e 100644 --- a/flourish-client/src/types/User.ts +++ b/flourish-client/src/types/User.ts @@ -1,14 +1,14 @@ -import { Status } from "./Status"; - export enum userTypes { - ADMIN = "ADMIN", - CLIENT = "CLIENT", - COUNSELOR = "COUNSELOR", + ADMIN = "adminCounselor", + CLIENT = "counselor", + COUNSELOR = "client", } export interface User { token: string; - name: string; email: string; - status: Status; + name: string; + adminCounselor: string | null; + counselor: string | null; + client: string | null; }