From 5404369432af7f3f7a5502e0ae7e440824f09b35 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 12 Dec 2022 02:01:54 -0300 Subject: [PATCH] feat: dashboard add/edit/delete technologies --- src/App.jsx | 141 +++---------- src/assets/img/arrow.svg | 3 + src/assets/img/trasher.svg | 3 + src/components/Form/LoginForm/index.jsx | 12 +- src/components/Form/LoginForm/loginSchema.jsx | 4 +- .../Select => }/Option/index.jsx | 2 +- .../Select => }/Option/style.jsx | 0 .../Form/RegisterForm/Select/index.jsx | 25 --- src/components/Form/RegisterForm/index.jsx | 59 ++++-- .../Form/RegisterForm/registerSchema.jsx | 30 +-- src/components/Form/Select/index.jsx | 13 ++ .../Form/{RegisterForm => }/Select/styled.jsx | 7 +- src/components/Modal/ModalForm/index.jsx | 143 ++++++++++++++ .../Modal/ModalForm/modalSchema.jsx | 9 + src/components/Modal/ModalForm/style.jsx | 15 ++ src/components/Modal/index.jsx | 41 ++++ src/components/Modal/style.jsx | 41 ++++ src/components/ProtectedPage/index.jsx | 14 +- src/components/TechList/CardTech/index.jsx | 54 +++++ src/components/TechList/CardTech/style.jsx | 43 ++++ src/components/TechList/index.jsx | 23 +++ src/components/TechList/style.jsx | 16 ++ src/contexts/AuthContext.jsx | 138 +++++++++++++ src/contexts/TechContext.jsx | 187 ++++++++++++++++++ src/hooks/useOutClick.jsx | 16 ++ src/index.js | 5 +- src/pages/DashBoard/Header/index.jsx | 10 +- src/pages/DashBoard/Header/style.jsx | 2 +- src/pages/DashBoard/Main/index.jsx | 33 +++- src/pages/DashBoard/Main/style.jsx | 10 + src/pages/DashBoard/Navbar/index.jsx | 5 +- src/pages/DashBoard/index.jsx | 12 +- src/pages/DashBoard/style.jsx | 6 + src/pages/Login/index.jsx | 12 +- src/pages/Login/style.jsx | 1 + src/pages/Register/index.jsx | 13 +- src/pages/Register/style.jsx | 1 + src/routes/index.jsx | 29 +-- src/styles/button.jsx | 34 +++- 39 files changed, 977 insertions(+), 235 deletions(-) create mode 100644 src/assets/img/arrow.svg create mode 100644 src/assets/img/trasher.svg rename src/components/Form/{RegisterForm/Select => }/Option/index.jsx (63%) rename src/components/Form/{RegisterForm/Select => }/Option/style.jsx (100%) delete mode 100644 src/components/Form/RegisterForm/Select/index.jsx create mode 100644 src/components/Form/Select/index.jsx rename src/components/Form/{RegisterForm => }/Select/styled.jsx (68%) create mode 100644 src/components/Modal/ModalForm/index.jsx create mode 100644 src/components/Modal/ModalForm/modalSchema.jsx create mode 100644 src/components/Modal/ModalForm/style.jsx create mode 100644 src/components/Modal/index.jsx create mode 100644 src/components/Modal/style.jsx create mode 100644 src/components/TechList/CardTech/index.jsx create mode 100644 src/components/TechList/CardTech/style.jsx create mode 100644 src/components/TechList/index.jsx create mode 100644 src/components/TechList/style.jsx create mode 100644 src/contexts/AuthContext.jsx create mode 100644 src/contexts/TechContext.jsx create mode 100644 src/hooks/useOutClick.jsx diff --git a/src/App.jsx b/src/App.jsx index c1980fe..6ae1242 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,124 +1,33 @@ -import { useState } from "react"; -import { toast } from "react-toastify"; import { RoutesComponent } from "./routes"; -import { api } from "./services/api"; import "react-toastify/dist/ReactToastify.css"; -import { MessageToast } from "./styles/messageToast"; import { StyledContainer } from "./styles/toastTheme"; -import { useNavigate } from "react-router-dom"; +import { AuthContext } from "./contexts/AuthContext"; +import { useContext } from "react"; export const App = () => { - const localStorageToken = localStorage.getItem("@TOKEN"); - const localStorageUserId = localStorage.getItem("@USERID"); - const [user, setUser] = useState(null); - const navigate = useNavigate(); - - const userLogin = async (data, setLoading) => { - try { - setLoading(true); - const request = await toast.promise(api.post("sessions", data), { - pending: { - render() { - return ; - }, - icon: true, - theme: "dark", - }, - success: { - render({ data }) { - return ( - - ); - }, - icon: true, - theme: "dark", - }, - error: { - render({ data }) { - return ; - }, - icon: true, - theme: "dark", - }, - }); - if (request.statusText === "OK") { - console.log(request.data); - setUser(request.data); - localStorage.setItem("@TOKEN", request.data.token); - localStorage.setItem("@USERID", request.data.user.id); - navigate("user"); - return request.data; - } - } catch (error) { - console.log(error); - } finally { - setLoading(false); - } - }; - - const userLogout = () => { - localStorage.clear(); - setUser(null); - navigate(""); - }; - - const userRegister = async (data, setLoading) => { - try { - setLoading(true); - const request = await toast.promise(api.post("users", data), { - pending: { - render() { - return ; - }, - icon: true, - theme: "dark", - }, - success: { - render() { - return ; - }, - icon: true, - theme: "dark", - }, - error: { - render({ data }) { - return ; - }, - icon: true, - theme: "dark", - }, - }); - navigate(""); - return request.data; - } catch (error) { - console.log(error); - } finally { - setLoading(false); - } - }; - + const { globalLoading } = useContext(AuthContext); return ( -
- - - -
+ <> + {globalLoading ? ( +

Loading

+ ) : ( + <> + + + + + )} + ); }; diff --git a/src/assets/img/arrow.svg b/src/assets/img/arrow.svg new file mode 100644 index 0000000..b835c90 --- /dev/null +++ b/src/assets/img/arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/trasher.svg b/src/assets/img/trasher.svg new file mode 100644 index 0000000..8886e05 --- /dev/null +++ b/src/assets/img/trasher.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Form/LoginForm/index.jsx b/src/components/Form/LoginForm/index.jsx index ad6290a..9511996 100644 --- a/src/components/Form/LoginForm/index.jsx +++ b/src/components/Form/LoginForm/index.jsx @@ -4,10 +4,12 @@ import { Input } from "../Input"; import { ErrorParagraph, FormLoginStyled } from "./style"; import { yupResolver } from "@hookform/resolvers/yup"; import { LoginSchema } from "./loginSchema"; -import { useState } from "react"; +import { useContext, useState } from "react"; +import { AuthContext } from "../../../contexts/AuthContext"; -export const LoginForm = ({ userLogin }) => { +export const LoginForm = () => { const [loading, setLoading] = useState(false); + const { userLogin } = useContext(AuthContext); const { register, @@ -27,7 +29,7 @@ export const LoginForm = ({ userLogin }) => { id="email" label="E-mail" type="email" - placeholder="Digite aqui seu email" + placeholder="Enter your e-mail" register={register("email")} disabled={loading} /> @@ -36,7 +38,7 @@ export const LoginForm = ({ userLogin }) => { id="password" label="Senha" type="password" - placeholder="Digite aqui sua senha" + placeholder="Enter your password" register={register("password")} disabled={loading} /> @@ -44,7 +46,7 @@ export const LoginForm = ({ userLogin }) => { {errors.password.message} )} - {loading ? "Entrando" : "Entrar"} + {loading ? "Logging in..." : "Log in"} ); diff --git a/src/components/Form/LoginForm/loginSchema.jsx b/src/components/Form/LoginForm/loginSchema.jsx index 184f7ba..c943221 100644 --- a/src/components/Form/LoginForm/loginSchema.jsx +++ b/src/components/Form/LoginForm/loginSchema.jsx @@ -1,6 +1,6 @@ import * as yup from "yup"; export const LoginSchema = yup.object().shape({ - email: yup.string().required("Insira um email válido."), - password: yup.string().required("Insira uma senha válida."), + email: yup.string().required("Enter valid email"), + password: yup.string().required("Enter valid password"), }); diff --git a/src/components/Form/RegisterForm/Select/Option/index.jsx b/src/components/Form/Option/index.jsx similarity index 63% rename from src/components/Form/RegisterForm/Select/Option/index.jsx rename to src/components/Form/Option/index.jsx index 6ab42c7..eeb6a43 100644 --- a/src/components/Form/RegisterForm/Select/Option/index.jsx +++ b/src/components/Form/Option/index.jsx @@ -1,5 +1,5 @@ import { OptionStyled } from "./style"; -export const OptionSelectModule = ({ value, children }) => { +export const OptionSelect = ({ value, children }) => { return {children}; }; diff --git a/src/components/Form/RegisterForm/Select/Option/style.jsx b/src/components/Form/Option/style.jsx similarity index 100% rename from src/components/Form/RegisterForm/Select/Option/style.jsx rename to src/components/Form/Option/style.jsx diff --git a/src/components/Form/RegisterForm/Select/index.jsx b/src/components/Form/RegisterForm/Select/index.jsx deleted file mode 100644 index b92d088..0000000 --- a/src/components/Form/RegisterForm/Select/index.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { FieldSetStyled, LabelStyled } from "../../Input/style"; -import { OptionSelectModule } from "./Option"; -import { SelectStyled } from "./styled"; - -export const SelectCourseModule = ({ id, label, disabled, register }) => { - return ( - - {label} - - - Primeiro módulo (Introdução ao Frontend) - - - Segundo módulo (Frontend Avançado) - - - Terceiro módulo (Introdução ao Backend) - - - Quarto módulo (Backend Avançado) - - - - ); -}; diff --git a/src/components/Form/RegisterForm/index.jsx b/src/components/Form/RegisterForm/index.jsx index 3f87fe4..b6eee6b 100644 --- a/src/components/Form/RegisterForm/index.jsx +++ b/src/components/Form/RegisterForm/index.jsx @@ -1,13 +1,16 @@ import { yupResolver } from "@hookform/resolvers/yup"; -import { useState } from "react"; +import { useContext, useState } from "react"; import { useForm } from "react-hook-form"; +import { AuthContext } from "../../../contexts/AuthContext"; import { ButtonStyled } from "../../../styles/button"; import { Input } from "../Input"; +import { OptionSelect } from "../Option"; +import { Select } from "../Select"; import { RegisterSchema } from "./registerSchema"; -import { SelectCourseModule } from "./Select"; import { ErrorParagraph, FormRegisterStyled } from "./style"; -export const RegisterForm = ({ userRegister }) => { +export const RegisterForm = () => { const [loading, setLoading] = useState(false); + const { userRegister } = useContext(AuthContext); const { register, handleSubmit, @@ -15,6 +18,13 @@ export const RegisterForm = ({ userRegister }) => { } = useForm({ mode: "onBlur", resolver: yupResolver(RegisterSchema), + defaultValues: { + name: "", + email: "", + password: "", + bio: "", + contact: "", + }, }); const submit = (data) => { @@ -24,9 +34,9 @@ export const RegisterForm = ({ userRegister }) => { @@ -35,16 +45,16 @@ export const RegisterForm = ({ userRegister }) => { id="email" label="E-mail" type="email" - placeholder="Digite aqui seu email" + placeholder="Enter your e-mail" register={register("email")} disabled={loading} /> {errors.email && {errors.email.message}} @@ -53,9 +63,9 @@ export const RegisterForm = ({ userRegister }) => { )} @@ -66,28 +76,41 @@ export const RegisterForm = ({ userRegister }) => { id="bio" label="Bio" type="text" - placeholder="Fale sobre você" + placeholder="Talk about you" register={register("bio")} disabled={loading} /> {errors.bio && {errors.bio.message}} {errors.contact && ( {errors.contact.message} )} - + > + + First module (Basic Frontend) + + + Second module (Frontend Advanced) + + + Third module (Basic Backend) + + + Fourth module (Backend Advanced) + + {errors.course_module && ( {errors.course_module.message} )} @@ -97,10 +120,10 @@ export const RegisterForm = ({ userRegister }) => { buttonStyle="disabledButton" disabled={!isValid} > - Cadastrar + Sign up ) : ( - Cadastrar + Sign up )} ); diff --git a/src/components/Form/RegisterForm/registerSchema.jsx b/src/components/Form/RegisterForm/registerSchema.jsx index c90b806..81ccec6 100644 --- a/src/components/Form/RegisterForm/registerSchema.jsx +++ b/src/components/Form/RegisterForm/registerSchema.jsx @@ -2,34 +2,34 @@ import * as yup from "yup"; export const RegisterSchema = yup.object().shape({ name: yup .string() - .required("Insira seu nome") - .min(3, "O nome deve conter no mínimo três caracteres"), + .required("Enter your name") + .min(3, "Your name must contain at least three characters"), email: yup .string() - .required("Insira seu e-mail") - .email("Digite um e-mail válido"), + .required("Enter your e-mail") + .email("Enter a valid e-mail"), password: yup .string() - .required("Insira sua senha") + .required("Enter your password") .matches( /(?=.*?[A-Z])/, - "A senha deve conter no mínimo uma letra maiúscula." + "Your password must contain at least one upper case letter" ) .matches( /(?=.*?[a-z])/, - "A senha deve conter no mínimo uma letra minúscula." + "Your password must contain at least one lower case letter" ) - .matches(/(?=.*?[0-9])/, "A senha deve conter no mínimo um número.") + .matches(/(?=.*?[0-9])/, "Your password must contain at least one number") .matches( /(?=.*?[#?!@$%^&*-])/, - "A senha deve conter no mínimo um caractere especial" + "Your password must contain at least one special character" ) - .min(8, "A senha deve conter no mínimo 8 caracteres"), + .min(8, "Your password must contain a minimum of eigth characters"), passwordConfirmation: yup .string() - .required("A confirmação da senha é necessária") - .oneOf([yup.ref("password")], "As senhas devem ser correspondentes"), - bio: yup.string().required("Fale um pouco sobre você"), - contact: yup.string().required("Insira um meio de contato"), - course_module: yup.string().required("Selecione um módulo do curso"), + .required("Confirmation your password required") + .oneOf([yup.ref("password")], "Passwords must match"), + bio: yup.string().required("Talk about you"), + contact: yup.string().required("Enter your contact"), + course_module: yup.string().required("Select a course module"), }); diff --git a/src/components/Form/Select/index.jsx b/src/components/Form/Select/index.jsx new file mode 100644 index 0000000..eb81fb6 --- /dev/null +++ b/src/components/Form/Select/index.jsx @@ -0,0 +1,13 @@ +import { FieldSetStyled, LabelStyled } from "../Input/style"; +import { SelectStyled } from "./styled"; + +export const Select = ({ id, label, disabled, register, name, children }) => { + return ( + + {label} + + {children} + + + ); +}; diff --git a/src/components/Form/RegisterForm/Select/styled.jsx b/src/components/Form/Select/styled.jsx similarity index 68% rename from src/components/Form/RegisterForm/Select/styled.jsx rename to src/components/Form/Select/styled.jsx index 4246fe9..74d1f47 100644 --- a/src/components/Form/RegisterForm/Select/styled.jsx +++ b/src/components/Form/Select/styled.jsx @@ -6,7 +6,12 @@ export const SelectStyled = styled.select` padding: 0rem 1.6rem; background-color: var(--color-grey2); outline: none; - color: var(--color-grey0); + color: var(--color-grey1); border-radius: 0.4rem; border: 0.1rem solid var(--color-grey2); + + &&:focus { + color: var(--color-grey0); + border: 0.1rem solid var(--color-grey0); + } `; diff --git a/src/components/Modal/ModalForm/index.jsx b/src/components/Modal/ModalForm/index.jsx new file mode 100644 index 0000000..72395e9 --- /dev/null +++ b/src/components/Modal/ModalForm/index.jsx @@ -0,0 +1,143 @@ +import { useContext, useState } from "react"; +import { TechContext } from "../../../contexts/TechContext"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { ModalSchema } from "./modalSchema"; +import { ModalStyledForm } from "./style"; +import { Input } from "../../Form/Input"; +import { ErrorParagraph } from "../../Form/LoginForm/style"; +import { Select } from "../../Form/Select"; +import { OptionSelect } from "../../Form/Option"; +import { ButtonStyled } from "../../../styles/button"; + +export const ModalForm = () => { + const [buttonText, setButtonText] = useState(); + const [loading, setLoading] = useState(); + const { + addTechnology, + setModal, + createTechnology, + deleteTechnology, + idTechnology, + editTechnology, + } = useContext(TechContext); + + const { + register, + handleSubmit, + formState: { isValid, errors }, + } = useForm({ + mode: "onBlur", + resolver: yupResolver(ModalSchema), + defaultValues: { + title: "", + }, + }); + + const submit = (data) => { + addTechnology(data, setLoading); + setModal(false); + }; + + const edit = (data) => { + buttonText === "Delete" + ? deleteTechnology(idTechnology, setLoading) + : editTechnology(idTechnology, data, setLoading); + setModal(false); + }; + + return ( + <> + {createTechnology ? ( + + + {errors.title && ( + {errors.title.message} + )} + + {errors.status && ( + {errors.status.message} + )} + {!isValid ? ( + + Register technology + + ) : ( + Register technology + )} + + ) : ( + + + {errors.title && ( + {errors.title.message} + )} + + {errors.status && ( + {errors.status.message} + )} +
+ {!isValid ? ( + + Save change + + ) : ( + setButtonText(event.target.innerText)} + > + Save change + + )} + setButtonText(event.target.innerText)} + > + Delete + +
+
+ )} + + ); +}; diff --git a/src/components/Modal/ModalForm/modalSchema.jsx b/src/components/Modal/ModalForm/modalSchema.jsx new file mode 100644 index 0000000..e21c676 --- /dev/null +++ b/src/components/Modal/ModalForm/modalSchema.jsx @@ -0,0 +1,9 @@ +import * as yup from "yup"; + +export const ModalSchema = yup.object().shape({ + title: yup + .string() + .required("Enter technology") + .min(3, "Technology name must contain at least three characters"), + status: yup.string().required("Select technology level"), +}); diff --git a/src/components/Modal/ModalForm/style.jsx b/src/components/Modal/ModalForm/style.jsx new file mode 100644 index 0000000..806c011 --- /dev/null +++ b/src/components/Modal/ModalForm/style.jsx @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +export const ModalStyledForm = styled.form` + padding: 1.8rem 2.2rem; + display: flex; + flex-direction: column; + gap: 2rem; + + && div { + display: flex; + align-items: center; + justify-content: space-between; + gap: 2.2rem; + } +`; diff --git a/src/components/Modal/index.jsx b/src/components/Modal/index.jsx new file mode 100644 index 0000000..2b654ef --- /dev/null +++ b/src/components/Modal/index.jsx @@ -0,0 +1,41 @@ +import { StyledTitle } from "../../styles/typography"; +import { ModalContainer, ModalWrapper } from "./style"; +import { MdClose } from "react-icons/md"; +import { useContext, useRef } from "react"; +import { TechContext } from "../../contexts/TechContext"; +import { ModalForm } from "./ModalForm"; +import { useOutClick } from "../../hooks/useOutClick"; + +export const Modal = () => { + const { setModal, createTechnology } = useContext(TechContext); + + const modalRemove = () => { + setModal(false); + }; + + const close = useRef(); + useOutClick(close, () => setModal(false)); + + return ( + + +
+ {createTechnology ? ( + + Register technology + + ) : ( + + Technology details + + )} + + +
+ +
+
+ ); +}; diff --git a/src/components/Modal/style.jsx b/src/components/Modal/style.jsx new file mode 100644 index 0000000..55795af --- /dev/null +++ b/src/components/Modal/style.jsx @@ -0,0 +1,41 @@ +import styled from "styled-components"; + +export const ModalWrapper = styled.div` + position: fixed; + width: 100vw; + height: 100vh; + background-color: rgba(18, 18, 20, 0.5); + display: flex; + justify-content: center; + z-index: 1; +`; + +export const ModalContainer = styled.div` + align-self: center; + position: absolute; + width: 100%; + max-width: 36.9rem; + min-height: 34.2rem; + border-radius: 0.4rem; + background-color: var(--color-grey3); + + && > div { + height: 5rem; + background-color: var(--color-grey2); + padding: 0rem 2.2rem; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 0.4rem 0.4rem 0 0; + } + + && > div > button { + width: 2.6rem; + height: 2.6rem; + background-color: transparent; + outline: none; + border: none; + opacity: 0.5; + cursor: pointer; + } +`; diff --git a/src/components/ProtectedPage/index.jsx b/src/components/ProtectedPage/index.jsx index 3f8f697..6d73bdd 100644 --- a/src/components/ProtectedPage/index.jsx +++ b/src/components/ProtectedPage/index.jsx @@ -1,12 +1,14 @@ -import React, { useEffect } from "react"; -import { Outlet, useNavigate } from "react-router-dom"; +import React, { useContext, useEffect } from "react"; +import { Outlet } from "react-router-dom"; +import { AuthContext } from "../../contexts/AuthContext"; -export const ProtectedRoute = ({ user }) => { - const navigate = useNavigate(); +export const ProtectedRoute = () => { + const { navigate, user } = useContext(AuthContext); useEffect(() => { if (!user) { - navigate(""); + navigate("/"); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - return <>{user && }; + return <>{user ? : Loading...}; }; diff --git a/src/components/TechList/CardTech/index.jsx b/src/components/TechList/CardTech/index.jsx new file mode 100644 index 0000000..34d5762 --- /dev/null +++ b/src/components/TechList/CardTech/index.jsx @@ -0,0 +1,54 @@ +import { StyledText, StyledTitle } from "../../../styles/typography"; +import { CardTechStyled, TrashButton } from "./style"; +import trasher from "../../../assets/img/trasher.svg"; +import { useContext } from "react"; +import { TechContext } from "../../../contexts/TechContext"; +import { useState } from "react"; + +export const CardTech = ({ techStatus, techTitle, id }) => { + const { + deleteTechnology, + setModal, + setCreateTechnology, + setIdTechnology, + setTechnologyEdit, + } = useContext(TechContext); + const [loading, setLoading] = useState(false); + + const removeTechnology = (event) => { + event.stopPropagation(); + deleteTechnology(id, setLoading); + }; + + const submit = () => { + setModal(true); + setIdTechnology(id); + setTechnologyEdit({ + title: techTitle, + status: techStatus, + }); + setCreateTechnology(false); + }; + return ( + + + {techTitle} + +
+ + {techStatus} + + removeTechnology(event)} + disabled={loading} + > + + +
+
+ ); +}; diff --git a/src/components/TechList/CardTech/style.jsx b/src/components/TechList/CardTech/style.jsx new file mode 100644 index 0000000..e2e50bb --- /dev/null +++ b/src/components/TechList/CardTech/style.jsx @@ -0,0 +1,43 @@ +import styled from "styled-components"; +export const CardTechStyled = styled.li` + min-height: 4.9rem; + background-color: var(--color-grey3); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0rem 1.218rem; + filter: brightness(0.8); + border-radius: 0.4rem; + cursor: pointer; + + &&:hover { + background-color: var(--color-grey2); + color: var(--color-grey0); + filter: brightness(1); + } + + && > div { + display: flex; + justify-content: space-between; + width: 100%; + max-width: 13rem; + gap: 2.5rem; + margin-right: 1rem; + } +`; + +export const TrashButton = styled.button` + display: flex; + align-items: center; + background-color: transparent; + outline: none; + border: none; + cursor: pointer; + + && img { + align-self: center; + object-fit: cover; + height: 1.3rem; + width: 1.2rem; + } +`; diff --git a/src/components/TechList/index.jsx b/src/components/TechList/index.jsx new file mode 100644 index 0000000..6ee1b76 --- /dev/null +++ b/src/components/TechList/index.jsx @@ -0,0 +1,23 @@ +import { useContext } from "react"; +import { AuthContext } from "../../contexts/AuthContext"; +import { CardTech } from "./CardTech"; +import { TechUl } from "./style"; + +export const TechList = () => { + const { user } = useContext(AuthContext); + return ( + + {user.techs.map((tech) => { + return ( + + ); + })} + + ); +}; diff --git a/src/components/TechList/style.jsx b/src/components/TechList/style.jsx new file mode 100644 index 0000000..eed052d --- /dev/null +++ b/src/components/TechList/style.jsx @@ -0,0 +1,16 @@ +import styled from "styled-components"; + +export const TechUl = styled.ul` + max-height: 41.637rem; + background-color: var(--color-grey3); + padding: 2.3rem 2.6rem; + border-radius: 0.286rem; + display: flex; + flex-direction: column; + gap: 1.2rem; + overflow-y: scroll; + + ::-webkit-scrollbar { + width: 0; + } +`; diff --git a/src/contexts/AuthContext.jsx b/src/contexts/AuthContext.jsx new file mode 100644 index 0000000..b736a8d --- /dev/null +++ b/src/contexts/AuthContext.jsx @@ -0,0 +1,138 @@ +import { createContext, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { api } from "../services/api"; +import { MessageToast } from "../styles/messageToast"; + +export const AuthContext = createContext({}); + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const navigate = useNavigate(); + const [globalLoading, setGlobalLoading] = useState(false); + const token = localStorage.getItem("@TOKEN"); + + useEffect(() => { + (async () => { + if (token) { + try { + setGlobalLoading(true); + const request = await api.get("profile", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (request.statusText === "OK") { + setUser(request.data); + navigate("user"); + } + } catch (error) { + localStorage.clear(); + setUser(null); + navigate(""); + } finally { + setGlobalLoading(false); + } + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const userLogin = async (data, setLoading) => { + try { + setLoading(true); + const request = await toast.promise(api.post("sessions", data), { + pending: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + success: { + render({ data }) { + return ; + }, + icon: true, + theme: "dark", + }, + error: { + render({ data }) { + return ; + }, + icon: true, + theme: "dark", + }, + }); + if (request.statusText === "OK") { + setUser(request.data.user); + + localStorage.setItem("@TOKEN", request.data.token); + localStorage.setItem("@USERID", request.data.user.id); + + navigate("user"); + } + } catch (error) { + return error; + } finally { + setLoading(false); + } + }; + + const userLogout = () => { + localStorage.clear(); + setUser(null); + navigate(""); + }; + + const userRegister = async (data, setLoading) => { + try { + setLoading(true); + const request = await toast.promise(api.post("users", data), { + pending: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + success: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + error: { + render({ data }) { + return ; + }, + icon: true, + theme: "dark", + }, + }); + navigate(""); + return request.data; + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } + }; + return ( + + {children} + + ); +}; diff --git a/src/contexts/TechContext.jsx b/src/contexts/TechContext.jsx new file mode 100644 index 0000000..fd51cdb --- /dev/null +++ b/src/contexts/TechContext.jsx @@ -0,0 +1,187 @@ +import { useEffect, useState } from "react"; +import { createContext, useContext } from "react"; +import { toast } from "react-toastify"; +import { api } from "../services/api"; +import { MessageToast } from "../styles/messageToast"; +import { AuthContext } from "./AuthContext"; + +export const TechContext = createContext({}); + +export const TechProvider = ({ children }) => { + const { token, setUser } = useContext(AuthContext); + const [modal, setModal] = useState(false); + const [createTechnology, setCreateTechnology] = useState(false); + const [idTechnology, setIdTechnology] = useState(); + const [technologyEdit, setTechnologyEdit] = useState(); + const [technology, setTechnology] = useState(); + + useEffect(() => { + const userLoad = async () => { + if (!token) { + return; + } + try { + const request = await api.get("profile", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (request.statusText === "OK") { + setUser(request.data); + } + } catch (error) { + return error; + } finally { + } + }; + userLoad(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [technology]); + + const addTechnology = async (data, setLoading) => { + try { + setLoading(true); + const request = await toast.promise( + api.post("users/techs", data, { + headers: { + Authorization: `Bearer ${token}`, + }, + }), + { + pending: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + success: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + error: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + } + ); + + if (request.statusText === "Created") { + setTechnology(request.data); + } + } catch (error) { + return error; + } finally { + setLoading(false); + } + }; + + const deleteTechnology = async (id, setLoading) => { + try { + setLoading(true); + const request = await toast.promise( + api.delete(`users/techs/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }), + { + pending: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + success: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + error: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + } + ); + setTechnology(request.data); + } catch (error) { + return error; + } finally { + setLoading(false); + } + }; + + const editTechnology = async (id, data, setLoading) => { + try { + setLoading(true); + const request = await toast.promise( + api.put(`users/techs/${id}`, data, { + headers: { + Authorization: `Bearer ${token}`, + }, + }), + { + pending: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + success: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + error: { + render() { + return ; + }, + icon: true, + theme: "dark", + }, + } + ); + setTechnology(request.data); + } catch (error) { + return error; + } finally { + setLoading(false); + } + }; + + return ( + + {children} + + ); +}; diff --git a/src/hooks/useOutClick.jsx b/src/hooks/useOutClick.jsx new file mode 100644 index 0000000..1c9b016 --- /dev/null +++ b/src/hooks/useOutClick.jsx @@ -0,0 +1,16 @@ +import { useEffect } from "react"; + +export const useOutClick = (ref, handler) => { + useEffect(() => { + const eventListener = (event) => { + if (!ref.current || ref.current.contains(event.target)) { + return; + } + handler(event); + }; + document.addEventListener("mousedown", eventListener); + return () => { + document.removeEventListener("mousedown", eventListener); + }; + }, [ref, handler]); +}; diff --git a/src/index.js b/src/index.js index 16a9879..614b54c 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ import { App } from "./App"; import { BrowserRouter } from "react-router-dom"; import Global from "./styles/global"; import { Reset } from "./styles/reset"; +import { AuthProvider } from "./contexts/AuthContext"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( @@ -12,7 +13,9 @@ root.render( - + + + ); diff --git a/src/pages/DashBoard/Header/index.jsx b/src/pages/DashBoard/Header/index.jsx index 749bf9e..40e8fc7 100644 --- a/src/pages/DashBoard/Header/index.jsx +++ b/src/pages/DashBoard/Header/index.jsx @@ -1,15 +1,17 @@ +import { useContext } from "react"; +import { AuthContext } from "../../../contexts/AuthContext"; import { Container } from "../../../styles/container"; import { StyledText, StyledTitle } from "../../../styles/typography"; import { HeaderContentContainer, HeaderStyled } from "./style"; -export const Header = ({ user }) => { - console.log(user.user.name); +export const Header = () => { + const { user } = useContext(AuthContext); return ( - Olá, {user.user.name} + Olá, {user.name} { color="#868E96" alignSelf="center" > - {user.user.course_module} + {user.course_module} diff --git a/src/pages/DashBoard/Header/style.jsx b/src/pages/DashBoard/Header/style.jsx index 8f0e803..42ba518 100644 --- a/src/pages/DashBoard/Header/style.jsx +++ b/src/pages/DashBoard/Header/style.jsx @@ -14,5 +14,5 @@ export const HeaderContentContainer = styled.div` justify-content: space-between; width: 100%; padding: 0rem 1.2rem; - gap: 1rem; + gap: 7rem; `; diff --git a/src/pages/DashBoard/Main/index.jsx b/src/pages/DashBoard/Main/index.jsx index 1d785c8..35a6e6e 100644 --- a/src/pages/DashBoard/Main/index.jsx +++ b/src/pages/DashBoard/Main/index.jsx @@ -1,18 +1,37 @@ +import { useContext } from "react"; +import { TechList } from "../../../components/TechList"; +import { TechContext } from "../../../contexts/TechContext"; +import { ButtonStyled } from "../../../styles/button"; import { Container } from "../../../styles/container"; -import { StyledText, StyledTitle } from "../../../styles/typography"; +import { StyledTitle } from "../../../styles/typography"; import { MainContentContainer, MainStyled } from "./style"; export const Main = () => { + const { setModal, setCreateTechnology } = useContext(TechContext); + + const addModal = () => { + setModal(true); + setCreateTechnology(true); + }; + return ( - - Que pena! Estamos em desenvolvimento :( - - - Nossa aplicação está em desenvolvimento, em breve teremos novidades - +
+ + Technologies + + + + + +
+
diff --git a/src/pages/DashBoard/Main/style.jsx b/src/pages/DashBoard/Main/style.jsx index a7311ae..2c990f7 100644 --- a/src/pages/DashBoard/Main/style.jsx +++ b/src/pages/DashBoard/Main/style.jsx @@ -14,4 +14,14 @@ export const MainContentContainer = styled.div` width: 100%; padding: 0rem 1.2rem; gap: 2.3rem; + + && div { + display: flex; + justify-content: space-between; + align-items: center; + } + + && ul { + + } `; diff --git a/src/pages/DashBoard/Navbar/index.jsx b/src/pages/DashBoard/Navbar/index.jsx index 44c3ba4..a875e16 100644 --- a/src/pages/DashBoard/Navbar/index.jsx +++ b/src/pages/DashBoard/Navbar/index.jsx @@ -2,8 +2,11 @@ import { ButtonStyled } from "../../../styles/button"; import { Container } from "../../../styles/container"; import { NavContentContainer, NavStyle } from "./style"; import LogoDesktop from "../../../assets/img/LogoDesktop.svg"; +import { useContext } from "react"; +import { AuthContext } from "../../../contexts/AuthContext"; -export const Navbar = ({ userLogout }) => { +export const Navbar = () => { + const { userLogout } = useContext(AuthContext); return ( diff --git a/src/pages/DashBoard/index.jsx b/src/pages/DashBoard/index.jsx index 98f6a41..ae7e5d5 100644 --- a/src/pages/DashBoard/index.jsx +++ b/src/pages/DashBoard/index.jsx @@ -1,12 +1,18 @@ +import { useContext } from "react"; +import { Modal } from "../../components/Modal"; +import { TechContext } from "../../contexts/TechContext"; import { Header } from "./Header"; import { Main } from "./Main"; import { Navbar } from "./Navbar"; -export const DashBoard = ({ userLogout, user }) => { +export const DashBoard = () => { + const { modal } = useContext(TechContext); + return ( <> - -
+ {modal && } + +
); diff --git a/src/pages/DashBoard/style.jsx b/src/pages/DashBoard/style.jsx index e69de29..c06c6bb 100644 --- a/src/pages/DashBoard/style.jsx +++ b/src/pages/DashBoard/style.jsx @@ -0,0 +1,6 @@ +import styled from "styled-components"; + +export const ContentDashBoard = styled.div` + display: flex; + flex-direction: column; +`; diff --git a/src/pages/Login/index.jsx b/src/pages/Login/index.jsx index 415aa7d..5349758 100644 --- a/src/pages/Login/index.jsx +++ b/src/pages/Login/index.jsx @@ -1,10 +1,14 @@ +import { useContext } from "react"; import LogoDesktop from "../../assets/img/LogoDesktop.svg"; import { LoginForm } from "../../components/Form/LoginForm"; +import { AuthContext } from "../../contexts/AuthContext"; import { ButtonStyled } from "../../styles/button"; import { Container } from "../../styles/container"; import { StyledText, StyledTitle } from "../../styles/typography"; import { LoginFormContainer, LoginPageContainer, LoginWrapper } from "./style"; -export const LoginPage = ({ navigate, userLogin }) => { +export const LoginPage = () => { + const { navigate } = useContext(AuthContext); + const handleRedirect = () => { navigate("/register"); }; @@ -22,20 +26,20 @@ export const LoginPage = ({ navigate, userLogin }) => { > Login - + - Ainda não possui uma conta? + Don't have an account yet? - Cadastra-se + Sign up diff --git a/src/pages/Login/style.jsx b/src/pages/Login/style.jsx index 26f6565..d950de9 100644 --- a/src/pages/Login/style.jsx +++ b/src/pages/Login/style.jsx @@ -10,6 +10,7 @@ export const LoginWrapper = styled.div` export const LoginPageContainer = styled.div` width: 36.9rem; + align-self: center; display: flex; flex-direction: column; gap: 3.6rem; diff --git a/src/pages/Register/index.jsx b/src/pages/Register/index.jsx index 882e39f..658b4d0 100644 --- a/src/pages/Register/index.jsx +++ b/src/pages/Register/index.jsx @@ -9,8 +9,11 @@ import LogoDesktop from "../../assets/img/LogoDesktop.svg"; import { RegisterForm } from "../../components/Form/RegisterForm"; import { StyledText, StyledTitle } from "../../styles/typography"; import { ButtonStyled } from "../../styles/button"; +import { useContext } from "react"; +import { AuthContext } from "../../contexts/AuthContext"; -export const RegisterPage = ({ userRegister, navigate }) => { +export const RegisterPage = () => { + const { navigate } = useContext(AuthContext); const handleReturn = () => { navigate(""); }; @@ -26,7 +29,7 @@ export const RegisterPage = ({ userRegister, navigate }) => { buttonStyle="defaultButtonSmall" onClick={handleReturn} > - Voltar + Back @@ -36,12 +39,12 @@ export const RegisterPage = ({ userRegister, navigate }) => { color="#F8F9FA" textAlign="center" > - Crie sua conta + Create your account - Rapido e grátis, vamos nessa + Fast and free, let's go! - + diff --git a/src/pages/Register/style.jsx b/src/pages/Register/style.jsx index 70c1882..34a1c96 100644 --- a/src/pages/Register/style.jsx +++ b/src/pages/Register/style.jsx @@ -10,6 +10,7 @@ export const RegisterWrapper = styled.div` export const RegisterPageContainer = styled.div` padding-top: 10rem; + height: 100vh; width: 36.9rem; display: flex; flex-direction: column; diff --git a/src/routes/index.jsx b/src/routes/index.jsx index e07df72..4d8b69c 100644 --- a/src/routes/index.jsx +++ b/src/routes/index.jsx @@ -1,33 +1,24 @@ import { Route, Routes } from "react-router-dom"; import { ProtectedRoute } from "../components/ProtectedPage"; +import { TechProvider } from "../contexts/TechContext"; import { DashBoard } from "../pages/DashBoard"; import { LoginPage } from "../pages/Login"; import { NotFound } from "../pages/NotFound/style"; import { RegisterPage } from "../pages/Register"; -export const RoutesComponent = ({ - navigate, - userLogin, - user, - userLogout, - userRegister, -}) => { +export const RoutesComponent = () => { return ( - } - /> - - } - /> - }> + } /> + } /> + }> } + element={ + + + + } /> } /> diff --git a/src/styles/button.jsx b/src/styles/button.jsx index 92435e6..469ccb3 100644 --- a/src/styles/button.jsx +++ b/src/styles/button.jsx @@ -2,6 +2,7 @@ import styled, { css } from "styled-components"; export const ButtonStyled = styled.button` font-weight: ${({ weigth }) => weigth}; + font-size: ${({ fontSize }) => fontSize}; ${({ buttonStyle }) => { switch (buttonStyle) { default: @@ -59,7 +60,6 @@ export const ButtonStyled = styled.button` `; case "defaultButtonSmall": return css` - width: 6.7rem; height: 4.8rem; padding: 0rem 1.6rem; text-align: center; @@ -73,6 +73,38 @@ export const ButtonStyled = styled.button` border: 0.1rem solid var(--color-grey1); } `; + case "buttonPlus": + return css` + width: 3.2rem; + height: 3.2rem; + /* padding: 1.1rem; */ + text-align: center; + background-color: var(--color-grey3); + color: var(--color-grey0); + border: 0.1rem solid var(--color-grey3); + border-radius: 0.4rem; + cursor: pointer; + &&:hover { + background-color: var(--color-grey1); + border: 0.1rem solid var(--color-grey1); + } + `; + case "greyButtonDelete": + return css` + height: 4.8rem; + width: 100%; + max-width: 9.8rem; + padding: 0rem 1.6rem; + text-align: center; + background-color: var(--color-grey1); + color: var(--color-grey0); + border: 0.1rem solid var(--color-grey1); + border-radius: 0.4rem; + cursor: pointer; + &&:hover { + opacity: 0.7; + } + `; } }} `;