login and profile page implemented
This commit is contained in:
parent
9af9818f9a
commit
e50b99641c
2
.env
2
.env
@ -1 +1 @@
|
|||||||
VITE_API_URL=http://127.0.0.1:8000
|
VITE_API_URL=https://gallery.steve-dekart.xyz
|
||||||
@ -1,18 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
type LoginPayload,
|
type LoginPayload,
|
||||||
type LoginResponse,
|
type LoginResponse,
|
||||||
type MeResponse,
|
type User,
|
||||||
} from "../components/shared/types/AuthTypes";
|
} from "../components/shared/types/AuthTypes";
|
||||||
import axiosInstance from "./axiosInstance";
|
import axiosInstance from "./axiosInstance";
|
||||||
|
|
||||||
export const loginRequest = async (data: LoginPayload) => {
|
export const loginRequest = async (data: LoginPayload) => {
|
||||||
const res = await axiosInstance.post<LoginResponse>("/api/auth/login", data);
|
const res = await axiosInstance.post<LoginResponse>("/api/auth/login/", data);
|
||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchMe = async () => {
|
export const fetchMe = async () => {
|
||||||
const res = await axiosInstance.get<MeResponse>("/api/auth/me");
|
const res = await axiosInstance.get<User>("/api/auth/me/");
|
||||||
return res.data.user;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const axiosInstance = axios.create({
|
|||||||
axiosInstance.interceptors.request.use(
|
axiosInstance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = localStorage.getItem("access_token");
|
const token = localStorage.getItem("access_token");
|
||||||
if (token) config.headers.Authorization = `Bearer ${token}`;
|
if (token) config.headers.Authorization = `Token ${token}`;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,10 +11,13 @@ import MenuItem from "@mui/material/MenuItem";
|
|||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Container } from "@mui/material";
|
import { Container } from "@mui/material";
|
||||||
import CameraIcon from '@mui/icons-material/Camera';
|
import CameraIcon from "@mui/icons-material/Camera";
|
||||||
|
import appColors from "../../utils/colors";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(
|
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(
|
||||||
null
|
null
|
||||||
@ -31,7 +34,7 @@ function Header() {
|
|||||||
const goToAdmin = () => navigate("/admin");
|
const goToAdmin = () => navigate("/admin");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar sx={{ backgroundColor: "#008080" }} position="static">
|
<AppBar sx={{ backgroundColor: appColors.primary }} position="static">
|
||||||
<Container>
|
<Container>
|
||||||
<Toolbar disableGutters>
|
<Toolbar disableGutters>
|
||||||
<CameraIcon sx={{ display: { xs: "none", md: "flex" }, mr: 1 }} />
|
<CameraIcon sx={{ display: { xs: "none", md: "flex" }, mr: 1 }} />
|
||||||
@ -74,7 +77,7 @@ function Header() {
|
|||||||
onClose={handleCloseNavMenu}
|
onClose={handleCloseNavMenu}
|
||||||
sx={{ display: { xs: "block", md: "none" } }}
|
sx={{ display: { xs: "block", md: "none" } }}
|
||||||
>
|
>
|
||||||
|
{!user && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleCloseNavMenu();
|
handleCloseNavMenu();
|
||||||
@ -83,9 +86,10 @@ function Header() {
|
|||||||
>
|
>
|
||||||
<Typography textAlign="center">Login</Typography>
|
<Typography textAlign="center">Login</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
|
{user && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleCloseNavMenu();
|
handleCloseNavMenu();
|
||||||
@ -94,20 +98,19 @@ function Header() {
|
|||||||
>
|
>
|
||||||
<Typography textAlign="center">Profile</Typography>
|
<Typography textAlign="center">Profile</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
)}
|
||||||
<MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
handleCloseNavMenu();
|
|
||||||
goToAdmin();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography textAlign="center">
|
|
||||||
Admin Dashboard
|
|
||||||
</Typography>
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
|
|
||||||
|
{user && user.is_superuser && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleCloseNavMenu();
|
||||||
|
goToAdmin();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography textAlign="center">Admin Dashboard</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -132,33 +135,32 @@ function Header() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
|
<Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
|
||||||
|
{!user && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate("/login")}
|
onClick={() => navigate("/login")}
|
||||||
sx={{ my: 2, color: "white", display: "block" }}
|
sx={{ my: 2, color: "white", display: "block" }}
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{user && (
|
||||||
<>
|
<Button
|
||||||
<Button
|
onClick={() => navigate("/profile")}
|
||||||
onClick={() => navigate("/profile")}
|
sx={{ my: 2, color: "white", display: "block" }}
|
||||||
sx={{ my: 2, color: "white", display: "block" }}
|
>
|
||||||
>
|
Profile
|
||||||
Profile
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
|
|
||||||
<Button
|
{user?.is_superuser && (
|
||||||
onClick={goToAdmin}
|
<Button
|
||||||
sx={{ my: 2, color: "white", display: "block" }}
|
onClick={goToAdmin}
|
||||||
>
|
sx={{ my: 2, color: "white", display: "block" }}
|
||||||
Admin Dashboard
|
>
|
||||||
</Button>
|
Admin Dashboard
|
||||||
|
</Button>
|
||||||
</>
|
)}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export interface LoginResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginPayload {
|
export interface LoginPayload {
|
||||||
email: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
65
src/pages/LoginPage/LoginPage.tsx
Normal file
65
src/pages/LoginPage/LoginPage.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
|
||||||
|
import { Box, Button, Container, TextField, Typography } from "@mui/material";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import appColors from "../../utils/colors";
|
||||||
|
|
||||||
|
|
||||||
|
const LoginPage = () => {
|
||||||
|
const { login, user } = useAuth();
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
}, [user, navigate]);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await login({ username, password });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Box sx={{ padding: 4, maxWidth: 360, margin: "auto" }}>
|
||||||
|
<Typography variant="h4" mb={3} textAlign="center">
|
||||||
|
Login
|
||||||
|
</Typography>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextField
|
||||||
|
label="Username"
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
sx={{ mt: "10px", backgroundColor: appColors.primary }}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginPage;
|
||||||
57
src/pages/ProfilePage/ProfilePage.tsx
Normal file
57
src/pages/ProfilePage/ProfilePage.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Box, Button, Container, Typography } from "@mui/material";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import appColors from "../../utils/colors";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
const ProfilePage = () => {
|
||||||
|
const { user, logout, isLoading } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && !user) {
|
||||||
|
toast.error("You must be logged in to view your profile");
|
||||||
|
navigate("/login");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Box sx={{ padding: 4, maxWidth: 360 }}>
|
||||||
|
<Typography variant="h4" mb={2}>
|
||||||
|
Profile
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" mb={2}>
|
||||||
|
Username: {user?.username}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" mb={2}>
|
||||||
|
E-mail: {user?.email}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" mb={2}>
|
||||||
|
Index: {user?.school_index}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" mb={2}>
|
||||||
|
Status: {user?.is_superuser ? "Admin" : "User"}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
sx={{ backgroundColor: appColors.primary }}
|
||||||
|
variant="contained"
|
||||||
|
onClick={logout}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
sx={{ marginLeft: "10px", backgroundColor: appColors.secondary }}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => navigate("/admin")}
|
||||||
|
>
|
||||||
|
Admin Dashboard
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfilePage;
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import { createBrowserRouter } from "react-router-dom";
|
import { createBrowserRouter } from "react-router-dom";
|
||||||
import MainLayout from "../MainLayout/MainLayout";
|
import MainLayout from "../MainLayout/MainLayout";
|
||||||
import { Container } from "@mui/material";
|
import { Container } from "@mui/material";
|
||||||
|
import LoginPage from "../pages/LoginPage/LoginPage";
|
||||||
|
import ProfilePage from "../pages/ProfilePage/ProfilePage";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -10,6 +12,14 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
element: <Container>index page</Container>
|
element: <Container>index page</Container>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
element: <LoginPage/>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/profile",
|
||||||
|
element: <ProfilePage/>
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
])
|
])
|
||||||
|
|||||||
8
src/utils/colors.ts
Normal file
8
src/utils/colors.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const appColors = {
|
||||||
|
secondary: '#B3CDE0',
|
||||||
|
bg: '#FFFFFF',
|
||||||
|
primary: '#011F4B',
|
||||||
|
tertiary: '#EDF2FA',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default appColors;
|
||||||
Loading…
x
Reference in New Issue
Block a user