ui redesign
finished
This commit is contained in:
parent
7c02511dab
commit
a68dda7d77
36
frontend/package-lock.json
generated
36
frontend/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 (
|
||||
<AuthProvider>
|
||||
<div >
|
||||
<BrowserRouter >
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path='/' element={<Home />} />
|
||||
<Route path='/contact' element={<Contact />} />
|
||||
<Route path='/about' element={<About />} />
|
||||
<Route path='/consulting' element={<Consulting />} />
|
||||
<Route path='/lab' element={<Lab />} />
|
||||
<Route path='/ultrasound' element={<UltraSound />} />
|
||||
<Route path='/gallery' element={<Gallery />} />
|
||||
<Route path='/certificates' element={<Certificates />} />
|
||||
<Route path='/clients' element={
|
||||
<ProtectedRoute>
|
||||
<Clients />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/admin' element={
|
||||
<ProtectedRoute>
|
||||
<AdminPanel />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/login' element={<Login />} />
|
||||
<Route path='/dashboard' element={
|
||||
<ProtectedRoute>
|
||||
<Dashboard />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
</Routes>
|
||||
<Footer />
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
<ThemeProvider>
|
||||
<div className="min-h-screen bg-neutral-50">
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path='/' element={<Home />} />
|
||||
<Route path='/contact' element={<Contact />} />
|
||||
<Route path='/about' element={<About />} />
|
||||
<Route path='/consulting' element={<Consulting />} />
|
||||
<Route path='/lab' element={<Lab />} />
|
||||
<Route path='/ultrasound' element={<UltraSound />} />
|
||||
<Route path='/gallery' element={<Gallery />} />
|
||||
<Route path='/certificates' element={<Certificates />} />
|
||||
<Route path='/clients' element={
|
||||
<ProtectedRoute>
|
||||
<Clients />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/admin' element={
|
||||
<ProtectedRoute>
|
||||
<AdminPanel />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/login' element={<Login />} />
|
||||
<Route path='/dashboard' element={
|
||||
<ProtectedRoute>
|
||||
<Dashboard />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
</Routes>
|
||||
<Footer />
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
)
|
||||
}
|
||||
|
||||
64
frontend/src/components/Layout/AppLayout.jsx
Normal file
64
frontend/src/components/Layout/AppLayout.jsx
Normal file
@ -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 (
|
||||
<div className="min-h-screen bg-neutral-50">
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-neutral-200">
|
||||
<div className="page-container h-16 flex items-center justify-between">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className="lg:hidden p-2 rounded-lg hover:bg-neutral-100"
|
||||
>
|
||||
<MenuIcon className="w-6 h-6 text-neutral-500" />
|
||||
</button>
|
||||
<Logo className="h-8 w-auto" />
|
||||
</div>
|
||||
|
||||
<nav className="hidden lg:flex items-center gap-x-8">
|
||||
{/* Navigation items */}
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-x-4">
|
||||
{/* User menu, notifications, etc. */}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main content */}
|
||||
<main className="flex-1">
|
||||
<div className="page-container py-8">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Mobile sidebar */}
|
||||
<AnimatePresence>
|
||||
{isSidebarOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.5 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
className="fixed inset-0 bg-black lg:hidden"
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ x: '-100%' }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: '-100%' }}
|
||||
className="fixed inset-y-0 left-0 w-64 bg-white border-r border-neutral-200 lg:hidden"
|
||||
>
|
||||
{/* Mobile navigation */}
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
frontend/src/components/UI/Badge.jsx
Normal file
37
frontend/src/components/UI/Badge.jsx
Normal file
@ -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 (
|
||||
<span
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
83
frontend/src/components/UI/Button.jsx
Normal file
83
frontend/src/components/UI/Button.jsx
Normal file
@ -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 (
|
||||
<Link
|
||||
to={to}
|
||||
className={classes}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={classes}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classes}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
Button.displayName = 'Button';
|
||||
76
frontend/src/components/UI/Card.jsx
Normal file
76
frontend/src/components/UI/Card.jsx
Normal file
@ -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 (
|
||||
<div
|
||||
className={cn(
|
||||
cardVariants.base,
|
||||
cardVariants.padding[padding],
|
||||
interactive && cardVariants.interactive,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardHeader({ className, children, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={cn('flex items-center justify-between mb-4', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardTitle({ className, children, ...props }) {
|
||||
return (
|
||||
<h3
|
||||
className={cn('text-lg font-semibold text-neutral-900', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardContent({ className, children, ...props }) {
|
||||
return (
|
||||
<div className={cn('space-y-4', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardFooter({ className, children, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={cn('flex items-center justify-end gap-x-4 mt-6', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
40
frontend/src/components/UI/Form.jsx
Normal file
40
frontend/src/components/UI/Form.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
export function FormField({ label, error, children }) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{label && (
|
||||
<label className="label">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
{children}
|
||||
{error && (
|
||||
<p className="text-sm text-red-600 mt-1">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Input({ type = 'text', ...props }) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className="input"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Button({ variant = 'primary', children, ...props }) {
|
||||
const className = variant === 'primary' ? 'btn-primary' : 'btn-secondary';
|
||||
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
55
frontend/src/components/UI/Input.jsx
Normal file
55
frontend/src/components/UI/Input.jsx
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { cn } from '../../utils/cn';
|
||||
|
||||
export function Input({
|
||||
className,
|
||||
type = 'text',
|
||||
error,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'block w-full rounded-lg shadow-sm sm:text-sm transition-colors duration-200',
|
||||
error
|
||||
? 'border-error-300 text-error-900 placeholder-error-300 focus:border-error-500 focus:ring-error-500'
|
||||
: 'border-neutral-300 focus:border-primary-500 focus:ring-primary-500',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormGroup({ className, children, ...props }) {
|
||||
return (
|
||||
<div className={cn('space-y-1', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Label({ className, children, ...props }) {
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'block text-sm font-medium text-neutral-700',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorMessage({ children }) {
|
||||
if (!children) return null;
|
||||
|
||||
return (
|
||||
<p className="text-sm text-error-600 mt-1">
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
88
frontend/src/components/UI/Table.jsx
Normal file
88
frontend/src/components/UI/Table.jsx
Normal file
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { cn } from '../../utils/cn';
|
||||
|
||||
export function Table({ className, children, ...props }) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<table
|
||||
className={cn(
|
||||
'min-w-full divide-y divide-neutral-200',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableHeader({ className, children, ...props }) {
|
||||
return (
|
||||
<thead
|
||||
className={cn(
|
||||
'bg-neutral-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</thead>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableRow({ className, children, ...props }) {
|
||||
return (
|
||||
<tr
|
||||
className={cn(
|
||||
'hover:bg-neutral-50 transition-colors',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableHead({ className, children, ...props }) {
|
||||
return (
|
||||
<th
|
||||
className={cn(
|
||||
'px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableBody({ className, children, ...props }) {
|
||||
return (
|
||||
<tbody
|
||||
className={cn(
|
||||
'bg-white divide-y divide-neutral-200',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableCell({ className, children, ...props }) {
|
||||
return (
|
||||
<td
|
||||
className={cn(
|
||||
'px-6 py-4 whitespace-nowrap text-sm text-neutral-600',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
5
frontend/src/components/UI/index.js
Normal file
5
frontend/src/components/UI/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './Button';
|
||||
export * from './Input';
|
||||
export * from './Card';
|
||||
export * from './Badge';
|
||||
export * from './Table';
|
||||
@ -90,8 +90,8 @@ function AdminPanel() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="flex items-center space-x-3 text-blue-500">
|
||||
<div className="min-h-screen flex items-center justify-center bg-primary-900">
|
||||
<div className="flex items-center space-x-3 text-primary-400">
|
||||
<FiLoader className="w-6 h-6 animate-spin" />
|
||||
<span className="text-lg font-medium">Loading...</span>
|
||||
</div>
|
||||
@ -100,11 +100,11 @@ function AdminPanel() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 p-6">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-900 to-primary-800 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<header className="mb-8 mt-20">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Admin Dashboard</h1>
|
||||
<p className="text-gray-400">Manage users and documents</p>
|
||||
<p className="text-neutral-400">Manage users and documents</p>
|
||||
</header>
|
||||
|
||||
{/* 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'}
|
||||
`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
@ -134,36 +134,36 @@ function AdminPanel() {
|
||||
|
||||
<div className="grid gap-6">
|
||||
{activeTab === 'documents' && (
|
||||
<div className="bg-white/5 backdrop-blur-lg rounded-xl overflow-hidden">
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl overflow-hidden shadow-xl">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-700">
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Title</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Status</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Uploaded By</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Shared With</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Created At</th>
|
||||
<tr className="border-b border-primary-700">
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Title</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Status</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Uploaded By</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Shared With</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{documents.map((doc) => (
|
||||
<tr key={doc.id} className="border-b border-gray-700/50 hover:bg-white/5">
|
||||
<td className="px-6 py-4 text-gray-200">{doc.title}</td>
|
||||
<tr key={doc.id} className="border-b border-primary-700/50 hover:bg-primary-700/30">
|
||||
<td className="px-6 py-4 text-white">{doc.title}</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
|
||||
doc.status === 'completed' ? 'bg-green-500/10 text-green-400' :
|
||||
doc.status === 'pending' ? 'bg-yellow-500/10 text-yellow-400' :
|
||||
doc.status === 'uploading' ? 'bg-blue-500/10 text-blue-400' :
|
||||
'bg-gray-500/10 text-gray-400'
|
||||
doc.status === 'completed' ? 'bg-green-500/20 text-green-300' :
|
||||
doc.status === 'pending' ? 'bg-yellow-500/20 text-yellow-300' :
|
||||
doc.status === 'uploading' ? 'bg-primary-500/20 text-primary-300' :
|
||||
'bg-neutral-500/20 text-neutral-300'
|
||||
}`}>
|
||||
{doc.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-gray-400">
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{doc.uploadedBy?.name} ({doc.uploadedBy?.email})
|
||||
</td>
|
||||
<td className="px-6 py-4 text-gray-400">
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{doc.sharedWith && doc.sharedWith.length > 0
|
||||
? doc.sharedWith.map(user => (
|
||||
<div key={user.id} className="whitespace-nowrap">
|
||||
@ -172,7 +172,7 @@ function AdminPanel() {
|
||||
))
|
||||
: 'None'}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-gray-400">
|
||||
<td className="px-6 py-4 text-neutral-300">
|
||||
{new Date(doc.createdAt).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
@ -185,7 +185,7 @@ function AdminPanel() {
|
||||
|
||||
{activeTab === 'users' && (
|
||||
<>
|
||||
<div className="bg-white/5 backdrop-blur-lg rounded-xl p-6 mb-6">
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl p-6 mb-6 shadow-xl">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Create New User</h2>
|
||||
<form onSubmit={handleCreateUser} className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<input
|
||||
@ -193,7 +193,9 @@ function AdminPanel() {
|
||||
placeholder="Name"
|
||||
value={newUser.name}
|
||||
onChange={(e) => 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
|
||||
/>
|
||||
<input
|
||||
@ -201,7 +203,9 @@ function AdminPanel() {
|
||||
placeholder="Email"
|
||||
value={newUser.email}
|
||||
onChange={(e) => 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
|
||||
/>
|
||||
<input
|
||||
@ -209,7 +213,9 @@ function AdminPanel() {
|
||||
placeholder="Password"
|
||||
value={newUser.password}
|
||||
onChange={(e) => 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
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
@ -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"
|
||||
/>
|
||||
<label htmlFor="isAdmin" className="text-gray-200">Is Admin</label>
|
||||
<label htmlFor="isAdmin" className="text-white">Is Admin</label>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center justify-center space-x-2 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition-colors"
|
||||
className="flex items-center justify-center space-x-2 px-4 py-2
|
||||
bg-primary-600 hover:bg-primary-700 text-white rounded-lg
|
||||
transition-colors shadow-lg"
|
||||
>
|
||||
<FiUserPlus className="w-4 h-4" />
|
||||
<span>Create User</span>
|
||||
@ -232,23 +241,23 @@ function AdminPanel() {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 backdrop-blur-lg rounded-xl overflow-hidden">
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl overflow-hidden shadow-xl">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-700">
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Name</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Email</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-gray-400">Role</th>
|
||||
<tr className="border-b border-primary-700">
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Name</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Email</th>
|
||||
<th className="px-6 py-4 text-left text-sm text-neutral-400">Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id} className="border-b border-gray-700/50 hover:bg-white/5">
|
||||
<td className="px-6 py-4 text-gray-200">{user.name}</td>
|
||||
<td className="px-6 py-4 text-gray-200">{user.email}</td>
|
||||
<tr key={user.id} className="border-b border-primary-700/50 hover:bg-primary-700/30">
|
||||
<td className="px-6 py-4 text-white">{user.name}</td>
|
||||
<td className="px-6 py-4 text-white">{user.email}</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium
|
||||
${user.isAdmin ? 'bg-purple-500/10 text-purple-400' : 'bg-gray-500/10 text-gray-400'}`}>
|
||||
${user.isAdmin ? 'bg-primary-500/20 text-primary-300' : 'bg-neutral-500/20 text-neutral-300'}`}>
|
||||
{user.isAdmin ? 'Admin' : 'User'}
|
||||
</span>
|
||||
</td>
|
||||
@ -261,7 +270,7 @@ function AdminPanel() {
|
||||
)}
|
||||
|
||||
{activeTab === 'upload' && (
|
||||
<div className="bg-white/5 backdrop-blur-lg rounded-xl p-6">
|
||||
<div className="bg-primary-800/50 backdrop-blur-lg rounded-xl p-6 shadow-xl">
|
||||
<DocumentUpload />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -66,8 +66,8 @@ function Dashboard() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="flex items-center space-x-3 text-blue-500">
|
||||
<div className="min-h-screen flex items-center justify-center bg-primary-900">
|
||||
<div className="flex items-center space-x-3 text-primary-400">
|
||||
<FiLoader className="w-6 h-6 animate-spin" />
|
||||
<span className="text-lg font-medium">Loading documents...</span>
|
||||
</div>
|
||||
@ -77,7 +77,7 @@ function Dashboard() {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="min-h-screen flex items-center justify-center bg-primary-900">
|
||||
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg text-red-200">
|
||||
{error}
|
||||
</div>
|
||||
@ -86,11 +86,11 @@ function Dashboard() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 p-6">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-900 to-primary-800 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<header className="mb-8 mt-20">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Your Documents</h1>
|
||||
<p className="text-gray-400">Access and manage your shared documents</p>
|
||||
<p className="text-neutral-400">Access and manage your shared documents</p>
|
||||
</header>
|
||||
|
||||
<div className="grid gap-6">
|
||||
@ -98,16 +98,18 @@ function Dashboard() {
|
||||
Object.entries(documents).map(([folderName, docs]) => (
|
||||
<div
|
||||
key={folderName}
|
||||
className="bg-white/5 backdrop-blur-lg rounded-xl overflow-hidden transition-all duration-200 hover:bg-white/10"
|
||||
className="bg-neutral-900/80 backdrop-blur-lg rounded-xl overflow-hidden
|
||||
transition-all duration-200 hover:bg-neutral-900/90 border border-neutral-800"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleFolder(folderName)}
|
||||
className="w-full px-6 py-4 flex items-center justify-between text-white hover:bg-white/5 transition-colors"
|
||||
className="w-full px-6 py-4 flex items-center justify-between text-white
|
||||
hover:bg-primary-900/20 transition-colors"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<FiFolder className="w-5 h-5 text-blue-400" />
|
||||
<FiFolder className="w-5 h-5 text-primary-400" />
|
||||
<span className="font-medium">{folderName}</span>
|
||||
<span className="text-sm text-gray-400">({docs.length} files)</span>
|
||||
<span className="text-sm text-neutral-400">({docs.length} files)</span>
|
||||
</div>
|
||||
<FiChevronRight
|
||||
className={`w-5 h-5 transition-transform duration-200 ${
|
||||
@ -117,19 +119,21 @@ function Dashboard() {
|
||||
</button>
|
||||
|
||||
{expandedFolders[folderName] && (
|
||||
<div className="border-t border-gray-700">
|
||||
<div className="border-t border-neutral-800">
|
||||
{docs.map((doc) => (
|
||||
<div
|
||||
key={doc.id}
|
||||
className="px-6 py-3 flex items-center justify-between hover:bg-white/5 transition-colors"
|
||||
className="px-6 py-3 flex items-center justify-between
|
||||
hover:bg-primary-900/20 transition-colors"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<FiFile className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-200">{doc.title}</span>
|
||||
<FiFile className="w-4 h-4 text-neutral-400" />
|
||||
<span className="text-neutral-200">{doc.title}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDownload(doc.s3Key)}
|
||||
className="p-2 text-gray-400 hover:text-blue-400 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||
className="p-2 text-neutral-400 hover:text-primary-400 rounded-lg
|
||||
hover:bg-primary-500/10 transition-colors"
|
||||
title="Download document"
|
||||
>
|
||||
<FiDownload className="w-4 h-4" />
|
||||
@ -141,8 +145,9 @@ function Dashboard() {
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-400">No documents available</p>
|
||||
<div className="text-center py-12 bg-neutral-900/80 backdrop-blur-lg rounded-xl
|
||||
border border-neutral-800">
|
||||
<p className="text-neutral-400">No documents available</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<footer
|
||||
className="text-center text-white bg-gradient-to-b from-cyan-900 to-cyan-800 lg:text-left">
|
||||
<footer className="bg-gradient-to-br from-primary-900 to-primary-700 text-white">
|
||||
{/* Main Footer Content */}
|
||||
<div className="max-w-7xl mx-auto px-6 py-12">
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-4">
|
||||
{/* Company Info */}
|
||||
<div className="space-y-4">
|
||||
<h6 className="text-lg font-semibold text-white">
|
||||
Испитување материјали и консултантство
|
||||
</h6>
|
||||
<p className="text-neutral-300 leading-relaxed">
|
||||
Вашиот партнер во контролата на безбедно градење и квалитетно живеење
|
||||
</p>
|
||||
<img
|
||||
src="/imklogorgb.png"
|
||||
alt="IMK Logo"
|
||||
className="h-12 w-auto mt-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="mx-6 py-10 text-center md:text-left">
|
||||
<div className="grid-1 grid gap-8 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="">
|
||||
<h6
|
||||
className="mb-4 flex items-center justify-center font-semibold uppercase md:justify-start">
|
||||
|
||||
Испитување материјали и консултантство
|
||||
</h6>
|
||||
<p>
|
||||
Вашиот партнер во контролата на безбедно градење и квалитетно живеење
|
||||
</p>
|
||||
</div>
|
||||
<div className="">
|
||||
<h6
|
||||
className="mb-4 flex justify-center font-semibold uppercase md:justify-start">
|
||||
Услуги
|
||||
</h6>
|
||||
<NavLink to={'lab'}>
|
||||
<p className="mb-4">
|
||||
<a href="#!" className="text-neutral-600 dark:text-neutral-200"
|
||||
>Лабораториски анализи</a
|
||||
>
|
||||
</p>
|
||||
{/* Services */}
|
||||
<div className="space-y-4">
|
||||
<h6 className="text-lg font-semibold text-white">
|
||||
Услуги
|
||||
</h6>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<NavLink
|
||||
to="/lab"
|
||||
className="text-neutral-300 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Лабораториски анализи
|
||||
</NavLink>
|
||||
<NavLink to={'ultrasound'}>
|
||||
<p className="mb-4">
|
||||
<a href="#!" className="text-neutral-600 dark:text-neutral-200"
|
||||
>Испитување со ултразвук</a
|
||||
>
|
||||
</p>
|
||||
<NavLink
|
||||
to="/ultrasound"
|
||||
className="text-neutral-300 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Испитување со ултразвук
|
||||
</NavLink>
|
||||
<NavLink to={'consulting'}>
|
||||
<p className="mb-4">
|
||||
<a href="#!" className="text-neutral-600 dark:text-neutral-200"
|
||||
>Консултатски услуги</a
|
||||
>
|
||||
</p>
|
||||
<NavLink
|
||||
to="/consulting"
|
||||
className="text-neutral-300 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Консултатски услуги
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="">
|
||||
<h6
|
||||
className="mb-4 flex justify-center font-semibold uppercase md:justify-start">
|
||||
Корисни линкови
|
||||
</h6>
|
||||
<p className="mb-4">
|
||||
<a href="#!" className="text-neutral-600 dark:text-neutral-200"
|
||||
>Безбедно градење</a
|
||||
</div>
|
||||
|
||||
{/* Useful Links */}
|
||||
<div className="space-y-4">
|
||||
<h6 className="text-lg font-semibold text-white">
|
||||
Корисни линкови
|
||||
</h6>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<a
|
||||
href="#"
|
||||
className="text-neutral-300 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Безбедно градење
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="text-neutral-300 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Контрола на квалитет
|
||||
</a>
|
||||
<NavLink
|
||||
to="/certificates"
|
||||
className="text-neutral-300 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Сертификати
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="space-y-4">
|
||||
<h6 className="text-lg font-semibold text-white">
|
||||
Контакт
|
||||
</h6>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div className="flex items-center space-x-3 text-neutral-300">
|
||||
<FiMapPin className="h-5 w-5 text-primary-400" />
|
||||
<span>16-та Македонска бригада бр. 18</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3 text-neutral-300">
|
||||
<FiMail className="h-5 w-5 text-primary-400" />
|
||||
<a
|
||||
href="mailto:stanko@imk.mk"
|
||||
className="hover:text-primary-400 transition-colors"
|
||||
>
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
<a href="#!" className="text-neutral-600 dark:text-neutral-200"
|
||||
>Контрола на квалитет</a
|
||||
stanko@imk.mk
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3 text-neutral-300">
|
||||
<FiMail className="h-5 w-5 text-primary-400" />
|
||||
<a
|
||||
href="mailto:info.imkmk@gmail.com"
|
||||
className="hover:text-primary-400 transition-colors"
|
||||
>
|
||||
</p>
|
||||
<NavLink to="/certificates" >
|
||||
<p className="mb-4">
|
||||
<a>Сертификати</a>
|
||||
</p>
|
||||
</NavLink>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h6
|
||||
className="mb-4 flex justify-center font-semibold uppercase md:justify-start">
|
||||
Контакт
|
||||
</h6>
|
||||
<p className="mb-4 flex items-center justify-center md:justify-start">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="mr-3 h-5 w-5">
|
||||
<path
|
||||
d="M11.47 3.84a.75.75 0 011.06 0l8.69 8.69a.75.75 0 101.06-1.06l-8.689-8.69a2.25 2.25 0 00-3.182 0l-8.69 8.69a.75.75 0 001.061 1.06l8.69-8.69z" />
|
||||
<path
|
||||
d="M12 5.432l8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 01-.75-.75v-4.5a.75.75 0 00-.75-.75h-3a.75.75 0 00-.75.75V21a.75.75 0 01-.75.75H5.625a1.875 1.875 0 01-1.875-1.875v-6.198a2.29 2.29 0 00.091-.086L12 5.43z" />
|
||||
</svg>
|
||||
16-та Македонска бригада бр. 18
|
||||
</p>
|
||||
<p className="mb-4 flex items-center justify-center md:justify-start">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="mr-3 h-5 w-5">
|
||||
<path
|
||||
d="M1.5 8.67v8.58a3 3 0 003 3h15a3 3 0 003-3V8.67l-8.928 5.493a3 3 0 01-3.144 0L1.5 8.67z" />
|
||||
<path
|
||||
d="M22.5 6.908V6.75a3 3 0 00-3-3h-15a3 3 0 00-3 3v.158l9.714 5.978a1.5 1.5 0 001.572 0L22.5 6.908z" />
|
||||
</svg>
|
||||
stanko@imk.mk
|
||||
</p>
|
||||
<p className="mb-4 flex items-center justify-center md:justify-start">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="mr-3 h-5 w-5">
|
||||
<path
|
||||
d="M1.5 8.67v8.58a3 3 0 003 3h15a3 3 0 003-3V8.67l-8.928 5.493a3 3 0 01-3.144 0L1.5 8.67z" />
|
||||
<path
|
||||
d="M22.5 6.908V6.75a3 3 0 00-3-3h-15a3 3 0 00-3 3v.158l9.714 5.978a1.5 1.5 0 001.572 0L22.5 6.908z" />
|
||||
</svg>
|
||||
info.imkmk@gmail.com
|
||||
</p>
|
||||
<p className="mb-4 flex items-center justify-center md:justify-start">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="mr-3 h-5 w-5">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M1.5 4.5a3 3 0 013-3h1.372c.86 0 1.61.586 1.819 1.42l1.105 4.423a1.875 1.875 0 01-.694 1.955l-1.293.97c-.135.101-.164.249-.126.352a11.285 11.285 0 006.697 6.697c.103.038.25.009.352-.126l.97-1.293a1.875 1.875 0 011.955-.694l4.423 1.105c.834.209 1.42.959 1.42 1.82V19.5a3 3 0 01-3 3h-2.25C8.552 22.5 1.5 15.448 1.5 6.75V4.5z"
|
||||
clipRule="evenodd" />
|
||||
</svg>
|
||||
+ 389 70 279 877
|
||||
</p>
|
||||
|
||||
info.imkmk@gmail.com
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3 text-neutral-300">
|
||||
<FiPhone className="h-5 w-5 text-primary-400" />
|
||||
<a
|
||||
href="tel:+38970279877"
|
||||
className="hover:text-primary-400 transition-colors"
|
||||
>
|
||||
+ 389 70 279 877
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-neutral-200 p-6 text-center dark:bg-neutral-700">
|
||||
<span>© 2023 Copyright:</span>
|
||||
<a
|
||||
className="font-semibold text-neutral-600 dark:text-neutral-400"
|
||||
href="https://tailwind-elements.com/"
|
||||
> IMK</a
|
||||
>
|
||||
{/* Copyright Bar */}
|
||||
<div className="border-t border-primary-800">
|
||||
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||
<div className="flex flex-col md:flex justify-between items-center space-y-2 md:space-y-0">
|
||||
<div className="text-neutral-400 text-sm">
|
||||
© {new Date().getFullYear()} IMK. Сите права се задржани.
|
||||
</div>
|
||||
{/* <div className="flex items-center space-x-4">
|
||||
<a
|
||||
href="/privacy"
|
||||
className="text-sm text-neutral-400 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Политика на приватност
|
||||
</a>
|
||||
<a
|
||||
href="/terms"
|
||||
className="text-sm text-neutral-400 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Услови за користење
|
||||
</a>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</footer >
|
||||
|
||||
</>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div className="min-h-screen bg-gradient-to-b from-cyan-900 to-cyan-800 py-20">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-900 to-primary-800">
|
||||
{/* Hero Section */}
|
||||
<div className="container mx-auto px-4 mb-16 my-20">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
|
||||
Нашата Галерија
|
||||
</h1>
|
||||
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||
Погледнете ја нашата опрема и работен процес преку слики
|
||||
</p>
|
||||
<section className="relative h-[20vh] overflow-hidden">
|
||||
{/* <div className="absolute inset-0 bg-pattern opacity-5"></div> */}
|
||||
<div className="relative max-w-[90rem] mx-auto px-4 sm:px-6 lg:px-8 h-full flex items-center justify-center text-center">
|
||||
<div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-4xl md:text-5xl font-display font-bold mb-6 text-white"
|
||||
>
|
||||
Нашата Галерија
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="text-xl text-neutral-300 max-w-3xl mx-auto"
|
||||
>
|
||||
Погледнете ја нашата опрема и работен процес преку слики
|
||||
</motion.p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
||||
</div>
|
||||
) : (
|
||||
<motion.div
|
||||
className="container mx-auto px-4"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{images.map((image) => (
|
||||
<motion.div
|
||||
key={image.id}
|
||||
className=""
|
||||
variants={itemVariants}
|
||||
{/* Gallery Section */}
|
||||
<section className="py-16">
|
||||
<div className="max-w-[90rem] mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Category Filter */}
|
||||
<div className="flex flex-wrap gap-3 justify-center mb-12">
|
||||
<Button
|
||||
variant={activeCategory === 'all' ? 'primary' : 'secondary'}
|
||||
onClick={() => setActiveCategory('all')}
|
||||
className={`min-w-[100px] ${
|
||||
activeCategory === 'all'
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-white/10 text-white hover:bg-white/20'
|
||||
}`}
|
||||
>
|
||||
Сите
|
||||
</Button>
|
||||
{categories.map(category => (
|
||||
<Button
|
||||
key={category}
|
||||
variant={activeCategory === category ? 'primary' : 'secondary'}
|
||||
onClick={() => setActiveCategory(category)}
|
||||
className={`min-w-[100px] ${
|
||||
activeCategory === category
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-white/10 text-white hover:bg-white/20'
|
||||
}`}
|
||||
>
|
||||
<div className="aspect-w-16 aspect-h-12 rounded-xl overflow-hidden bg-white/10 backdrop-blur-lg">
|
||||
<img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
className="w-full h-full object-cover transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<div className="absolute bottom-0 left-0 right-0 p-4">
|
||||
<p className="text-white text-sm">{image.alt}</p>
|
||||
<span className="text-blue-400 text-xs">{image.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedImage(image)}
|
||||
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-blue-500 p-3 rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
>
|
||||
{/* <FiZoomIn className="w-6 h-6 text-white" /> */}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
{category}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="loading-spinner text-primary-400" />
|
||||
</div>
|
||||
) : (
|
||||
<motion.div
|
||||
layout
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6 lg:gap-8"
|
||||
>
|
||||
<AnimatePresence>
|
||||
{filteredImages.map((image) => (
|
||||
<motion.div
|
||||
key={image.id}
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Card
|
||||
padding="none"
|
||||
className="group cursor-pointer overflow-hidden bg-primary-800/50
|
||||
backdrop-blur-sm border border-primary-700/50 hover:border-primary-600/50
|
||||
transition-all duration-300"
|
||||
onClick={() => setSelectedImage(image)}
|
||||
>
|
||||
<div className="relative aspect-[4/3]">
|
||||
<img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
className="absolute inset-0 w-full h-full object-cover transition-transform
|
||||
duration-300 group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-primary-900/90
|
||||
via-primary-900/30 to-transparent opacity-0 group-hover:opacity-100
|
||||
transition-opacity duration-300">
|
||||
<div className="absolute bottom-0 left-0 right-0 p-4">
|
||||
<p className="text-white font-medium">{image.alt}</p>
|
||||
<span className="text-primary-300 text-sm">{image.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center
|
||||
opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<div className="bg-primary-600 p-3 rounded-full shadow-lg">
|
||||
<MagnifyingGlassIcon className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Image Modal */}
|
||||
{selectedImage && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
>
|
||||
<div className="relative max-w-4xl w-full">
|
||||
<img
|
||||
src={selectedImage.src}
|
||||
alt={selectedImage.alt}
|
||||
className="w-full h-auto rounded-xl"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setSelectedImage(null)}
|
||||
className="absolute top-4 right-4 text-white hover:text-blue-400"
|
||||
<AnimatePresence>
|
||||
{selectedImage && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4 md:p-8"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
className="relative max-w-7xl w-full"
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
src={selectedImage.src}
|
||||
alt={selectedImage.alt}
|
||||
className="w-full h-auto rounded-lg shadow-2xl"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="absolute top-4 right-4 text-white hover:bg-white/10"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
>
|
||||
<XMarkIcon className="w-6 h-6" />
|
||||
</Button>
|
||||
<div className="absolute bottom-4 left-4 text-white">
|
||||
<h3 className="text-lg font-medium">{selectedImage.alt}</h3>
|
||||
<p className="text-sm text-primary-200">{selectedImage.category}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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%;
|
||||
|
||||
157
frontend/src/components/login/Login.jsx
Normal file
157
frontend/src/components/login/Login.jsx
Normal file
@ -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 (
|
||||
<div className="relative min-h-screen bg-gradient-to-br from-primary-900 to-primary-800 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
{/* Pattern overlay with lower z-index */}
|
||||
<div className="absolute inset-0 bg-pattern opacity-5 z-0"></div>
|
||||
|
||||
{/* 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
|
||||
border border-neutral-800 rounded-xl">
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-2xl font-semibold text-white">Sign in to your account</h2>
|
||||
<p className="text-sm text-neutral-300 mt-2">Welcome back! Please enter your details.</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-6 p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
|
||||
<p className="text-red-200 text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<FormGroup>
|
||||
<Label htmlFor="username" className="text-white">
|
||||
Username
|
||||
</Label>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Enter your username"
|
||||
value={formData.username}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="password" className="text-white">
|
||||
Password
|
||||
</Label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => navigate('/forgot-password')}
|
||||
className="text-neutral-300 hover:text-white"
|
||||
>
|
||||
Forgot password?
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
required
|
||||
placeholder="Enter your password"
|
||||
value={formData.password}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
fullWidth
|
||||
disabled={isLoading}
|
||||
className="bg-primary-600 hover:bg-primary-700 focus:ring-primary-500
|
||||
disabled:bg-primary-800 disabled:cursor-not-allowed
|
||||
transition-all duration-200 ease-in-out"
|
||||
>
|
||||
{isLoading ? (
|
||||
<span className="flex items-center justify-center">
|
||||
<svg
|
||||
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
Signing in...
|
||||
</span>
|
||||
) : (
|
||||
'Sign in'
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-neutral-300">
|
||||
Don't have an account?{' '}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => navigate('/register')}
|
||||
className="text-primary-400 hover:text-primary-300"
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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 (
|
||||
// <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
// <div className="max-w-md w-full space-y-8">
|
||||
// <div>
|
||||
// <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
// Sign in to your account
|
||||
// </h2>
|
||||
// </div>
|
||||
// <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
// {error && (
|
||||
// <div className="text-red-500 text-center text-sm">
|
||||
// {error}
|
||||
// </div>
|
||||
// )}
|
||||
// <div className="rounded-md shadow-sm -space-y-px">
|
||||
// <div>
|
||||
// <input
|
||||
// type="text"
|
||||
// required
|
||||
// className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
// placeholder="Username"
|
||||
// value={username}
|
||||
// onChange={(e) => setUsername(e.target.value)}
|
||||
// />
|
||||
// </div>
|
||||
// <div>
|
||||
// <input
|
||||
// type="password"
|
||||
// required
|
||||
// className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
// placeholder="Password"
|
||||
// value={password}
|
||||
// onChange={(e) => setPassword(e.target.value)}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div>
|
||||
// <button
|
||||
// type="submit"
|
||||
// className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
// >
|
||||
// Sign in
|
||||
// </button>
|
||||
// </div>
|
||||
// </form>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// 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 (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800">
|
||||
<div className="w-full max-w-md px-8 py-10 bg-white/5 backdrop-blur-lg rounded-2xl shadow-2xl">
|
||||
<div className="mb-10 text-center">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Welcome Back</h1>
|
||||
<p className="text-gray-400">Please sign in to continue</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{error && (
|
||||
<div className="p-3 text-sm text-red-200 bg-red-500/10 border border-red-500/20 rounded-lg">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="relative">
|
||||
<FiUser className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
className="w-full px-10 py-3 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white placeholder-gray-400 transition-colors"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<FiLock className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
className="w-full px-10 py-3 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white placeholder-gray-400 transition-colors"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200 flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-gray-400">
|
||||
Need help? Contact your administrator
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
@ -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 (
|
||||
<div className="absolute w-full z-50 py-10">
|
||||
<header className="relative bg-transparent">
|
||||
<nav
|
||||
className="container mx-auto flex items-center justify-between p-6 lg:px-8"
|
||||
aria-label="Global"
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="flex lg:flex-1">
|
||||
<a href="/" className="flex items-center -m-1.5 p-1.5">
|
||||
<div className="w-full bg-gradient-to-b from-primary-900 to-primary-800 border-b border-primary-700/50">
|
||||
<header className="relative">
|
||||
<nav className="container-base py-4" aria-label="Global">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Left Logo */}
|
||||
<Link to="/" className="p-2 hover:bg-white/20 transition-colors">
|
||||
<img
|
||||
className="h-12 w-auto hover:scale-105 transition-transform duration-300"
|
||||
className="h-10 w-auto transition-transform duration-300 hover:scale-105"
|
||||
src="/imklogorgb.png"
|
||||
alt="IMK logo"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<div className="flex lg:hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-white hover:bg-white/10 transition-colors duration-300"
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
>
|
||||
<span className="sr-only">Open main menu</span>
|
||||
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Centered Desktop Navigation */}
|
||||
<div className="hidden lg:flex flex-grow justify-center mx-8">
|
||||
<div className="flex items-center gap-x-1">
|
||||
{navigation.map((item) => (
|
||||
<NavLink
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={({ isActive }) =>
|
||||
`px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
||||
${isActive
|
||||
? 'bg-primary-700 text-white shadow-lg'
|
||||
: 'text-neutral-200 hover:bg-primary-700/50 hover:text-white'}`
|
||||
}
|
||||
>
|
||||
{item.name}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop navigation */}
|
||||
<div className="hidden lg:flex lg:gap-x-12">
|
||||
{navigation.map((item) => (
|
||||
<NavLink
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={({ isActive }) =>
|
||||
`text-base font-semibold leading-6 transition-all duration-300
|
||||
${isActive
|
||||
? 'text-white border-b-2 border-white'
|
||||
: 'text-gray-200 hover:text-white hover:border-b-2 hover:border-white/50'
|
||||
}`
|
||||
}
|
||||
{/* Right Side - TUF Logo & Mobile Menu */}
|
||||
<div className="flex items-center gap-x-4">
|
||||
{/* TUF Logo */}
|
||||
<a href="/" className="hidden lg:flex 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>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="lg:hidden text-white hover:bg-primary-700/50"
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
>
|
||||
{item.name}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* TUF Logo */}
|
||||
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
|
||||
<a href="/" className="flex items-center -m-1.5 p-1.5">
|
||||
<img
|
||||
className="h-12 w-auto hover:scale-105 transition-transform duration-300"
|
||||
src="/tuf.png"
|
||||
alt="TUF logo"
|
||||
/>
|
||||
</a>
|
||||
<span className="sr-only">Open main menu</span>
|
||||
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -84,45 +81,43 @@ export default function Navbar() {
|
||||
open={mobileMenuOpen}
|
||||
onClose={setMobileMenuOpen}
|
||||
>
|
||||
<div className="fixed inset-0 z-50" />
|
||||
<Dialog.Panel className="fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-gradient-to-b from-blue-900 to-blue-800 px-6 py-6 sm:max-w-sm">
|
||||
<div className="fixed inset-0 z-50 bg-neutral-900/50 backdrop-blur-sm" />
|
||||
<Dialog.Panel className="fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-primary-900 px-6 py-6 sm:max-w-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<a href="/" className="-m-1.5 p-1.5">
|
||||
<Link to="/" className="bg-white/10 rounded-lg p-2">
|
||||
<img
|
||||
className="h-8 w-auto"
|
||||
src="/imklogorgb.png"
|
||||
alt="IMK logo"
|
||||
/>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
className="-m-2.5 rounded-md p-2.5 text-white hover:bg-white/10 transition-colors duration-300"
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="text-white hover:bg-primary-700/50"
|
||||
>
|
||||
<span className="sr-only">Close menu</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-6 flow-root">
|
||||
<div className="-my-6 divide-y divide-white/10">
|
||||
<div className="space-y-2 py-6">
|
||||
{navigation.map((item) => (
|
||||
<NavLink
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={({ isActive }) =>
|
||||
`-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 transition-colors duration-300
|
||||
${isActive
|
||||
? 'text-white bg-white/10'
|
||||
: 'text-gray-200 hover:bg-white/5 hover:text-white'
|
||||
}`
|
||||
}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{item.name}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
<div className="space-y-1 py-6">
|
||||
{navigation.map((item) => (
|
||||
<NavLink
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={({ isActive }) =>
|
||||
`block px-3 py-2 text-base font-medium rounded-lg transition-colors
|
||||
${isActive
|
||||
? 'bg-primary-700 text-white'
|
||||
: 'text-neutral-200 hover:bg-primary-700/50 hover:text-white'
|
||||
}`
|
||||
}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{item.name}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
|
||||
@ -1,3 +1,308 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://api.fontshare.com/v2/css?f[]=clash-display@400,500,600,700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
/* Base styles */
|
||||
html {
|
||||
@apply antialiased scroll-smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-neutral-50 text-neutral-800 font-sans;
|
||||
}
|
||||
|
||||
/* Typography base styles */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-display;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-primary-400 ring-offset-2;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Layout Components */
|
||||
.container-base {
|
||||
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
.section-padding {
|
||||
@apply py-12 md:py-16 lg:py-24;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
@apply flex items-center justify-center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
@apply flex items-center justify-between;
|
||||
}
|
||||
|
||||
/* Card Variants */
|
||||
.card {
|
||||
@apply bg-white rounded-lg shadow-sm border border-neutral-200/80 transition-all duration-200 hover:shadow-md hover:border-neutral-300/80;
|
||||
}
|
||||
|
||||
.card-interactive {
|
||||
@apply card cursor-pointer hover:border-primary-200;
|
||||
}
|
||||
|
||||
.card-padding-sm {
|
||||
@apply p-4;
|
||||
}
|
||||
|
||||
.card-padding-base {
|
||||
@apply p-6;
|
||||
}
|
||||
|
||||
.card-padding-lg {
|
||||
@apply p-8;
|
||||
}
|
||||
|
||||
/* Button Variants */
|
||||
.btn-base {
|
||||
@apply 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;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply btn-base bg-primary-600 text-white
|
||||
hover:bg-primary-700
|
||||
focus:ring-primary-400
|
||||
shadow-sm border border-transparent;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply btn-base bg-neutral-100 text-neutral-800
|
||||
hover:bg-neutral-200
|
||||
border border-neutral-200
|
||||
focus:ring-primary-400;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
@apply btn-base bg-transparent text-primary-600
|
||||
hover:bg-primary-50
|
||||
border border-primary-600
|
||||
focus:ring-primary-400;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
@apply btn-base bg-red-600 text-white hover:bg-red-700
|
||||
focus:ring-red-500 shadow-sm border border-transparent;
|
||||
}
|
||||
|
||||
/* Button Sizes */
|
||||
.btn-sm {
|
||||
@apply px-3 py-1.5 text-sm;
|
||||
}
|
||||
|
||||
.btn-base {
|
||||
@apply px-4 py-2 text-sm;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
@apply px-6 py-3 text-base;
|
||||
}
|
||||
|
||||
/* Form Elements */
|
||||
.form-group {
|
||||
@apply space-y-1;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
@apply block text-sm font-medium text-neutral-700 mb-1;
|
||||
}
|
||||
|
||||
.input-base {
|
||||
@apply block w-full rounded-lg
|
||||
border-neutral-300
|
||||
shadow-sm
|
||||
focus:border-primary-500 focus:ring-primary-400
|
||||
disabled:bg-neutral-100 disabled:cursor-not-allowed
|
||||
placeholder:text-neutral-400
|
||||
sm:text-sm;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
@apply border-red-300 text-red-900 placeholder-red-300
|
||||
focus:border-red-500 focus:ring-red-500;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
@apply text-sm text-red-600 mt-1;
|
||||
}
|
||||
|
||||
.input-help {
|
||||
@apply text-sm text-neutral-500 mt-1;
|
||||
}
|
||||
|
||||
/* Typography Utilities */
|
||||
.heading-1 {
|
||||
@apply text-4xl sm:text-5xl lg:text-6xl font-display font-bold text-neutral-900 tracking-tight;
|
||||
}
|
||||
|
||||
.heading-2 {
|
||||
@apply text-3xl sm:text-4xl font-display font-semibold text-neutral-900;
|
||||
}
|
||||
|
||||
.heading-3 {
|
||||
@apply text-2xl font-display font-semibold text-neutral-900;
|
||||
}
|
||||
|
||||
.heading-4 {
|
||||
@apply text-xl font-display font-semibold text-neutral-900;
|
||||
}
|
||||
|
||||
.text-body-lg {
|
||||
@apply text-lg text-neutral-600;
|
||||
}
|
||||
|
||||
.text-body {
|
||||
@apply text-base text-neutral-600;
|
||||
}
|
||||
|
||||
.text-body-sm {
|
||||
@apply text-sm text-neutral-600;
|
||||
}
|
||||
|
||||
/* Badge Variants */
|
||||
.badge-base {
|
||||
@apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
@apply badge-base bg-primary-100 text-primary-800;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
@apply badge-base bg-success-100 text-success-800;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
@apply badge-base bg-warning-100 text-warning-800;
|
||||
}
|
||||
|
||||
.badge-error {
|
||||
@apply badge-base bg-error-100 text-error-800;
|
||||
}
|
||||
|
||||
/* Table Styles */
|
||||
.table-container {
|
||||
@apply overflow-x-auto rounded-lg border border-neutral-200 bg-white;
|
||||
}
|
||||
|
||||
.table-base {
|
||||
@apply min-w-full divide-y divide-neutral-200;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
@apply bg-neutral-50 text-left text-sm font-medium text-neutral-600;
|
||||
}
|
||||
|
||||
.table-header-cell {
|
||||
@apply px-6 py-3;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
@apply divide-y divide-neutral-200 bg-white;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
@apply hover:bg-neutral-50 transition-colors duration-150;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
@apply whitespace-nowrap px-6 py-4 text-sm text-neutral-700;
|
||||
}
|
||||
|
||||
/* Grid Layouts */
|
||||
.grid-cards {
|
||||
@apply grid gap-6 sm:grid-cols-2 lg:grid-cols-3;
|
||||
}
|
||||
|
||||
.grid-cards-4 {
|
||||
@apply grid gap-6 sm:grid-cols-2 lg:grid-cols-4;
|
||||
}
|
||||
|
||||
/* Animation Utilities */
|
||||
.animate-fade-in {
|
||||
@apply transition-opacity duration-300 ease-in-out;
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
@apply transition-all duration-300 ease-in-out transform;
|
||||
}
|
||||
|
||||
/* Loading States */
|
||||
.loading-spinner {
|
||||
@apply animate-spin rounded-full border-4 border-neutral-200 border-t-primary-600 h-8 w-8;
|
||||
}
|
||||
|
||||
.loading-pulse {
|
||||
@apply animate-pulse bg-neutral-200 rounded;
|
||||
}
|
||||
|
||||
/* Overlay/Modal */
|
||||
.overlay {
|
||||
@apply fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm transition-opacity;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
@apply fixed inset-0 z-50 overflow-y-auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
@apply relative transform overflow-hidden
|
||||
rounded-xl bg-white
|
||||
shadow-xl transition-all
|
||||
border border-neutral-200;
|
||||
}
|
||||
|
||||
/* Navigation styles */
|
||||
.nav-link {
|
||||
@apply text-neutral-600 hover:text-primary-600
|
||||
transition-colors duration-200
|
||||
px-3 py-2 rounded-lg
|
||||
hover:bg-primary-50;
|
||||
}
|
||||
|
||||
.nav-link-active {
|
||||
@apply text-primary-600 bg-primary-50;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
@layer utilities {
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.scrollbar-hidden {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.scrollbar-hidden::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for Webkit browsers */
|
||||
.scrollbar-custom::-webkit-scrollbar {
|
||||
@apply w-2;
|
||||
}
|
||||
|
||||
.scrollbar-custom::-webkit-scrollbar-track {
|
||||
@apply bg-neutral-100 rounded-full;
|
||||
}
|
||||
|
||||
.scrollbar-custom::-webkit-scrollbar-thumb {
|
||||
@apply bg-neutral-300 rounded-full hover:bg-neutral-400;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { motion } from "framer-motion";
|
||||
import { SectionHeader } from "../../shared/SectionHeader";
|
||||
import { FiTrendingUp, FiAward, FiCheckCircle } from "react-icons/fi";
|
||||
import { Button, Card } from "../../components/UI";
|
||||
|
||||
const milestones = [
|
||||
{ year: "2008", title: "Основање на компанијата" },
|
||||
@ -12,120 +11,157 @@ const milestones = [
|
||||
{ year: "2023", title: "Проширување на тимот" },
|
||||
];
|
||||
|
||||
const values = [
|
||||
{
|
||||
title: "Квалитет",
|
||||
description: "Посветени сме на обезбедување највисок квалитет во сите наши услуги",
|
||||
color: "text-primary-500",
|
||||
},
|
||||
{
|
||||
title: "Интегритет",
|
||||
description: "Работиме со целосна транспарентност и професионална етика",
|
||||
color: "text-primary-500",
|
||||
},
|
||||
{
|
||||
title: "Иновација",
|
||||
description: "Постојано инвестираме во нови технологии и методи",
|
||||
color: "text-primary-500",
|
||||
},
|
||||
{
|
||||
title: "Експертиза",
|
||||
description: "Нашиот тим се состои од високо квалификувани професионалци",
|
||||
color: "text-primary-500",
|
||||
},
|
||||
{
|
||||
title: "Одговорност",
|
||||
description: "Преземаме целосна одговорност за квалитетот на нашата работа",
|
||||
color: "text-primary-500",
|
||||
},
|
||||
];
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<div className="isolate bg-graymin-h-screen bg-gradient-to-b from-cyan-900 to-cyan-400">
|
||||
<div className="min-h-screen">
|
||||
{/* Hero Section */}
|
||||
<div className="relative h-screen overflow-hidden">
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/90 to-blue-600/80" />
|
||||
</div>
|
||||
<div className="relative container mx-auto px-4 h-full flex items-center">
|
||||
<section className="relative h-screen overflow-hidden bg-gradient-to-br from-primary-900 to-primary-700">
|
||||
<div className="absolute inset-0 bg-black/20" />
|
||||
<div className="relative container-base h-full flex items-center">
|
||||
<div className="max-w-3xl">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, ease: "easeInOut" }}
|
||||
className="text-4xl md:text-6xl font-bold text-white mb-6"
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-4xl md:text-6xl font-display font-bold text-white mb-6"
|
||||
>
|
||||
За Нас
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.2, ease: "easeIn" }}
|
||||
className="text-xl text-gray-200 mb-8"
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="text-xl text-neutral-100 mb-8"
|
||||
>
|
||||
Повеќе од 15 години искуство во обезбедување квалитет
|
||||
</motion.p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Vision & Mission Section */}
|
||||
<section className="py-24 bg-neutral-50">
|
||||
<div className="container-base">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-display font-bold text-neutral-900 mb-4">
|
||||
Нашата Мисија
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-primary-500 mx-auto mb-6"></div>
|
||||
<p className="text-lg text-neutral-600 max-w-2xl mx-auto">
|
||||
Посветени сме на обезбедување највисок квалитет во нашата работа
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Vision & Values Section */}
|
||||
<div className="bg-white py-24">
|
||||
<div className="container bg-gradient-to-b from-cyan-900 to-cyan-400 rounded-xl mx-auto p-20">
|
||||
<SectionHeader
|
||||
title="Нашата Мисија"
|
||||
subtitle="Посветени сме на обезбедување највисок квалитет во нашата работа"
|
||||
className="text-white"
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-16">
|
||||
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8">
|
||||
<div className="flex items-center mb-4">
|
||||
<FiTrendingUp className="w-6 h-6 text-white mr-3" />
|
||||
<h3 className="text-2xl font-semibold text-white">Визија</h3>
|
||||
<Card className="bg-gradient-to-br from-primary-900 to-primary-700 text-white">
|
||||
<div className="flex items-center mb-6">
|
||||
<FiTrendingUp className="w-8 h-8 mr-4" />
|
||||
<h3 className="text-2xl font-display font-semibold">Визија</h3>
|
||||
</div>
|
||||
<p className="text-white text-xl">
|
||||
<p className="text-neutral-100 text-lg leading-relaxed">
|
||||
Нашата визија е да бидеме водечка компанија во областа на
|
||||
испитување на материјали и контрола на квалитет во Македонија.
|
||||
Стремиме кон постојано унапредување на нашите услуги преку
|
||||
имплементација на најсовремени технологии и методологии. Сакаме
|
||||
да воспоставиме нови стандарди во индустријата и да допринесеме
|
||||
за развојот на градежништвото и инженерството во регионот.
|
||||
имплементација на најсовремени технологии и методологии.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8">
|
||||
<div className="flex items-center mb-4">
|
||||
<FiAward className="w-6 h-6 text-white mr-3" />
|
||||
<h3 className="text-2xl font-semibold text-white">Вредности</h3>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-br from-primary-800 to-primary-600 text-white">
|
||||
<div className="flex items-center mb-6">
|
||||
<FiAward className="w-8 h-8 mr-4" />
|
||||
<h3 className="text-2xl font-display font-semibold">Вредности</h3>
|
||||
</div>
|
||||
<ul className="text-gray-300 space-y-3">
|
||||
<li className="flex items-start text-xl">
|
||||
<FiCheckCircle className="w-5 h-5 text-red mr-2 mt-1" />
|
||||
<div>
|
||||
<strong className="text-white">Квалитет</strong> - Посветени
|
||||
сме на обезбедување највисок квалитет во сите наши услуги
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
|
||||
<div>
|
||||
<strong className="text-white">Интегритет</strong> -
|
||||
Работиме со целосна транспарентност и професионална етика
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
|
||||
<div>
|
||||
<strong className="text-white">Иновација</strong> -
|
||||
Постојано инвестираме во нови технологии и методи
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
|
||||
<div>
|
||||
<strong className="text-white">Експертиза</strong> - Нашиот
|
||||
тим се состои од високо квалификувани професионалци
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<FiCheckCircle className="w-5 h-5 text-blue-400 mr-2 mt-1" />
|
||||
<div>
|
||||
<strong className="text-white">Одговорност</strong> -
|
||||
Преземаме целосна одговорност за квалитетот на нашата работа
|
||||
</div>
|
||||
</li>
|
||||
<ul className="space-y-4">
|
||||
{values.map((value, index) => (
|
||||
<li key={index} className="flex items-start">
|
||||
<FiCheckCircle className="w-5 h-5 mr-3 mt-1" />
|
||||
<div>
|
||||
<strong className="block text-lg mb-1">{value.title}</strong>
|
||||
<span className="text-neutral-100">{value.description}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Milestones Section */}
|
||||
<section className="py-24 bg-white">
|
||||
<div className="container-base">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-display font-bold text-neutral-900 mb-4">
|
||||
Нашиот Пат
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-primary-500 mx-auto mb-6"></div>
|
||||
<p className="text-lg text-neutral-600 max-w-2xl mx-auto">
|
||||
Клучни моменти во нашиот развој
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{milestones.map((milestone, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className="text-center hover:-translate-y-1 transition-transform duration-300"
|
||||
>
|
||||
<div className="text-primary-600 text-3xl font-bold mb-2">
|
||||
{milestone.year}
|
||||
</div>
|
||||
<div className="text-neutral-800 text-lg font-medium">
|
||||
{milestone.title}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<div className="isolate bg-gray bg-gradient-to-b from-blue-700 to-blue-500 text-white py-24">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-8">
|
||||
<section className="py-24 bg-gradient-to-br from-primary-900 to-primary-700">
|
||||
<div className="container-base text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-display font-bold text-white mb-8">
|
||||
Започнете го вашиот проект со нас
|
||||
</h2>
|
||||
<NavLink
|
||||
<Button
|
||||
to="/contact"
|
||||
className="inline-block bg-white text-blue-600 px-8 py-3 rounded-full font-semibold hover:bg-gray-100 transition-colors duration-300"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="bg-white text-primary-600 hover:bg-neutral-50"
|
||||
>
|
||||
Контактирајте нѐ
|
||||
</NavLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,115 +1,228 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
EnvelopeIcon,
|
||||
PhoneIcon,
|
||||
MapPinIcon
|
||||
} from '@heroicons/react/24/outline'
|
||||
import { Button, Card, Input } from '../../components/UI'
|
||||
import { Switch } from '@headlessui/react'
|
||||
|
||||
const contactInfo = [
|
||||
{
|
||||
icon: PhoneIcon,
|
||||
title: 'Телефон',
|
||||
content: '+389 XX XXX XXX',
|
||||
link: 'tel:+389XXXXXXX'
|
||||
},
|
||||
{
|
||||
icon: EnvelopeIcon,
|
||||
title: 'Емаил',
|
||||
content: 'contact@imk.mk',
|
||||
link: 'mailto:contact@imk.mk'
|
||||
},
|
||||
{
|
||||
icon: MapPinIcon,
|
||||
title: 'Адреса',
|
||||
content: 'Скопје, Македонија',
|
||||
link: 'https://goo.gl/maps/your-location'
|
||||
}
|
||||
]
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
export default function Contact() {
|
||||
const [agreed, setAgreed] = useState(false)
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
company: '',
|
||||
email: '',
|
||||
message: ''
|
||||
})
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [submitStatus, setSubmitStatus] = useState(null)
|
||||
const [agreed, setAgreed] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="isolate bg-gray px-6 bg-gradient-to-b from-cyan-900 to-cyan-800 sm:py-32 lg:px-8">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
try {
|
||||
const response = await fetch('https://formsubmit.co/taratur@gmail.com', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
setSubmitStatus('success')
|
||||
setFormData({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
company: '',
|
||||
email: '',
|
||||
message: ''
|
||||
})
|
||||
} else {
|
||||
setSubmitStatus('error')
|
||||
}
|
||||
} catch (error) {
|
||||
setSubmitStatus('error')
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="isolate px-6 bg-gradient-to-b from-primary-900 to-primary-700 sm:py-32 lg:px-8 h-screen">
|
||||
<div className="mx-auto max-w-2xl text-center mt-10">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-white sm:text-3xl">
|
||||
Пишете ни порака | Закажете средба
|
||||
</h2>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="mx-auto mt-16 max-w-xl sm:mt-20"
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="first-name"
|
||||
className="block text-sm font-semibold leading-6 text-white"
|
||||
>
|
||||
|
||||
Име
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="text"
|
||||
name="first-name"
|
||||
id="first-name"
|
||||
autoComplete="given-name"
|
||||
value={formData.firstName}
|
||||
onChange={handleChange}
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
|
||||
ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
|
||||
focus:ring-2 focus:ring-inset focus:ring-primary-600
|
||||
sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-auto max-w-2xl text-center mt-10">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-gray-900 sm:text-3xl">Пишете ни порака | Закажете средба</h2>
|
||||
{/* <p className="mt-2 text-lg leading-8 text-gray-600">
|
||||
take a wheel or let it slide
|
||||
|
||||
</p> */}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="last-name"
|
||||
className="block text-sm font-semibold leading-6 text-white"
|
||||
>
|
||||
Презиме
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="text"
|
||||
name="last-name"
|
||||
id="last-name"
|
||||
autoComplete="family-name"
|
||||
value={formData.lastName}
|
||||
onChange={handleChange}
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
|
||||
ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
|
||||
focus:ring-2 focus:ring-inset focus:ring-primary-600
|
||||
sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
<form action="https://formsubmit.co/taratur@gmail.com" method="POST" className="mx-auto mt-16 max-w-xl sm:mt-20">
|
||||
<div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label htmlFor="first-name" className="block text-sm font-semibold leading-6 text-white">
|
||||
Име
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="text"
|
||||
name="first-name"
|
||||
id="first-name"
|
||||
autoComplete="given-name"
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="last-name" className="block text-sm font-semibold leading-6 text-white">
|
||||
Презиме
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="text"
|
||||
name="last-name"
|
||||
id="last-name"
|
||||
autoComplete="family-name"
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label htmlFor="company" className="block text-sm font-semibold leading-6 text-white">
|
||||
Компанија
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="text"
|
||||
name="company"
|
||||
id="company"
|
||||
autoComplete="organization"
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label htmlFor="email" className="block text-sm font-semibold leading-6 text-white">
|
||||
Мејл
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
autoComplete="email"
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label
|
||||
htmlFor="company"
|
||||
className="block text-sm font-semibold leading-6 text-white"
|
||||
>
|
||||
Компанија
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="text"
|
||||
name="company"
|
||||
id="company"
|
||||
autoComplete="organization"
|
||||
value={formData.company}
|
||||
onChange={handleChange}
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
|
||||
ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
|
||||
focus:ring-2 focus:ring-inset focus:ring-primary-600
|
||||
sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-semibold leading-6 text-white"
|
||||
>
|
||||
Мејл
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
autoComplete="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
|
||||
ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
|
||||
focus:ring-2 focus:ring-inset focus:ring-primary-600
|
||||
sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-2">
|
||||
<label htmlFor="message" className="block text-sm font-semibold leading-6 text-white">
|
||||
Порака
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<textarea
|
||||
name="message"
|
||||
id="message"
|
||||
rows={4}
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
defaultValue={''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch.Group as="div" className="flex gap-x-4 sm:col-span-2">
|
||||
</Switch.Group>
|
||||
</div>
|
||||
<input type="hidden" name="_next" value="https://imk.mk/"></input>
|
||||
<div className="mt-10">
|
||||
<button
|
||||
type="submit"
|
||||
className="block w-full rounded-md bg-gray-500 px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-sm hover:bg-gray-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500"
|
||||
>
|
||||
Испрати
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="sm:col-span-2">
|
||||
<label
|
||||
htmlFor="message"
|
||||
className="block text-sm font-semibold leading-6 text-white"
|
||||
>
|
||||
Порака
|
||||
</label>
|
||||
<div className="mt-2.5">
|
||||
<textarea
|
||||
name="message"
|
||||
id="message"
|
||||
rows={4}
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
|
||||
ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
|
||||
focus:ring-2 focus:ring-inset focus:ring-primary-600
|
||||
sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch.Group as="div" className="flex gap-x-4 sm:col-span-2">
|
||||
</Switch.Group>
|
||||
</div>
|
||||
)
|
||||
<input type="hidden" name="_next" value="https://imk.mk/"></input>
|
||||
<div className="mt-10">
|
||||
<button
|
||||
type="submit"
|
||||
className="block w-full rounded-md bg-primary-600 px-3.5 py-2.5 text-center text-sm
|
||||
font-semibold text-white shadow-sm hover:bg-primary-700
|
||||
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2
|
||||
focus-visible:outline-primary-600 transition-colors duration-200"
|
||||
>
|
||||
Испрати
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Link } from "react-router-dom";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRightIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { SectionHeader } from "../../shared/SectionHeader";
|
||||
import { Button, Card } from "../../components/UI";
|
||||
|
||||
const serviceCards = [
|
||||
{
|
||||
title: "Лабораториски услуги",
|
||||
@ -47,19 +48,17 @@ const serviceCards = [
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="isolate bg-graymin-h-screen bg-gradient-to-b from-cyan-900 to-cyan-400">
|
||||
{/* Hero Section with Parallax */}
|
||||
<div className="relative h-screen overflow-hidden">
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/90 to-blue-600/80" />
|
||||
</div>
|
||||
<div className="min-h-screen">
|
||||
{/* Hero Section */}
|
||||
<section className="relative h-screen overflow-hidden bg-gradient-to-br from-primary-900 to-primary-700">
|
||||
<div className="absolute inset-0 bg-black/20" />
|
||||
|
||||
<div className="relative container mx-auto px-4 h-full flex items-center">
|
||||
<div className="relative container-base h-full flex items-center">
|
||||
<div className="max-w-3xl">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-4xl md:text-6xl font-bold text-white mb-6"
|
||||
className="text-4xl md:text-6xl font-display font-bold text-white mb-6"
|
||||
>
|
||||
Вашиот партнер во контролата на безбедно градење
|
||||
</motion.h1>
|
||||
@ -67,7 +66,7 @@ export default function Home() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="text-xl text-gray-200 mb-8"
|
||||
className="text-xl text-neutral-100 mb-8"
|
||||
>
|
||||
Професионални лабораториски услуги со најсовремена опрема и
|
||||
експертиза
|
||||
@ -78,139 +77,130 @@ export default function Home() {
|
||||
transition={{ delay: 0.4 }}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<NavLink
|
||||
<Button
|
||||
to="/contact"
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white px-8 py-3 rounded-full font-semibold transition-colors duration-300"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="bg-white text-primary-600 hover:bg-neutral-100"
|
||||
>
|
||||
Контактирајте нѐ
|
||||
</NavLink>
|
||||
<NavLink
|
||||
</Button>
|
||||
<Button
|
||||
to="/about"
|
||||
className="bg-white/10 hover:bg-white/20 text-white px-8 py-3 rounded-full font-semibold transition-colors duration-300"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="text-white border-white hover:bg-white/10"
|
||||
>
|
||||
Дознајте повеќе
|
||||
</NavLink>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Section */}
|
||||
{/* <div className="container mx-auto px-4 py-16 ">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
{[
|
||||
{ number: "15+", label: "Години искуство" },
|
||||
{ number: "1000+", label: "Задоволни клиенти" },
|
||||
{ number: "5000+", label: "Завршени проекти" },
|
||||
{ number: "100%", label: "Задоволство" },
|
||||
].map((stat) => (
|
||||
<div key={stat.label} className="text-center">
|
||||
<div className="text-4xl font-bold text-white mb-2">{stat.number}</div>
|
||||
<div className="text-white">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
</section>
|
||||
|
||||
{/* Services Section */}
|
||||
<div className="bg-white py-24">
|
||||
<div className="container bg-gradient-to-b from-cyan-900 to-cyan-400 rounded-xl mx-auto p-20">
|
||||
{/* <div className="text-center mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||
<section className="bg-neutral-50 py-24">
|
||||
<div className="container-base">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-display font-bold text-neutral-900 mb-4">
|
||||
Нашите Услуги
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-blue-500 mx-auto"></div>
|
||||
</div> */}
|
||||
<SectionHeader
|
||||
title="Нашите Услуги"
|
||||
subtitle="Нашиот тим е спремен да ви помогне со нашите услуги"
|
||||
className="text-white"
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||
<div className="w-24 h-1 bg-primary-500 mx-auto mb-6"></div>
|
||||
<p className="text-lg text-neutral-600 max-w-2xl mx-auto">
|
||||
Нашиот тим е спремен да ви помогне со професионални решенија
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{serviceCards.map((card) => (
|
||||
<NavLink
|
||||
<Link
|
||||
key={card.title}
|
||||
to={card.link}
|
||||
className="group bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1"
|
||||
className="group"
|
||||
>
|
||||
<div className="relative h-56 overflow-hidden">
|
||||
<img
|
||||
src={card.image}
|
||||
alt={card.title}
|
||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
|
||||
<h3 className="absolute bottom-4 left-4 text-xl font-semibold text-white">
|
||||
{card.title}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<p className="text-gray-600 mb-4">{card.description}</p>
|
||||
<ul className="space-y-2">
|
||||
{card.services.slice(0, 3).map((service, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center text-gray-700"
|
||||
>
|
||||
<CheckCircleIcon className="h-5 w-5 text-blue-500 mr-2" />
|
||||
{service}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-4 flex items-center text-blue-500 font-semibold">
|
||||
Дознајте повеќе
|
||||
<ArrowRightIcon className="h-5 w-5 ml-2 group-hover:translate-x-2 transition-transform duration-300" />
|
||||
<Card
|
||||
className="h-full hover:-translate-y-1 transition-all duration-300"
|
||||
padding="none"
|
||||
>
|
||||
<div className="relative h-56 overflow-hidden">
|
||||
<img
|
||||
src={card.image}
|
||||
alt={card.title}
|
||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
|
||||
<h3 className="absolute bottom-4 left-4 text-xl font-semibold text-white">
|
||||
{card.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</NavLink>
|
||||
<div className="p-6">
|
||||
<p className="text-neutral-600 mb-4">{card.description}</p>
|
||||
<ul className="space-y-2">
|
||||
{card.services.slice(0, 3).map((service, index) => (
|
||||
<li key={index} className="flex items-center text-neutral-700">
|
||||
<CheckCircleIcon className="h-5 w-5 text-primary-500 mr-2" />
|
||||
{service}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-4 flex items-center text-primary-600 font-semibold">
|
||||
Дознајте повеќе
|
||||
<ArrowRightIcon className="h-5 w-5 ml-2 group-hover:translate-x-2 transition-transform duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Projects/Clients */}
|
||||
<div className="container mx-auto px-4 py-24">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-white mb-4">
|
||||
Наши Клиенти
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-blue-500 mx-auto mb-8"></div>
|
||||
<p className="text-white text-xl max-w-2xl mx-auto">
|
||||
Горди сме на довербата која ни ја укажуваат нашите клиенти
|
||||
</p>
|
||||
</div>
|
||||
{/* Clients Section */}
|
||||
{/* <section className="bg-gradient-to-br from-primary-900 to-primary-700 py-24">
|
||||
<div className="container-base">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-display font-bold text-white mb-4">
|
||||
Наши Клиенти
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-white mx-auto mb-8"></div>
|
||||
<p className="text-xl text-neutral-100 max-w-2xl mx-auto">
|
||||
Горди сме на довербата која ни ја укажуваат нашите клиенти
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
{/* Add client logos here */}
|
||||
{[1, 2, 3, 4].map((index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300"
|
||||
>
|
||||
<img
|
||||
src={`/client-${index}.png`}
|
||||
alt={`Client ${index}`}
|
||||
className="w-full h-20 object-contain grayscale hover:grayscale-0 transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
{[1, 2, 3, 4].map((index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className="p-8 hover:shadow-lg transition-shadow duration-300"
|
||||
>
|
||||
<img
|
||||
src={`/client-${index}.png`}
|
||||
alt={`Client ${index}`}
|
||||
className="w-full h-20 object-contain grayscale hover:grayscale-0 transition-all duration-300"
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section> */}
|
||||
|
||||
{/* CTA Section */}
|
||||
<div className="isolate bg-gray bg-gradient-to-b from-blue-700 to-blue-500 text-white py-24">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-8">
|
||||
{/* <section className="bg-gradient-to-br from-primary-800 to-primary-600 py-24">
|
||||
<div className="container-base text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-display font-bold text-white mb-8">
|
||||
Спремни сме да ви помогнеме во вашиот следен проект
|
||||
</h2>
|
||||
<NavLink
|
||||
<Button
|
||||
to="/contact"
|
||||
className="inline-block bg-white text-blue-600 px-8 py-3 rounded-full font-semibold hover:bg-gray-100 transition-colors duration-300"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="bg-white text-primary-600 hover:bg-neutral-100"
|
||||
>
|
||||
Контактирајте нѐ
|
||||
</NavLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
46
frontend/src/theme/ThemeProvider.jsx
Normal file
46
frontend/src/theme/ThemeProvider.jsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
const ThemeContext = createContext();
|
||||
|
||||
export function ThemeProvider({ children }) {
|
||||
const theme = {
|
||||
colors: {
|
||||
primary: {
|
||||
main: 'text-primary-600',
|
||||
hover: 'hover:text-primary-700',
|
||||
bg: 'bg-primary-600',
|
||||
bgHover: 'hover:bg-primary-700',
|
||||
},
|
||||
neutral: {
|
||||
text: 'text-neutral-600',
|
||||
heading: 'text-neutral-900',
|
||||
bg: 'bg-neutral-50',
|
||||
border: 'border-neutral-200',
|
||||
},
|
||||
accent: {
|
||||
main: 'text-accent-600',
|
||||
bg: 'bg-accent-600',
|
||||
light: 'bg-accent-50',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
h1: 'text-4xl font-display font-bold tracking-tight',
|
||||
h2: 'text-2xl font-display font-semibold',
|
||||
h3: 'text-xl font-display font-semibold',
|
||||
body: 'text-base text-neutral-600',
|
||||
small: 'text-sm text-neutral-500',
|
||||
},
|
||||
spacing: {
|
||||
section: 'py-12 md:py-20',
|
||||
container: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
6
frontend/src/utils/cn.js
Normal file
6
frontend/src/utils/cn.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@ -1,8 +1,105 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
// Main brand color - A sophisticated blue-purple
|
||||
primary: {
|
||||
50: '#f8fafc',
|
||||
100: '#f1f5f9',
|
||||
200: '#e2e8f0',
|
||||
300: '#cbd5e1',
|
||||
400: '#94a3b8',
|
||||
500: '#64748b',
|
||||
600: '#475569',
|
||||
700: '#334155',
|
||||
800: '#1e293b',
|
||||
900: '#0f172a',
|
||||
950: '#020617',
|
||||
},
|
||||
// Secondary color - Warm gray with slight purple undertone
|
||||
neutral: {
|
||||
50: '#f8f7f9',
|
||||
100: '#f0eff2',
|
||||
200: '#e3e1e7',
|
||||
300: '#cbc8d2',
|
||||
400: '#aba6b6',
|
||||
500: '#8c869a',
|
||||
600: '#726d81',
|
||||
700: '#5d596a',
|
||||
800: '#4a4754',
|
||||
900: '#2f2d36',
|
||||
},
|
||||
// Success color - Soothing green
|
||||
success: {
|
||||
50: '#f0fdf6',
|
||||
100: '#dcfce9',
|
||||
200: '#bbf7d6',
|
||||
300: '#86efac',
|
||||
400: '#4ade80',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
700: '#15803c',
|
||||
800: '#166534',
|
||||
900: '#14532d',
|
||||
},
|
||||
// Warning color - Soft orange
|
||||
warning: {
|
||||
50: '#fff8f1',
|
||||
100: '#feecdc',
|
||||
200: '#fcd9bd',
|
||||
300: '#fdba8c',
|
||||
400: '#ff9f56',
|
||||
500: '#ff8a3d',
|
||||
600: '#e67132',
|
||||
700: '#c45d2b',
|
||||
800: '#9c4a23',
|
||||
900: '#7b3a1c',
|
||||
},
|
||||
// Error color - Refined red
|
||||
error: {
|
||||
50: '#fef2f2',
|
||||
100: '#fee2e2',
|
||||
200: '#fecaca',
|
||||
300: '#fca5a5',
|
||||
400: '#f87171',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
700: '#b91c1c',
|
||||
800: '#991b1b',
|
||||
900: '#7f1d1d',
|
||||
},
|
||||
// Accent color - Vibrant purple
|
||||
accent: {
|
||||
50: '#faf5ff',
|
||||
100: '#f3e8ff',
|
||||
200: '#e9d5ff',
|
||||
300: '#d8b4fe',
|
||||
400: '#c084fc',
|
||||
500: '#a855f7',
|
||||
600: '#9333ea',
|
||||
700: '#7e22ce',
|
||||
800: '#6b21a8',
|
||||
900: '#581c87',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter var', 'sans-serif'],
|
||||
display: ['Clash Display', 'sans-serif'],
|
||||
},
|
||||
spacing: {
|
||||
'18': '4.5rem',
|
||||
'88': '22rem',
|
||||
'128': '32rem',
|
||||
},
|
||||
borderRadius: {
|
||||
'4xl': '2rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user