From a68dda7d7770b107cb891015e272fcfd763c7732 Mon Sep 17 00:00:00 2001 From: dimitar Date: Tue, 25 Feb 2025 16:45:16 +0100 Subject: [PATCH] ui redesign finished --- frontend/package-lock.json | 36 +- frontend/package.json | 5 +- frontend/src/App.jsx | 72 ++-- frontend/src/components/Layout/AppLayout.jsx | 64 ++++ frontend/src/components/UI/Badge.jsx | 37 ++ frontend/src/components/UI/Button.jsx | 83 +++++ frontend/src/components/UI/Card.jsx | 76 +++++ frontend/src/components/UI/Form.jsx | 40 +++ frontend/src/components/UI/Input.jsx | 55 +++ frontend/src/components/UI/Table.jsx | 88 +++++ frontend/src/components/UI/index.js | 5 + .../src/components/adminPanel/AdminPanel.jsx | 87 ++--- .../src/components/dashboard/Dashboard.jsx | 37 +- frontend/src/components/footer/footer.jsx | 256 +++++++------- frontend/src/components/gallery/Gallery.jsx | 247 +++++++++----- frontend/src/components/gallery/gallery.css | 6 + frontend/src/components/login/Login.jsx | 157 +++++++++ frontend/src/components/login/login.jsx | 168 ---------- frontend/src/components/navbar/Navbar.jsx | 155 +++++---- frontend/src/index.css | 307 ++++++++++++++++- frontend/src/pages/aboutpage/About.jsx | 194 ++++++----- frontend/src/pages/contactpage/Contact.jsx | 315 ++++++++++++------ frontend/src/pages/homepage/Home.jsx | 214 ++++++------ frontend/src/theme/ThemeProvider.jsx | 46 +++ frontend/src/utils/cn.js | 6 + frontend/tailwind.config.js | 101 +++++- 26 files changed, 2015 insertions(+), 842 deletions(-) create mode 100644 frontend/src/components/Layout/AppLayout.jsx create mode 100644 frontend/src/components/UI/Badge.jsx create mode 100644 frontend/src/components/UI/Button.jsx create mode 100644 frontend/src/components/UI/Card.jsx create mode 100644 frontend/src/components/UI/Form.jsx create mode 100644 frontend/src/components/UI/Input.jsx create mode 100644 frontend/src/components/UI/Table.jsx create mode 100644 frontend/src/components/UI/index.js create mode 100644 frontend/src/components/login/Login.jsx delete mode 100644 frontend/src/components/login/login.jsx create mode 100644 frontend/src/theme/ThemeProvider.jsx create mode 100644 frontend/src/utils/cn.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3a21fe2..8ccf950 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,13 +11,16 @@ "@headlessui/react": "^1.7.19", "@heroicons/react": "^2.2.0", "axios": "^1.7.7", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "date-fns": "^4.1.0", "framer-motion": "^11.18.2", "jwt-decode": "^4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.5.0", - "react-router-dom": "^6.15.0" + "react-router-dom": "^6.15.0", + "tailwind-merge": "^3.0.2" }, "devDependencies": { "@types/react": "^18.2.15", @@ -1416,11 +1419,32 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4100,6 +4124,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", + "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.14", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1c101ba..f0c8abf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,13 +13,16 @@ "@headlessui/react": "^1.7.19", "@heroicons/react": "^2.2.0", "axios": "^1.7.7", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "date-fns": "^4.1.0", "framer-motion": "^11.18.2", "jwt-decode": "^4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.5.0", - "react-router-dom": "^6.15.0" + "react-router-dom": "^6.15.0", + "tailwind-merge": "^3.0.2" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 39c4a86..6c667b2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,6 +1,5 @@ // import './App.css' import { AuthProvider } from './hooks/useAuth'; -import Navbar from './components/navbar/Navbar' import { BrowserRouter, Routes, Route } from "react-router-dom"; import Home from './pages/homepage/Home' import About from './pages/aboutpage/About' @@ -14,45 +13,50 @@ import Certificates from './components/Certificates/Certificates.jsx'; import Clients from './components/clients/clients'; import AdminPanel from './components/adminPanel/AdminPanel'; import Dashboard from './components/dashboard/Dashboard'; -import Login from './components/login/login'; +import Login from './components/login/Login'; +import Navbar from './components/navbar/Navbar'; import ProtectedRoute from './components/protectedRoute/ProtectedRoute'; +import { ThemeProvider } from './theme/ThemeProvider'; +// import { Navbar } from './components/navbar/Navbar'; function App() { return ( -
- - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - } /> - - - - } /> - } /> - - - - } /> - -
- -
+ +
+ + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + } /> + + + + } /> + } /> + + + + } /> + +
+ +
+
) } diff --git a/frontend/src/components/Layout/AppLayout.jsx b/frontend/src/components/Layout/AppLayout.jsx new file mode 100644 index 0000000..645b30d --- /dev/null +++ b/frontend/src/components/Layout/AppLayout.jsx @@ -0,0 +1,64 @@ +import { useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Outlet } from 'react-router-dom'; + +export default function AppLayout() { + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + return ( +
+ {/* Header */} +
+
+
+ + +
+ + + +
+ {/* User menu, notifications, etc. */} +
+
+
+ + {/* Main content */} +
+
+ +
+
+ + {/* Mobile sidebar */} + + {isSidebarOpen && ( + <> + setIsSidebarOpen(false)} + className="fixed inset-0 bg-black lg:hidden" + /> + + {/* Mobile navigation */} + + + )} + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/UI/Badge.jsx b/frontend/src/components/UI/Badge.jsx new file mode 100644 index 0000000..e7d0b0a --- /dev/null +++ b/frontend/src/components/UI/Badge.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { cva } from 'class-variance-authority'; +import { cn } from '../../utils/cn'; + +const badgeVariants = cva( + 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium', + { + variants: { + variant: { + default: 'bg-primary-100 text-primary-800', + secondary: 'bg-neutral-100 text-neutral-800', + success: 'bg-success-100 text-success-800', + warning: 'bg-warning-100 text-warning-800', + error: 'bg-error-100 text-error-800', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +); + +export function Badge({ + className, + variant, + children, + ...props +}) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/src/components/UI/Button.jsx b/frontend/src/components/UI/Button.jsx new file mode 100644 index 0000000..7455a03 --- /dev/null +++ b/frontend/src/components/UI/Button.jsx @@ -0,0 +1,83 @@ +import React, { forwardRef } from 'react'; +import { cva } from 'class-variance-authority'; +import { cn } from '../../utils/cn'; +import { Link } from 'react-router-dom'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-60 disabled:cursor-not-allowed', + { + variants: { + variant: { + primary: 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500 shadow-sm border border-transparent', + secondary: 'bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-300 focus:ring-primary-500', + outline: 'bg-transparent text-primary-600 hover:bg-primary-50 border border-primary-600 focus:ring-primary-500', + danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 shadow-sm border border-transparent', + ghost: 'bg-transparent text-neutral-700 hover:bg-neutral-100 border-transparent', + }, + size: { + sm: 'px-3 py-1.5 text-sm', + md: 'px-4 py-2 text-sm', + lg: 'px-6 py-3 text-base', + }, + fullWidth: { + true: 'w-full', + }, + }, + defaultVariants: { + variant: 'primary', + size: 'md', + fullWidth: false, + }, + } +); + +export const Button = forwardRef(({ + className, + variant, + size, + fullWidth, + children, + href, + to, + ...props +}, ref) => { + const classes = cn(buttonVariants({ variant, size, fullWidth }), className); + + if (to) { + return ( + + {children} + + ); + } + + if (href) { + return ( + + {children} + + ); + } + + return ( + + ); +}); + +Button.displayName = 'Button'; \ No newline at end of file diff --git a/frontend/src/components/UI/Card.jsx b/frontend/src/components/UI/Card.jsx new file mode 100644 index 0000000..1eec940 --- /dev/null +++ b/frontend/src/components/UI/Card.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { cn } from '../../utils/cn'; + +const cardVariants = { + base: 'bg-white rounded-lg shadow-sm border border-neutral-200 transition-shadow hover:shadow-md', + padding: { + none: '', + sm: 'p-4', + md: 'p-6', + lg: 'p-8', + }, + interactive: 'cursor-pointer hover:border-primary-200', +}; + +export function Card({ + className, + padding = 'md', + interactive = false, + children, + ...props +}) { + return ( +
+ {children} +
+ ); +} + +export function CardHeader({ className, children, ...props }) { + return ( +
+ {children} +
+ ); +} + +export function CardTitle({ className, children, ...props }) { + return ( +

+ {children} +

+ ); +} + +export function CardContent({ className, children, ...props }) { + return ( +
+ {children} +
+ ); +} + +export function CardFooter({ className, children, ...props }) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/UI/Form.jsx b/frontend/src/components/UI/Form.jsx new file mode 100644 index 0000000..856dbbb --- /dev/null +++ b/frontend/src/components/UI/Form.jsx @@ -0,0 +1,40 @@ +export function FormField({ label, error, children }) { + return ( +
+ {label && ( + + )} + {children} + {error && ( +

+ {error} +

+ )} +
+ ); +} + +export function Input({ type = 'text', ...props }) { + return ( + + ); +} + +export function Button({ variant = 'primary', children, ...props }) { + const className = variant === 'primary' ? 'btn-primary' : 'btn-secondary'; + + return ( + + ); +} \ No newline at end of file diff --git a/frontend/src/components/UI/Input.jsx b/frontend/src/components/UI/Input.jsx new file mode 100644 index 0000000..cd55536 --- /dev/null +++ b/frontend/src/components/UI/Input.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { cn } from '../../utils/cn'; + +export function Input({ + className, + type = 'text', + error, + ...props +}) { + return ( + + ); +} + +export function FormGroup({ className, children, ...props }) { + return ( +
+ {children} +
+ ); +} + +export function Label({ className, children, ...props }) { + return ( + + ); +} + +export function ErrorMessage({ children }) { + if (!children) return null; + + return ( +

+ {children} +

+ ); +} \ No newline at end of file diff --git a/frontend/src/components/UI/Table.jsx b/frontend/src/components/UI/Table.jsx new file mode 100644 index 0000000..80940de --- /dev/null +++ b/frontend/src/components/UI/Table.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { cn } from '../../utils/cn'; + +export function Table({ className, children, ...props }) { + return ( +
+ + {children} +
+
+ ); +} + +export function TableHeader({ className, children, ...props }) { + return ( + + {children} + + ); +} + +export function TableRow({ className, children, ...props }) { + return ( + + {children} + + ); +} + +export function TableHead({ className, children, ...props }) { + return ( + + {children} + + ); +} + +export function TableBody({ className, children, ...props }) { + return ( + + {children} + + ); +} + +export function TableCell({ className, children, ...props }) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/src/components/UI/index.js b/frontend/src/components/UI/index.js new file mode 100644 index 0000000..ec57f7e --- /dev/null +++ b/frontend/src/components/UI/index.js @@ -0,0 +1,5 @@ +export * from './Button'; +export * from './Input'; +export * from './Card'; +export * from './Badge'; +export * from './Table'; \ No newline at end of file diff --git a/frontend/src/components/adminPanel/AdminPanel.jsx b/frontend/src/components/adminPanel/AdminPanel.jsx index 2c414fc..3eae243 100644 --- a/frontend/src/components/adminPanel/AdminPanel.jsx +++ b/frontend/src/components/adminPanel/AdminPanel.jsx @@ -90,8 +90,8 @@ function AdminPanel() { if (loading) { return ( -
-
+
+
Loading...
@@ -100,11 +100,11 @@ function AdminPanel() { } return ( -
+

Admin Dashboard

-

Manage users and documents

+

Manage users and documents

{/* Tabs */} @@ -116,8 +116,8 @@ function AdminPanel() { className={` px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors ${activeTab === id - ? 'bg-white/10 text-white' - : 'text-gray-400 hover:bg-white/5 hover:text-white'} + ? 'bg-primary-600 text-white' + : 'text-neutral-400 hover:bg-primary-700/50 hover:text-white'} `} > @@ -134,36 +134,36 @@ function AdminPanel() {
{activeTab === 'documents' && ( -
+
- - - - - - + + + + + + {documents.map((doc) => ( - - + + - - - @@ -185,7 +185,7 @@ function AdminPanel() { {activeTab === 'users' && ( <> -
+

Create New User

setNewUser({ ...newUser, name: e.target.value })} - className="bg-white/5 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-400 focus:outline-none focus:border-blue-500" + className="bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2 + text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500 + focus:ring-1 focus:ring-primary-500" required /> setNewUser({ ...newUser, email: e.target.value })} - className="bg-white/5 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-400 focus:outline-none focus:border-blue-500" + className="bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2 + text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500 + focus:ring-1 focus:ring-primary-500" required /> setNewUser({ ...newUser, password: e.target.value })} - className="bg-white/5 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-400 focus:outline-none focus:border-blue-500" + className="bg-primary-700/30 border border-primary-600 rounded-lg px-4 py-2 + text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500 + focus:ring-1 focus:ring-primary-500" required />
@@ -218,13 +224,16 @@ function AdminPanel() { id="isAdmin" checked={newUser.isAdmin} onChange={(e) => setNewUser({ ...newUser, isAdmin: e.target.checked })} - className="rounded border-gray-700 bg-white/5 text-blue-500 focus:ring-blue-500" + className="rounded border-primary-600 bg-primary-700/30 text-primary-500 + focus:ring-primary-500" /> - +
-
+
TitleStatusUploaded ByShared WithCreated At
TitleStatusUploaded ByShared WithCreated At
{doc.title}
{doc.title} {doc.status} + {doc.uploadedBy?.name} ({doc.uploadedBy?.email}) + {doc.sharedWith && doc.sharedWith.length > 0 ? doc.sharedWith.map(user => (
@@ -172,7 +172,7 @@ function AdminPanel() { )) : 'None'}
+ {new Date(doc.createdAt).toLocaleString()}
- - - - + + + + {users.map((user) => ( - - - + + + @@ -261,7 +270,7 @@ function AdminPanel() { )} {activeTab === 'upload' && ( -
+
)} diff --git a/frontend/src/components/dashboard/Dashboard.jsx b/frontend/src/components/dashboard/Dashboard.jsx index 515268b..815ccf1 100644 --- a/frontend/src/components/dashboard/Dashboard.jsx +++ b/frontend/src/components/dashboard/Dashboard.jsx @@ -66,8 +66,8 @@ function Dashboard() { if (loading) { return ( -
-
+
+
Loading documents...
@@ -77,7 +77,7 @@ function Dashboard() { if (error) { return ( -
+
{error}
@@ -86,11 +86,11 @@ function Dashboard() { } return ( -
+

Your Documents

-

Access and manage your shared documents

+

Access and manage your shared documents

@@ -98,16 +98,18 @@ function Dashboard() { Object.entries(documents).map(([folderName, docs]) => (
)) ) : ( -
-

No documents available

+
+

No documents available

)}
diff --git a/frontend/src/components/footer/footer.jsx b/frontend/src/components/footer/footer.jsx index 099e501..1aa9b17 100644 --- a/frontend/src/components/footer/footer.jsx +++ b/frontend/src/components/footer/footer.jsx @@ -1,147 +1,149 @@ import { NavLink } from 'react-router-dom' +import { FiHome, FiMail, FiPhone, FiMapPin } from 'react-icons/fi' import './footer.css' const Footer = () => { return ( - <> -
+ ) } diff --git a/frontend/src/components/gallery/Gallery.jsx b/frontend/src/components/gallery/Gallery.jsx index ae1d06a..215cff9 100644 --- a/frontend/src/components/gallery/Gallery.jsx +++ b/frontend/src/components/gallery/Gallery.jsx @@ -1,25 +1,30 @@ import React from 'react' import './gallery.css' import { useEffect, useState } from 'react'; -// import { FiZoomIn } from 'react-icons/fi'; -import { motion } from 'framer-motion'; +import { motion, AnimatePresence } from 'framer-motion'; +import { XMarkIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +import { Button, Card } from '../../components/UI'; function Gallery() { const [selectedImage, setSelectedImage] = useState(null); const [loading, setLoading] = useState(true); + const [activeCategory, setActiveCategory] = useState('all'); + const [filteredImages, setFilteredImages] = useState([]); const images = [ - { id: 1, src: "1.jpg", alt: "Laboratory Equipment 1", category: "Lab" }, - { id: 2, src: "2.jpg", alt: "Laboratory Equipment 2", category: "Lab" }, - { id: 3, src: "3.jpg", alt: "Laboratory Equipment 3", category: "Lab" }, - { id: 4, src: "4.jpg", alt: "Testing Process 1", category: "Testing" }, - { id: 5, src: "5.jpg", alt: "Testing Process 2", category: "Testing" }, - { id: 6, src: "6.jpg", alt: "Testing Process 3", category: "Testing" }, - { id: 8, src: "8.jpg", alt: "Results 1", category: "Results" }, - { id: 9, src: "9.jpg", alt: "Results 2", category: "Results" }, - { id: 10, src: "10.jpg", alt: "Results 3", category: "Results" } + { id: 1, src: "1.jpg", alt: "Laboratory Equipment 1", category: "Лабораторија" }, + { id: 2, src: "2.jpg", alt: "Laboratory Equipment 2", category: "Лабораторија" }, + { id: 3, src: "3.jpg", alt: "Laboratory Equipment 3", category: "Лабораторија" }, + { id: 4, src: "4.jpg", alt: "Testing Process 1", category: "Тестирање" }, + { id: 5, src: "5.jpg", alt: "Testing Process 2", category: "Тестирање" }, + { id: 6, src: "6.jpg", alt: "Testing Process 3", category: "Тестирање" }, + { id: 8, src: "8.jpg", alt: "Results 1", category: "Резултати" }, + { id: 9, src: "9.jpg", alt: "Results 2", category: "Резултати" }, + { id: 10, src: "10.jpg", alt: "Results 3", category: "Резултати" } ]; + const categories = [...new Set(images.map(img => img.category))]; + useEffect(() => { window.scrollTo({ top: 0, behavior: "smooth" }) // Simulate images loading @@ -38,6 +43,14 @@ function Gallery() { loadImages(); }, []) + useEffect(() => { + setFilteredImages( + activeCategory === 'all' + ? images + : images.filter(img => img.category === activeCategory) + ); + }, [activeCategory]); + const containerVariants = { hidden: { opacity: 0 }, visible: { @@ -60,85 +73,157 @@ function Gallery() { }; return ( -
+
{/* Hero Section */} -
-
-

- Нашата Галерија -

-

- Погледнете ја нашата опрема и работен процес преку слики -

+
+ {/*
*/} +
+
+ + Нашата Галерија + + + Погледнете ја нашата опрема и работен процес преку слики + +
-
+ - {loading ? ( -
-
-
- ) : ( - -
- {images.map((image) => ( - +
+ {/* Category Filter */} +
+ + {categories.map(category => ( + -
- + {category} + ))}
-
- )} + + {loading ? ( +
+
+
+ ) : ( + + + {filteredImages.map((image) => ( + + setSelectedImage(image)} + > +
+ {image.alt} +
+
+

{image.alt}

+ {image.category} +
+
+
+
+ +
+
+
+
+
+ ))} +
+
+ )} +
+ {/* Image Modal */} - {selectedImage && ( -
setSelectedImage(null)} - > -
- {selectedImage.alt} - -
-
- )} + {selectedImage.alt} + +
+

{selectedImage.alt}

+

{selectedImage.category}

+
+ + + )} +
); } diff --git a/frontend/src/components/gallery/gallery.css b/frontend/src/components/gallery/gallery.css index ddc87ce..94e288d 100644 --- a/frontend/src/components/gallery/gallery.css +++ b/frontend/src/components/gallery/gallery.css @@ -2,6 +2,12 @@ @tailwind components; @tailwind utilities; +@layer components { + .loading-spinner { + @apply animate-spin rounded-full h-12 w-12 border-4 border-neutral-200 border-t-primary-600; + } +} + .aspect-w-16 { position: relative; padding-bottom: 75%; diff --git a/frontend/src/components/login/Login.jsx b/frontend/src/components/login/Login.jsx new file mode 100644 index 0000000..b930d07 --- /dev/null +++ b/frontend/src/components/login/Login.jsx @@ -0,0 +1,157 @@ +import { useState } from 'react'; +import { useAuth } from '../../hooks/useAuth'; +import { useNavigate } from 'react-router-dom'; +import { Button, Card, Input, FormGroup, Label, ErrorMessage } from '../UI'; + +export default function Login() { + const { login } = useAuth(); + const navigate = useNavigate(); + const [formData, setFormData] = useState({ + username: '', + password: '', + }); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + setIsLoading(true); + + try { + const userData = await login(formData.username, formData.password); + + // Navigate based on user role + if (userData.isAdmin) { + navigate('/admin'); + } else { + navigate('/dashboard'); // Navigate normal users to dashboard + } + } catch (err) { + setError('Invalid username or password'); + console.error('Login error:', err); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ {/* Pattern overlay with lower z-index */} +
+ + {/* Main content with higher z-index */} + +
+

Sign in to your account

+

Welcome back! Please enter your details.

+
+ + {error && ( +
+

{error}

+
+ )} + +
+ + + setFormData({ ...formData, username: e.target.value })} + error={error} + className="bg-neutral-800/50 border-neutral-700 text-white placeholder-neutral-400 + focus:border-primary-400 focus:ring-primary-400" + /> + + + +
+ + +
+ setFormData({ ...formData, password: e.target.value })} + error={error} + className="bg-neutral-800/50 border-neutral-700 text-white placeholder-neutral-400 + focus:border-primary-400 focus:ring-primary-400" + /> +
+ + + + +
+

+ Don't have an account?{' '} + +

+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/login/login.jsx b/frontend/src/components/login/login.jsx deleted file mode 100644 index bd18f8d..0000000 --- a/frontend/src/components/login/login.jsx +++ /dev/null @@ -1,168 +0,0 @@ -// import { useState } from 'react'; -// import { useAuth } from '../../hooks/useAuth'; -// import { useNavigate } from 'react-router-dom'; - -// const Login = () => { -// const [username, setUsername] = useState(''); // Changed from email to username -// const [password, setPassword] = useState(''); -// const [error, setError] = useState(''); -// const { login } = useAuth(); -// const navigate = useNavigate(); - -// const handleSubmit = async (e) => { -// e.preventDefault(); -// setError(''); - -// try { -// const userData = await login(username, password); -// console.log('Login result:', userData); -// if (userData?.isAdmin) { -// navigate('/admin'); -// } else { -// navigate('/dashboard'); -// } -// } catch (err) { -// setError('Invalid credentials'); -// } -// }; - -// return ( -//
-//
-//
-//

-// Sign in to your account -//

-//
-//
-// {error && ( -//
-// {error} -//
-// )} -//
-//
-// setUsername(e.target.value)} -// /> -//
-//
-// setPassword(e.target.value)} -// /> -//
-//
- -//
-// -//
-// -//
-//
-// ); -// }; - -// export default Login; -import { useState } from 'react'; -import { useAuth } from '../../hooks/useAuth'; -import { useNavigate } from 'react-router-dom'; -import { FiUser, FiLock } from 'react-icons/fi'; // Import icons - -const Login = () => { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - const { login } = useAuth(); - const navigate = useNavigate(); - - const handleSubmit = async (e) => { - e.preventDefault(); - setError(''); - - try { - const userData = await login(username, password); - if (userData?.isAdmin) { - navigate('/admin'); - } else { - navigate('/dashboard'); - } - } catch (err) { - setError('Invalid credentials'); - } - }; - - return ( -
-
-
-

Welcome Back

-

Please sign in to continue

-
- -
- {error && ( -
- {error} -
- )} - -
-
- - setUsername(e.target.value)} - /> -
- -
- - setPassword(e.target.value)} - /> -
-
- - - -
-

- Need help? Contact your administrator -

-
- -
-
- ); -}; - -export default Login; \ No newline at end of file diff --git a/frontend/src/components/navbar/Navbar.jsx b/frontend/src/components/navbar/Navbar.jsx index ea6d70e..24b522d 100644 --- a/frontend/src/components/navbar/Navbar.jsx +++ b/frontend/src/components/navbar/Navbar.jsx @@ -1,15 +1,15 @@ import { useState } from "react"; import { Dialog } from "@headlessui/react"; import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline"; -import { NavLink } from "react-router-dom"; +import { NavLink, Link } from "react-router-dom"; +import { Button } from '../UI'; const navigation = [ { name: "Дома", href: "/" }, { name: "За нас", href: "/about" }, { name: "Галерија", href: "/gallery" }, { name: "Контакт", href: "/contact" }, - // { name: "Клиенти", href: "/clients" }, - { name: "Админ", href: "/admin" }, + // { name: "Админ", href: "/admin" }, { name: "Логин", href: "/login" }, ]; @@ -17,63 +17,60 @@ export default function Navbar() { const [mobileMenuOpen, setMobileMenuOpen] = useState(false); return ( -
-
-
NameEmailRole
NameEmailRole
{user.name}{user.email}
{user.name}{user.email} + ${user.isAdmin ? 'bg-primary-500/20 text-primary-300' : 'bg-neutral-500/20 text-neutral-300'}`}> {user.isAdmin ? 'Admin' : 'User'}