login and profile page implemented

This commit is contained in:
David Katrinka 2025-12-16 18:58:36 +01:00
parent 9af9818f9a
commit e50b99641c
9 changed files with 188 additions and 46 deletions

2
.env
View File

@ -1 +1 @@
VITE_API_URL=http://127.0.0.1:8000 VITE_API_URL=https://gallery.steve-dekart.xyz

View File

@ -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;
}; };

View File

@ -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;
}, },

View File

@ -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>

View File

@ -18,7 +18,7 @@ export interface LoginResponse {
} }
export interface LoginPayload { export interface LoginPayload {
email: string; username: string;
password: string; password: string;
} }

View 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;

View 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;

View File

@ -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
View File

@ -0,0 +1,8 @@
export const appColors = {
secondary: '#B3CDE0',
bg: '#FFFFFF',
primary: '#011F4B',
tertiary: '#EDF2FA',
};
export default appColors;