From d878cfb1aa244dbbce7bdfa3eaae38c607c771dd Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 19 Nov 2023 18:35:04 +0800 Subject: [PATCH] Improved login and auth flow --- src/Components/API/API.tsx | 16 +-- src/Components/{Buttons => Button}/Button.tsx | 2 +- src/Components/DrawerButton/DrawerButton.tsx | 55 +++++++++ src/Components/Header/Header.tsx | 2 +- src/Components/LoginModal/LoginModal.tsx | 14 ++- .../RegisterModal/RegisterModal.tsx | 40 ++++-- src/Components/Revalidator/Revalidator.tsx | 36 ++++-- src/Components/Sidebar/Sidebar.tsx | 114 ++++++++++++++++++ src/Components/SidebarModal/SidebarModal.tsx | 65 ---------- src/Components/Types/Types.tsx | 2 + src/Pages/LandingPage/LandingPage.tsx | 2 +- 11 files changed, 249 insertions(+), 99 deletions(-) rename src/Components/{Buttons => Button}/Button.tsx (98%) create mode 100644 src/Components/DrawerButton/DrawerButton.tsx create mode 100644 src/Components/Sidebar/Sidebar.tsx delete mode 100644 src/Components/SidebarModal/SidebarModal.tsx diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 4a6a7e2..6f99637 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -43,11 +43,11 @@ export function RegisterAPI(register: RegisterType) { .post("api/v1/accounts/users/", register) .then(async (response) => { console.log(response.data); - return true; + return [true, 0]; }) - .catch(() => { + .catch((error) => { console.log("Registration failed"); - return false; + return [false, error.response]; }); } @@ -85,14 +85,10 @@ export async function JWTRefreshAPI() { }); } -export function UserAPI() { - const token = JSON.parse(localStorage.getItem("token") || "{}"); +export async function UserAPI() { + const config = await GetConfig(); return instance - .get("api/v1/accounts/users/me/", { - headers: { - Authorization: "Token " + token, - }, - }) + .get("api/v1/accounts/users/me/", config) .then((response) => { return response.data; }) diff --git a/src/Components/Buttons/Button.tsx b/src/Components/Button/Button.tsx similarity index 98% rename from src/Components/Buttons/Button.tsx rename to src/Components/Button/Button.tsx index df65188..df8a8b2 100644 --- a/src/Components/Buttons/Button.tsx +++ b/src/Components/Button/Button.tsx @@ -23,7 +23,7 @@ export default function Button(props: props) { onMouseLeave={() => setClicked(false)} style={{ borderRadius: 24, - minWidth: "50%", + minWidth: "128px", maxWidth: "128px", borderColor: colors.button_border, borderStyle: "solid", diff --git a/src/Components/DrawerButton/DrawerButton.tsx b/src/Components/DrawerButton/DrawerButton.tsx new file mode 100644 index 0000000..22fbf44 --- /dev/null +++ b/src/Components/DrawerButton/DrawerButton.tsx @@ -0,0 +1,55 @@ +import React, { useState } from "react"; +import styles from "../../styles"; +import { colors } from "../../styles"; + +export interface props { + onClick: React.MouseEventHandler; + children?: React.ReactNode; + icon?: React.ReactNode; + label: string; +} +export default function DrawerButton(props: props) { + const [clicked, setClicked] = useState(false); + return ( +
+ +
+ ); +} diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx index b650641..b36f9c4 100644 --- a/src/Components/Header/Header.tsx +++ b/src/Components/Header/Header.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import styles, { colors } from "../../styles"; import MenuIcon from "@mui/icons-material/Menu"; -import SidebarModal from "../SidebarModal/SidebarModal"; +import SidebarModal from "../Sidebar/Sidebar"; import { Drawer } from "@mui/material"; export interface props { label: string; diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx index e570756..50a8507 100644 --- a/src/Components/LoginModal/LoginModal.tsx +++ b/src/Components/LoginModal/LoginModal.tsx @@ -8,11 +8,12 @@ import Visibility from "@mui/icons-material/Visibility"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import LoginIcon from "@mui/icons-material/Login"; import Checkbox from "@mui/material/Checkbox"; -import Button from "../Buttons/Button"; +import Button from "../Button/Button"; import { useNavigate } from "react-router-dom"; import { LoginAPI } from "../API/API"; import { useDispatch } from "react-redux"; import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; +import { toast } from "react-toastify"; export default function LoginModal() { const navigate = useNavigate(); const [showPassword, setShowPassword] = useState(false); @@ -129,8 +130,17 @@ export default function LoginModal() { const status = await LoginAPI(user, remember_session); if (status === true) { await dispatch(auth_toggle()); - navigate("/dashboard"); + toast("Logged in", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); } else { setError("Invalid login"); } diff --git a/src/Components/RegisterModal/RegisterModal.tsx b/src/Components/RegisterModal/RegisterModal.tsx index 80157e7..fad4327 100644 --- a/src/Components/RegisterModal/RegisterModal.tsx +++ b/src/Components/RegisterModal/RegisterModal.tsx @@ -7,11 +7,13 @@ import IconButton from "@mui/material/IconButton"; import Visibility from "@mui/icons-material/Visibility"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import { AppRegistration } from "@mui/icons-material"; -import Button from "../Buttons/Button"; +import Button from "../Button/Button"; import { useNavigate } from "react-router-dom"; +import { RegisterAPI } from "../API/API"; export default function RegisterModal() { const navigate = useNavigate(); const [showPassword, setShowPassword] = useState(false); + const [user, setUser] = useState({ first_name: "", last_name: "", @@ -20,6 +22,7 @@ export default function RegisterModal() { password: "", confirm_password: "", }); + const [error, setError] = useState(""); return ( <>
) => - setUser({ ...user, first_name: e.target.value }) - } + onChange={(e: React.ChangeEvent) => { + setUser({ ...user, first_name: e.target.value }); + setError(""); + }} value={user.first_name} placeholder={"Enter your first name"} /> @@ -63,9 +67,10 @@ export default function RegisterModal() { id="outlined-helperText" label="Username" style={styles.input_form} - onChange={(e: React.ChangeEvent) => - setUser({ ...user, username: e.target.value }) - } + onChange={(e: React.ChangeEvent) => { + setUser({ ...user, username: e.target.value }); + setError(""); + }} value={user.username} placeholder={"Enter username"} /> @@ -112,12 +117,14 @@ export default function RegisterModal() { }} label="Confirm Password" placeholder={"Re-enter password"} - onChange={(e: React.ChangeEvent) => - setUser({ ...user, confirm_password: e.target.value }) - } + onChange={(e: React.ChangeEvent) => { + setUser({ ...user, confirm_password: e.target.value }); + setError(""); + }} value={user.confirm_password} />
+

{error}

{ - navigate(0); + onClick={async () => { + if (user.password !== user.confirm_password) { + setError("Passwords do not match"); + } else { + const status = await RegisterAPI(user); + if (status[0]) { + navigate("/"); + } else { + setError(status[1]); + } + } }} /> diff --git a/src/Components/Revalidator/Revalidator.tsx b/src/Components/Revalidator/Revalidator.tsx index 5ef104b..fc37f5d 100644 --- a/src/Components/Revalidator/Revalidator.tsx +++ b/src/Components/Revalidator/Revalidator.tsx @@ -1,22 +1,35 @@ import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import { JWTRefreshAPI, setAccessToken, setRefreshToken } from "../API/API"; import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; import { RootState } from "../Plugins/Redux/Store/Store"; +import { toast } from "react-toastify"; export default function Revalidator() { const dispatch = useDispatch(); const navigate = useNavigate(); + const location = useLocation(); const authenticated = useSelector((state: RootState) => state.auth.value); const [rechecked, setRechecked] = useState(false); useEffect(() => { if (!authenticated && rechecked) { - navigate("/"); - console.log("Not logged in"); + if (location.pathname !== "/") { + navigate("/"); + toast("Please log in to continue", { + position: "bottom-center", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + } } - }, [authenticated, navigate, rechecked]); + }, [authenticated, location.pathname, navigate, rechecked]); useEffect(() => { if (!authenticated) { @@ -24,16 +37,25 @@ export default function Revalidator() { if (response) { await dispatch(auth_toggle()); navigate("/dashboard"); - console.log("User session restored"); + toast("User session restored", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); } else { await setRefreshToken(""); await setAccessToken(""); - console.log("User session expired"); } setRechecked(true); }); } - }, [authenticated, dispatch, navigate]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return <>; } diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..3463d9b --- /dev/null +++ b/src/Components/Sidebar/Sidebar.tsx @@ -0,0 +1,114 @@ +import styles, { colors } from "../../styles"; +import AccountCircleIcon from "@mui/icons-material/AccountCircle"; +import HomeIcon from "@mui/icons-material/Home"; +import LogoutIcon from "@mui/icons-material/Logout"; +import { useQuery } from "@tanstack/react-query"; +import { UserAPI, setAccessToken, setRefreshToken } from "../API/API"; +import DrawerButton from "../DrawerButton/DrawerButton"; +import { useDispatch } from "react-redux"; +import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; +import { toast } from "react-toastify"; +import { useNavigate } from "react-router-dom"; +export default function Sidebar() { + const user = useQuery({ queryKey: ["user"], queryFn: UserAPI }); + const dispatch = useDispatch(); + const navigate = useNavigate(); + return ( +
+
+ +

+ {user.data + ? user.data.username + : user.isError + ? "Error loading user" + : "Loading user..."} +

+
+
+
+ +

+ Dashboard +

+
+
+ { + navigate("/"); + await dispatch(auth_toggle()); + await setAccessToken(""); + await setRefreshToken(""); + toast("Logged out", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + }} + icon={ + + } + label={"Log out"} + /> +
+
+ ); +} diff --git a/src/Components/SidebarModal/SidebarModal.tsx b/src/Components/SidebarModal/SidebarModal.tsx deleted file mode 100644 index 0a2d4ed..0000000 --- a/src/Components/SidebarModal/SidebarModal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import styles, { colors } from "../../styles"; -import AccountCircleIcon from "@mui/icons-material/AccountCircle"; -import HomeIcon from "@mui/icons-material/Home"; -export default function SidebarModal() { - return ( -
-
- -

- Placeholder Name -

-
-
-
- -

- Dashboard -

-
-
- ); -} diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index de6f952..784a3b6 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -2,6 +2,8 @@ export type RegisterType = { email: string; username: string; password: string; + first_name: string; + last_name: string; }; export type LoginType = { diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx index 86ce33d..ea0ac88 100644 --- a/src/Pages/LandingPage/LandingPage.tsx +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -1,4 +1,4 @@ -import Button from "../../Components/Buttons/Button"; +import Button from "../../Components/Button/Button"; import styles from "../../styles"; import citc_logo from "../../assets/citc_logo.jpg"; import Popup from "reactjs-popup";