diff --git a/client/src/components/auth/otp/OtpForm.tsx b/client/src/components/auth/otp/OtpForm.tsx index ee6592f..6735b33 100644 --- a/client/src/components/auth/otp/OtpForm.tsx +++ b/client/src/components/auth/otp/OtpForm.tsx @@ -19,7 +19,7 @@ const OtpForm = () => { email: auth.user?.email, }; - APIHandler("/auth/send-otp", false, "post", body).then( + APIHandler("/auth/send-otp", false, "POST", body).then( (res) => { toast.success(res.data.message, { style: { @@ -42,7 +42,7 @@ const OtpForm = () => { otp: values.otp, }; - APIHandler("/auth/verify-otp", false, "post", body).then( + APIHandler("/auth/verify-otp", false, "POST", body).then( (res) => { toast.success(res.data.message, { style: { diff --git a/client/src/components/auth/reset-password/ForgetPasswordForm.tsx b/client/src/components/auth/reset-password/ForgetPasswordForm.tsx index 8bd399e..b82052d 100644 --- a/client/src/components/auth/reset-password/ForgetPasswordForm.tsx +++ b/client/src/components/auth/reset-password/ForgetPasswordForm.tsx @@ -20,7 +20,7 @@ const ForgetPasswordForm = () => { APIHandler( "/auth/forget-password", false, - "post", + "POST", body ).then((res) => { toast.success(res.data.message, { diff --git a/client/src/components/auth/reset-password/ResetPasswordForm.tsx b/client/src/components/auth/reset-password/ResetPasswordForm.tsx index 94f08e1..2d6a583 100644 --- a/client/src/components/auth/reset-password/ResetPasswordForm.tsx +++ b/client/src/components/auth/reset-password/ResetPasswordForm.tsx @@ -36,7 +36,7 @@ const ResetPasswordForm = () => { APIHandler( `/auth/reset-password/${token}`, false, - "post", + "POST", resetPasswordBody ).then((res) => { toast.success(res.data.message, { diff --git a/client/src/components/profile/UpdateForm.tsx b/client/src/components/profile/UpdateForm.tsx index 58af2eb..f786e22 100644 --- a/client/src/components/profile/UpdateForm.tsx +++ b/client/src/components/profile/UpdateForm.tsx @@ -6,15 +6,15 @@ import { Button } from "../ui"; import { useAuth } from "../../hooks/useAuth"; import { User } from "../../interfaces/user.interface"; import { APIHandler } from "../../utils/api/api-handler"; +import { UpdateAccountBody } from "../../interfaces/api-body/update-account-body"; +import { AuthResponse } from "../../interfaces/api-response/auth-reponse"; +import toast from "react-hot-toast"; +import { useNavigate } from "react-router"; const validationSchema = Yup.object().shape({ - name: Yup.string().required("Le nom est obligatoire.").trim(), - email: Yup.string() - .email("Le format de l'e-mail est incorrect.") - .required("L'e-mail est obligatoire.") - .trim(), + name: Yup.string().trim(), + email: Yup.string().email("Le format de l'e-mail est incorrect.").trim(), password: Yup.string() - .required("Le mot de passe est obligatoire.") .matches( /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, "Le mot de passe doit contenir au moins huit caractères, une lettre majuscule, une lettre minuscule, un chiffre et un caractère special." @@ -22,26 +22,25 @@ const validationSchema = Yup.object().shape({ .trim(), confirmPassword: Yup.string() .oneOf([Yup.ref("password")], "Le mot de passe ne correspond pas.") - .required("La confirmation du mot de passe est obligatoire.") .trim(), }); const UpdateForm = () => { - const { token } = useAuth(); + const { token, setToken } = useAuth(); const [user, setUser] = useState(); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const navigate = useNavigate(); + const getAccount = useCallback(() => { - APIHandler("/my/profile", false, "get", undefined, token).then( + APIHandler("/my/profile", false, "GET", undefined, token).then( (res) => { setUser(res.data); } ); }, [token]); - console.log(user); - useEffect(() => { getAccount(); }, [getAccount]); @@ -57,11 +56,34 @@ const UpdateForm = () => { }} validationSchema={validationSchema} onSubmit={(values, actions) => { - console.log(values); + const body: UpdateAccountBody = { + email: values.email, + name: values.name, + password: values.password, + }; + console.log(token, body); + APIHandler( + "/my/account", + false, + "PATCH", + body, + token + ).then((res) => { + setToken(res.data.token); + localStorage.setItem("token", res.data.token); + toast.success(res.data.message, { + style: { + background: "#3D3D3D", + color: "#FAFAFA", + borderRadius: "12px", + }, + }); + navigate("/profile"); + }); actions.setSubmitting(false); }} > - {({ errors, touched, isValid, dirty }) => ( + {({ errors, touched }) => (
@@ -229,11 +251,7 @@ const UpdateForm = () => {
-
diff --git a/client/src/components/topbar/TopBar.tsx b/client/src/components/topbar/TopBar.tsx index fd641ea..e8e6fc3 100644 --- a/client/src/components/topbar/TopBar.tsx +++ b/client/src/components/topbar/TopBar.tsx @@ -4,13 +4,15 @@ import { useNavigate } from "react-router"; const TopBar = ({ title, hasReturn, + prevPage, }: { title: string; hasReturn: boolean; + prevPage: string; }) => { const navigate = useNavigate(); const handlePrevPage = () => { - navigate(-1); + navigate(prevPage); }; return (
diff --git a/client/src/components/ui/link.tsx b/client/src/components/ui/link.tsx index 1340846..304294f 100644 --- a/client/src/components/ui/link.tsx +++ b/client/src/components/ui/link.tsx @@ -11,6 +11,8 @@ const linksVariants = { secondary: "bg-alabaster-950 text-alabaster-50 rounded-xl shadow-secondary border-3 border-alabaster-400 active:shadow-none active:translate-y-1 hover:brightness-90", tertiary: "text-mandy-500 hover:text-mandy-600", + disabled: + "rounded-xl bg-alabaster-600 text-alabaster-300 shadow-none hover:brightness-100 active:translate-y-0 pointer-events-none", }; const link = cva(baseLink, { diff --git a/client/src/context/authContextProvider.tsx b/client/src/context/authContextProvider.tsx index c488370..fb68b22 100644 --- a/client/src/context/authContextProvider.tsx +++ b/client/src/context/authContextProvider.tsx @@ -27,7 +27,7 @@ export const AuthContextProvider = ({ const signin = useCallback( (body: SigninBody) => { - APIHandler("/auth/signin", false, "post", body) + APIHandler("/auth/signin", false, "POST", body) .then((res) => { setToken(res.data.token); localStorage.setItem("token", res.data.token); @@ -61,10 +61,10 @@ export const AuthContextProvider = ({ }; const signup = (body: SignupBody) => { - APIHandler("/auth/signup", false, "post", body) + APIHandler("/auth/signup", false, "POST", body) .then(() => { navigate("/otp"); - APIHandler("/auth/send-otp", false, "post", body).then( + APIHandler("/auth/send-otp", false, "POST", body).then( (res) => { setUser({ name: body.name, @@ -93,7 +93,7 @@ export const AuthContextProvider = ({ APIHandler( "/auth/token", false, - "get", + "GET", undefined, token ) @@ -114,7 +114,7 @@ export const AuthContextProvider = ({ }, 5 * 60 * 1000); return () => clearInterval(interval); - }, [refreshToken]); + }); const authContextValue: AuthContextType = { signin, diff --git a/client/src/interfaces/api-body/update-account-body.ts b/client/src/interfaces/api-body/update-account-body.ts new file mode 100644 index 0000000..d73c3f1 --- /dev/null +++ b/client/src/interfaces/api-body/update-account-body.ts @@ -0,0 +1,5 @@ +export interface UpdateAccountBody { + name?: string; + email?: string; + password?: string; +} diff --git a/client/src/pages/profile/AccountPage.tsx b/client/src/pages/profile/AccountPage.tsx index 6b9deda..8fc3ae9 100644 --- a/client/src/pages/profile/AccountPage.tsx +++ b/client/src/pages/profile/AccountPage.tsx @@ -3,8 +3,8 @@ import TopBar from "../../components/topbar/TopBar"; const AccountPage = () => { return ( -
- +
+
); diff --git a/client/src/pages/profile/ProfilePage.tsx b/client/src/pages/profile/ProfilePage.tsx index cc20692..f453bf5 100644 --- a/client/src/pages/profile/ProfilePage.tsx +++ b/client/src/pages/profile/ProfilePage.tsx @@ -12,7 +12,7 @@ const ProfilePage = () => { const [user, setUser] = useState(); const getUser = useCallback(() => { - APIHandler("/my/profile", false, "get", undefined, token).then( + APIHandler("/my/profile", false, "GET", undefined, token).then( (res) => { setUser(res.data); } @@ -22,10 +22,11 @@ const ProfilePage = () => { useEffect(() => { getUser(); }, [getUser]); + console.log(user?.badges); return (
- +
{user && (
@@ -46,10 +47,20 @@ const ProfilePage = () => {

Mes badges

- { - user && user.badges && user.badges.map(badge => ) - } + {user && user.badges?.length !== 0 ? ( + user.badges?.map((badge) => ( + + )) + ) : ( +

Tu n'as pas de badges !

+ )}
+ + Voir tous mes badges +
); diff --git a/client/src/utils/api/api-handler.ts b/client/src/utils/api/api-handler.ts index a4f95d6..771195c 100644 --- a/client/src/utils/api/api-handler.ts +++ b/client/src/utils/api/api-handler.ts @@ -2,7 +2,7 @@ import { ApiResponse } from "../../interfaces/api-response/api-response"; import { ErrorResponse } from "../../interfaces/api-response/error-response"; import toast from "react-hot-toast"; -type HTTPMethod = "get" | "post" | "patch"; +type HTTPMethod = "GET" | "POST" | "PATCH"; const apiURLFlami = "http://localhost:3001/api"; const apiURLMap = "https://maksance.alwaysdata.net/api-jo"; @@ -10,13 +10,13 @@ const apiURLMap = "https://maksance.alwaysdata.net/api-jo"; export const APIHandler = ( endpoint: string, isMap: boolean = false, - method: HTTPMethod = "get", + method: HTTPMethod = "GET", body: unknown = undefined, token: string | null = null ): Promise> => { const headers = new Headers(); const url = isMap ? apiURLMap : apiURLFlami; - if (method === "post") { + if (method === "POST" || method === "PATCH") { headers.append("Content-Type", "application/json"); } if (token) { diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index ea1ed86..49ed527 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -6,24 +6,26 @@ import { readFile } from "fs/promises"; const userController = { getProfile: async (req, res) => { let userdata = res.locals.user; - let content = await readFile('./data/badges.json', { encoding: "utf8" }); + let content = await readFile("./data/badges.json", { encoding: "utf8" }); let json = JSON.parse(content); return res.status(200).json({ data: { name: userdata.name, email: userdata.email, - badges: userdata.badges.slice(Math.max(0, userdata.badges.length - 3)).map(id => json[id] ?? json[0]), + badges: userdata.badges + .slice(Math.max(0, userdata.badges.length - 3)) + .map((id) => json[id] ?? json[0]), created_at: new Date(userdata.date).toDateString(), }, }); }, getBadges: async (req, res) => { let userdata = res.locals.user; - let content = await readFile('./data/badges.json', { encoding: "utf8" }); + let content = await readFile("./data/badges.json", { encoding: "utf8" }); let json = JSON.parse(content); return res.status(200).json({ data: { - badges: userdata.badges.map(id => json[id] ?? json[0]), + badges: userdata.badges.map((id) => json[id] ?? json[0]), }, }); }, @@ -33,7 +35,12 @@ const userController = { let patch = {}; - if (password && String(password).match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/)) + if ( + password && + String(password).match( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/ + ) + ) patch.password = bcrypt.hashSync(password, bcrypt.genSaltSync(11)); if (name) patch.name = name; if (email) patch.email = email;