This commit is contained in:
dimitar 2025-04-02 20:44:03 +02:00
parent 71b73d5fcb
commit 0269cefeeb
5 changed files with 150 additions and 57 deletions

View File

@ -153,7 +153,9 @@ export class AuthController {
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get("user-info") @Get("user-info")
async getUserInfo(@Request() req) { async getUserInfo(@Request() req) {
return this.authService.getUserInfo(req.user.userId); return await this.authService.getUserInfo(req.user.userId);
// const user = await this.authService.getUserInfo(req.user.userId);
// return user;
} }
@Post("forgot-password") @Post("forgot-password")

View File

@ -1,22 +1,22 @@
import { useState } from 'react'; import { useState } from "react";
import { useAuth } from '../../hooks/useAuth'; import { useAuth } from "../../hooks/useAuth";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import { Button, Card, Input, FormGroup, Label, ErrorMessage } from '../UI'; import { Button, Card, Input, FormGroup, Label, ErrorMessage } from "../UI";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
export default function Login() { export default function Login() {
const { login } = useAuth(); const { login } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
username: '', username: "",
password: '', password: "",
}); });
const [error, setError] = useState(''); const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setError(''); setError("");
setIsLoading(true); setIsLoading(true);
try { try {
@ -24,13 +24,13 @@ export default function Login() {
// Navigate based on user role // Navigate based on user role
if (userData.isAdmin) { if (userData.isAdmin) {
navigate('/admin'); navigate("/admin");
} else { } else {
navigate('/dashboard'); // Navigate normal users to dashboard navigate("/dashboard");
} }
} catch (err) { } catch (err) {
setError('Invalid username or password'); setError("Invalid username or password");
console.error('Login error:', err); console.error("Login error:", err);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@ -42,11 +42,17 @@ export default function Login() {
<div className="absolute inset-0 bg-pattern opacity-5 z-0"></div> <div className="absolute inset-0 bg-pattern opacity-5 z-0"></div>
{/* Main content with higher z-index */} {/* Main content with higher z-index */}
<Card className="relative z-10 max-w-md w-full bg-neutral-900/80 backdrop-blur-lg shadow-2xl <Card
border border-neutral-800 rounded-xl"> className="relative z-10 max-w-md w-full bg-neutral-900/80 backdrop-blur-lg shadow-2xl
border border-neutral-800 rounded-xl"
>
<div className="text-center mb-8"> <div className="text-center mb-8">
<h2 className="text-2xl font-semibold text-white">Sign in to your account</h2> <h2 className="text-2xl font-semibold text-white">
<p className="text-sm text-neutral-300 mt-2">Welcome back! Please enter your details.</p> Sign in to your account
</h2>
<p className="text-sm text-neutral-300 mt-2">
Welcome back! Please enter your details.
</p>
</div> </div>
{error && ( {error && (
@ -66,7 +72,9 @@ export default function Login() {
required required
placeholder="Enter your username" placeholder="Enter your username"
value={formData.username} value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })} onChange={(e) =>
setFormData({ ...formData, username: e.target.value })
}
error={error} error={error}
className="bg-neutral-800/50 border-neutral-700 text-white placeholder-neutral-400 className="bg-neutral-800/50 border-neutral-700 text-white placeholder-neutral-400
focus:border-primary-400 focus:ring-primary-400" focus:border-primary-400 focus:ring-primary-400"
@ -93,7 +101,9 @@ export default function Login() {
required required
placeholder="Enter your password" placeholder="Enter your password"
value={formData.password} value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })} onChange={(e) =>
setFormData({ ...formData, password: e.target.value })
}
error={error} error={error}
className="bg-neutral-800/50 border-neutral-700 text-white placeholder-neutral-400 className="bg-neutral-800/50 border-neutral-700 text-white placeholder-neutral-400
focus:border-primary-400 focus:ring-primary-400" focus:border-primary-400 focus:ring-primary-400"
@ -110,7 +120,7 @@ export default function Login() {
focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50
disabled:cursor-not-allowed transition-colors" disabled:cursor-not-allowed transition-colors"
> >
{isLoading ? 'Signing in...' : 'Sign in'} {isLoading ? "Signing in..." : "Sign in"}
</button> </button>
</div> </div>

View File

@ -1,37 +1,70 @@
import { useState } from "react"; import { useState } from "react";
import { Dialog } from "@headlessui/react"; import { Dialog } from "@headlessui/react";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline"; import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import { NavLink, Link } from "react-router-dom"; import { NavLink, Link, useNavigate } from "react-router-dom";
import { Button } from '../UI'; import { Button } from "../UI";
import { useAuth } from "../../hooks/useAuth";
const navigation = [ const publicNavigation = [
{ name: "Дома", href: "/" }, { name: "Дома", href: "/" },
{ name: "За нас", href: "/about" }, { name: "За нас", href: "/about" },
{ name: "Галерија", href: "/gallery" }, { name: "Галерија", href: "/gallery" },
{ name: "Контакт", href: "/contact" }, { name: "Контакт", href: "/contact" },
// { name: "Админ", href: "/admin" },
{ name: "Логин", href: "/login" }, { name: "Логин", href: "/login" },
]; ];
const basePrivateNavigation = [
{ name: "Дома", href: "/" },
{ name: "За нас", href: "/about" },
{ name: "Галерија", href: "/gallery" },
{ name: "Контакт", href: "/contact" },
];
export default function Navbar() { export default function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const { user, logout } = useAuth();
const navigate = useNavigate();
const getNavigation = () => {
if (!user) return publicNavigation;
// Add appropriate dashboard link based on user role
const privateNavigation = [...basePrivateNavigation];
if (user.isAdmin) {
privateNavigation.push({ name: "Админ Панел", href: "/admin" });
} else {
privateNavigation.push({ name: "Клиент Зона", href: "/dashboard" });
}
return privateNavigation;
};
const navigation = getNavigation();
const handleLogout = async () => {
try {
await logout();
navigate("/");
} catch (error) {
console.error("Logout failed:", error);
}
};
return ( return (
<div className="w-full bg-gradient-to-b from-primary-900 to-primary-800 border-b border-primary-700/50"> <div className="w-full bg-gradient-to-b from-primary-900 to-primary-800 border-b border-primary-700/50">
<header className="relative"> <header className="relative">
<nav className="container-base py-4" aria-label="Global"> <nav className="container-base py-4" aria-label="Global">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{/* Left Logo */} {/* Left Logo */}
<Link to="/" className="p-2 hover:bg-white/20 transition-colors"> <div className="flex-shrink-0">
<img <Link to="/" className="p-2 hover:bg-white/20 transition-colors">
className="h-10 w-auto transition-transform duration-300 hover:scale-105" <img
src="/imklogorgb.png" className="h-10 w-auto transition-transform duration-300 hover:scale-105"
alt="IMK logo" src="/imklogorgb.png"
/> alt="IMK logo"
</Link> />
</Link>
</div>
{/* Centered Desktop Navigation */} {/* Centered Desktop Navigation */}
<div className="hidden lg:flex flex-grow justify-center mx-8"> <div className="hidden lg:flex flex-grow justify-center">
<div className="flex items-center gap-x-1"> <div className="flex items-center gap-x-1">
{navigation.map((item) => ( {navigation.map((item) => (
<NavLink <NavLink
@ -39,9 +72,11 @@ export default function Navbar() {
to={item.href} to={item.href}
className={({ isActive }) => className={({ isActive }) =>
`px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 `px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
${isActive ${
? 'bg-primary-700 text-white shadow-lg' isActive
: 'text-neutral-200 hover:bg-primary-700/50 hover:text-white'}` ? "bg-primary-700 text-white shadow-lg"
: "text-neutral-200 hover:bg-primary-700/50 hover:text-white"
}`
} }
> >
{item.name} {item.name}
@ -50,18 +85,36 @@ export default function Navbar() {
</div> </div>
</div> </div>
{/* Right Side - TUF Logo & Mobile Menu */} {/* Right Side Content */}
<div className="flex items-center gap-x-4"> <div className="flex items-center gap-x-4">
{/* TUF Logo */} {/* User Menu & Logout */}
<a href="/" className="hidden lg:flex p-2 hover:bg-white/20 transition-colors"> {user && (
<img <div className="hidden lg:flex items-center gap-x-4">
className="h-10 w-auto transition-transform duration-300 hover:scale-105" <span className="text-white text-sm">
src="/tuf.png" {user.name || user.email}
alt="TUF logo" </span>
/> <Button
</a> variant="ghost"
onClick={handleLogout}
className="text-white hover:bg-primary-700/50 px-4 py-2 text-sm"
>
Одјави се
</Button>
</div>
)}
{/* Mobile menu button */} {/* TUF Logo */}
<div className="hidden lg:block">
<a href="/" className="p-2 hover:bg-white/20 transition-colors">
<img
className="h-10 w-auto transition-transform duration-300 hover:scale-105"
src="/tuf.png"
alt="TUF logo"
/>
</a>
</div>
{/* Mobile Menu Button */}
<Button <Button
variant="ghost" variant="ghost"
className="lg:hidden text-white hover:bg-primary-700/50" className="lg:hidden text-white hover:bg-primary-700/50"
@ -74,7 +127,7 @@ export default function Navbar() {
</div> </div>
</nav> </nav>
{/* Mobile menu */} {/* Mobile Menu */}
<Dialog <Dialog
as="div" as="div"
className="lg:hidden" className="lg:hidden"
@ -100,24 +153,53 @@ export default function Navbar() {
<XMarkIcon className="h-6 w-6" aria-hidden="true" /> <XMarkIcon className="h-6 w-6" aria-hidden="true" />
</Button> </Button>
</div> </div>
<div className="mt-6 flow-root"> <div className="mt-6 flow-root">
<div className="space-y-1 py-6"> <div className="space-y-1 py-6">
{/* User Info in Mobile Menu */}
{user && (
<div className="px-3 py-2 text-white border-b border-primary-700/50 mb-4">
<span className="block text-sm font-medium">
{user.name || user.email}
</span>
<span className="block text-sm text-primary-400">
{user.isAdmin ? "Администратор" : "Корисник"}
</span>
</div>
)}
{/* Navigation Items */}
{navigation.map((item) => ( {navigation.map((item) => (
<NavLink <NavLink
key={item.name} key={item.name}
to={item.href} to={item.href}
className={({ isActive }) => className={({ isActive }) =>
`block px-3 py-2 text-base font-medium rounded-lg transition-colors `block px-3 py-2 text-base font-medium rounded-lg transition-colors
${isActive ${
? 'bg-primary-700 text-white' isActive
: 'text-neutral-200 hover:bg-primary-700/50 hover:text-white' ? "bg-primary-700 text-white"
}` : "text-neutral-200 hover:bg-primary-700/50 hover:text-white"
}`
} }
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
> >
{item.name} {item.name}
</NavLink> </NavLink>
))} ))}
{/* Logout in Mobile Menu */}
{user && (
<Button
variant="ghost"
onClick={() => {
handleLogout();
setMobileMenuOpen(false);
}}
className="block w-full text-left px-3 py-2 text-base font-medium text-neutral-200 hover:bg-primary-700/50 hover:text-white rounded-lg mt-4"
>
Одјави се
</Button>
)}
</div> </div>
</div> </div>
</Dialog.Panel> </Dialog.Panel>

View File

@ -1,4 +1,3 @@
// frontend/src/hooks/useAuth.jsx
import { createContext, useContext, useState, useEffect } from "react"; import { createContext, useContext, useState, useEffect } from "react";
import api from "../services/api"; import api from "../services/api";
@ -51,6 +50,7 @@ export const AuthProvider = ({ children }) => {
}; };
const logout = () => { const logout = () => {
//no session menager in DB, can add it later
localStorage.removeItem("token"); localStorage.removeItem("token");
setUser(null); setUser(null);
}; };

View File

@ -122,5 +122,4 @@ api.interceptors.response.use(
return Promise.reject(error); return Promise.reject(error);
}, },
); );
export const logout = () => api.post("/auth/logout");
export default api; export default api;