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)
|
.post("api/v1/accounts/users/", register)
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
console.log(response.data);
|
console.log(response.data);
|
||||||
return true;
|
return [true, 0];
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
console.log("Registration failed");
|
console.log("Registration failed");
|
||||||
return false;
|
return [false, error.response];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,14 +85,10 @@ export async function JWTRefreshAPI() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserAPI() {
|
export async function UserAPI() {
|
||||||
const token = JSON.parse(localStorage.getItem("token") || "{}");
|
const config = await GetConfig();
|
||||||
return instance
|
return instance
|
||||||
.get("api/v1/accounts/users/me/", {
|
.get("api/v1/accounts/users/me/", config)
|
||||||
headers: {
|
|
||||||
Authorization: "Token " + token,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.data;
|
return response.data;
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default function Button(props: props) {
|
||||||
onMouseLeave={() => setClicked(false)}
|
onMouseLeave={() => setClicked(false)}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 24,
|
borderRadius: 24,
|
||||||
minWidth: "50%",
|
minWidth: "128px",
|
||||||
maxWidth: "128px",
|
maxWidth: "128px",
|
||||||
borderColor: colors.button_border,
|
borderColor: colors.button_border,
|
||||||
borderStyle: "solid",
|
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 { useState } from "react";
|
||||||
import styles, { colors } from "../../styles";
|
import styles, { colors } from "../../styles";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import SidebarModal from "../SidebarModal/SidebarModal";
|
import SidebarModal from "../Sidebar/Sidebar";
|
||||||
import { Drawer } from "@mui/material";
|
import { Drawer } from "@mui/material";
|
||||||
export interface props {
|
export interface props {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Visibility from "@mui/icons-material/Visibility";
|
||||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||||
import LoginIcon from "@mui/icons-material/Login";
|
import LoginIcon from "@mui/icons-material/Login";
|
||||||
import Checkbox from "@mui/material/Checkbox";
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
import Button from "../Buttons/Button";
|
import Button from "../Button/Button";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { LoginAPI } from "../API/API";
|
import { LoginAPI } from "../API/API";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
export default function LoginModal() {
|
export default function LoginModal() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
@ -129,8 +130,17 @@ export default function LoginModal() {
|
||||||
const status = await LoginAPI(user, remember_session);
|
const status = await LoginAPI(user, remember_session);
|
||||||
if (status === true) {
|
if (status === true) {
|
||||||
await dispatch(auth_toggle());
|
await dispatch(auth_toggle());
|
||||||
|
|
||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
|
toast("Logged in", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setError("Invalid login");
|
setError("Invalid login");
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,13 @@ import IconButton from "@mui/material/IconButton";
|
||||||
import Visibility from "@mui/icons-material/Visibility";
|
import Visibility from "@mui/icons-material/Visibility";
|
||||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||||
import { AppRegistration } from "@mui/icons-material";
|
import { AppRegistration } from "@mui/icons-material";
|
||||||
import Button from "../Buttons/Button";
|
import Button from "../Button/Button";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { RegisterAPI } from "../API/API";
|
||||||
export default function RegisterModal() {
|
export default function RegisterModal() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
const [user, setUser] = useState({
|
const [user, setUser] = useState({
|
||||||
first_name: "",
|
first_name: "",
|
||||||
last_name: "",
|
last_name: "",
|
||||||
|
@ -20,6 +22,7 @@ export default function RegisterModal() {
|
||||||
password: "",
|
password: "",
|
||||||
confirm_password: "",
|
confirm_password: "",
|
||||||
});
|
});
|
||||||
|
const [error, setError] = useState("");
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
@ -43,9 +46,10 @@ export default function RegisterModal() {
|
||||||
id="outlined-helperText"
|
id="outlined-helperText"
|
||||||
label="First Name"
|
label="First Name"
|
||||||
style={styles.input_form}
|
style={styles.input_form}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setUser({ ...user, first_name: e.target.value })
|
setUser({ ...user, first_name: e.target.value });
|
||||||
}
|
setError("");
|
||||||
|
}}
|
||||||
value={user.first_name}
|
value={user.first_name}
|
||||||
placeholder={"Enter your first name"}
|
placeholder={"Enter your first name"}
|
||||||
/>
|
/>
|
||||||
|
@ -63,9 +67,10 @@ export default function RegisterModal() {
|
||||||
id="outlined-helperText"
|
id="outlined-helperText"
|
||||||
label="Username"
|
label="Username"
|
||||||
style={styles.input_form}
|
style={styles.input_form}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setUser({ ...user, username: e.target.value })
|
setUser({ ...user, username: e.target.value });
|
||||||
}
|
setError("");
|
||||||
|
}}
|
||||||
value={user.username}
|
value={user.username}
|
||||||
placeholder={"Enter username"}
|
placeholder={"Enter username"}
|
||||||
/>
|
/>
|
||||||
|
@ -112,12 +117,14 @@ export default function RegisterModal() {
|
||||||
}}
|
}}
|
||||||
label="Confirm Password"
|
label="Confirm Password"
|
||||||
placeholder={"Re-enter password"}
|
placeholder={"Re-enter password"}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setUser({ ...user, confirm_password: e.target.value })
|
setUser({ ...user, confirm_password: e.target.value });
|
||||||
}
|
setError("");
|
||||||
|
}}
|
||||||
value={user.confirm_password}
|
value={user.confirm_password}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{error}</p>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.button_border,
|
backgroundColor: colors.button_border,
|
||||||
|
@ -130,8 +137,17 @@ export default function RegisterModal() {
|
||||||
<Button
|
<Button
|
||||||
type={"dark"}
|
type={"dark"}
|
||||||
label={"Register"}
|
label={"Register"}
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
navigate(0);
|
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 { useEffect, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
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 { JWTRefreshAPI, setAccessToken, setRefreshToken } from "../API/API";
|
||||||
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
||||||
import { RootState } from "../Plugins/Redux/Store/Store";
|
import { RootState } from "../Plugins/Redux/Store/Store";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
export default function Revalidator() {
|
export default function Revalidator() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const authenticated = useSelector((state: RootState) => state.auth.value);
|
const authenticated = useSelector((state: RootState) => state.auth.value);
|
||||||
const [rechecked, setRechecked] = useState(false);
|
const [rechecked, setRechecked] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authenticated && rechecked) {
|
if (!authenticated && rechecked) {
|
||||||
navigate("/");
|
if (location.pathname !== "/") {
|
||||||
console.log("Not logged in");
|
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(() => {
|
useEffect(() => {
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
|
@ -24,16 +37,25 @@ export default function Revalidator() {
|
||||||
if (response) {
|
if (response) {
|
||||||
await dispatch(auth_toggle());
|
await dispatch(auth_toggle());
|
||||||
navigate("/dashboard");
|
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 {
|
} else {
|
||||||
await setRefreshToken("");
|
await setRefreshToken("");
|
||||||
await setAccessToken("");
|
await setAccessToken("");
|
||||||
console.log("User session expired");
|
|
||||||
}
|
}
|
||||||
setRechecked(true);
|
setRechecked(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [authenticated, dispatch, navigate]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
return <></>;
|
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;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoginType = {
|
export type LoginType = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Button from "../../Components/Buttons/Button";
|
import Button from "../../Components/Button/Button";
|
||||||
import styles from "../../styles";
|
import styles from "../../styles";
|
||||||
import citc_logo from "../../assets/citc_logo.jpg";
|
import citc_logo from "../../assets/citc_logo.jpg";
|
||||||
import Popup from "reactjs-popup";
|
import Popup from "reactjs-popup";
|
||||||
|
|
Loading…
Reference in a new issue