mirror of
https://github.com/lemeow125/Borrowing-TrackerFrontend.git
synced 2024-11-17 06:19:27 +08:00
Improved login and auth flow
This commit is contained in:
parent
99cd673b12
commit
d878cfb1aa
11 changed files with 249 additions and 99 deletions
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -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",
|
55
src/Components/DrawerButton/DrawerButton.tsx
Normal file
55
src/Components/DrawerButton/DrawerButton.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import React, { useState } from "react";
|
||||
import styles from "../../styles";
|
||||
import { colors } from "../../styles";
|
||||
|
||||
export interface props {
|
||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||
children?: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
label: string;
|
||||
}
|
||||
export default function DrawerButton(props: props) {
|
||||
const [clicked, setClicked] = useState(false);
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
onMouseDown={() => {
|
||||
if (!clicked) {
|
||||
setClicked(!clicked);
|
||||
}
|
||||
}}
|
||||
onMouseUp={() => setClicked(false)}
|
||||
onMouseLeave={() => setClicked(false)}
|
||||
style={{
|
||||
borderRadius: 24,
|
||||
minWidth: "128px",
|
||||
maxWidth: "128px",
|
||||
borderColor: colors.button_border,
|
||||
borderStyle: "solid",
|
||||
borderWidth: "2px",
|
||||
paddingBottom: 0,
|
||||
paddingTop: 0,
|
||||
paddingRight: "4px",
|
||||
paddingLeft: "4px",
|
||||
marginBottom: "4px",
|
||||
marginTop: "4px",
|
||||
backgroundColor: clicked ? colors.button_light : colors.button_dark,
|
||||
}}
|
||||
>
|
||||
<div style={styles.flex_row}>
|
||||
{clicked ? <></> : props.icon}
|
||||
<p
|
||||
style={{
|
||||
...(clicked ? styles.text_dark : styles.text_light),
|
||||
...styles.text_M,
|
||||
...{ marginLeft: "4px" },
|
||||
}}
|
||||
>
|
||||
{props.label}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<div
|
||||
|
@ -43,9 +46,10 @@ export default function RegisterModal() {
|
|||
id="outlined-helperText"
|
||||
label="First Name"
|
||||
style={styles.input_form}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setUser({ ...user, first_name: e.target.value })
|
||||
}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) =>
|
||||
setUser({ ...user, username: e.target.value })
|
||||
}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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<HTMLInputElement>) =>
|
||||
setUser({ ...user, confirm_password: e.target.value })
|
||||
}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUser({ ...user, confirm_password: e.target.value });
|
||||
setError("");
|
||||
}}
|
||||
value={user.confirm_password}
|
||||
/>
|
||||
</div>
|
||||
<p style={{ ...styles.text_dark, ...styles.text_M }}>{error}</p>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: colors.button_border,
|
||||
|
@ -130,8 +137,17 @@ export default function RegisterModal() {
|
|||
<Button
|
||||
type={"dark"}
|
||||
label={"Register"}
|
||||
onClick={() => {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -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 <></>;
|
||||
}
|
||||
|
|
114
src/Components/Sidebar/Sidebar.tsx
Normal file
114
src/Components/Sidebar/Sidebar.tsx
Normal file
|
@ -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 (
|
||||
<div
|
||||
style={{
|
||||
width: "256px",
|
||||
height: "100%",
|
||||
padding: 16,
|
||||
alignContent: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: colors.header_color,
|
||||
}}
|
||||
>
|
||||
<div style={styles.flex_row}>
|
||||
<AccountCircleIcon
|
||||
style={{
|
||||
width: "48px",
|
||||
height: "48px",
|
||||
color: "white",
|
||||
marginRight: "4px",
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
...styles.text_light,
|
||||
...styles.text_S,
|
||||
...{ alignSelf: "center" },
|
||||
}}
|
||||
>
|
||||
{user.data
|
||||
? user.data.username
|
||||
: user.isError
|
||||
? "Error loading user"
|
||||
: "Loading user..."}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
marginTop: "16px",
|
||||
width: "100%",
|
||||
height: "2px",
|
||||
marginBottom: 8,
|
||||
}}
|
||||
/>
|
||||
<div style={styles.flex_row}>
|
||||
<HomeIcon
|
||||
style={{
|
||||
width: "48px",
|
||||
height: "48px",
|
||||
color: "white",
|
||||
marginRight: "2px",
|
||||
alignSelf: "center",
|
||||
justifySelf: "center",
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
...styles.text_light,
|
||||
...styles.text_M,
|
||||
}}
|
||||
>
|
||||
Dashboard
|
||||
</p>
|
||||
</div>
|
||||
<div style={styles.flex_row}>
|
||||
<DrawerButton
|
||||
onClick={async () => {
|
||||
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={
|
||||
<LogoutIcon
|
||||
style={{
|
||||
width: "48px",
|
||||
height: "48px",
|
||||
color: "white",
|
||||
marginRight: "2px",
|
||||
alignSelf: "center",
|
||||
justifySelf: "center",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={"Log out"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<div
|
||||
style={{
|
||||
width: "256px",
|
||||
height: "100%",
|
||||
padding: 16,
|
||||
alignContent: "center",
|
||||
justifyContent: "center",
|
||||
textAlign: "center",
|
||||
backgroundColor: colors.header_color,
|
||||
}}
|
||||
>
|
||||
<div style={styles.flex_row}>
|
||||
<AccountCircleIcon
|
||||
style={{
|
||||
width: "48px",
|
||||
height: "px",
|
||||
color: "white",
|
||||
marginRight: "4px",
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
...styles.text_light,
|
||||
...styles.text_S,
|
||||
...{ alignSelf: "center" },
|
||||
}}
|
||||
>
|
||||
Placeholder Name
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
marginTop: "16px",
|
||||
width: "100%",
|
||||
height: "2px",
|
||||
marginBottom: 8,
|
||||
}}
|
||||
/>
|
||||
<div style={styles.flex_row}>
|
||||
<HomeIcon
|
||||
style={{
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
color: "white",
|
||||
marginRight: "2px",
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
...styles.text_light,
|
||||
...styles.text_M,
|
||||
}}
|
||||
>
|
||||
Dashboard
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -2,6 +2,8 @@ export type RegisterType = {
|
|||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
};
|
||||
|
||||
export type LoginType = {
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue