ui redesign

finished
This commit is contained in:
dimitar 2025-02-25 16:45:16 +01:00
parent 7c02511dab
commit a68dda7d77
26 changed files with 2015 additions and 842 deletions

View File

@ -11,13 +11,16 @@
"@headlessui/react": "^1.7.19", "@headlessui/react": "^1.7.19",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"axios": "^1.7.7", "axios": "^1.7.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^11.18.2", "framer-motion": "^11.18.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router-dom": "^6.15.0" "react-router-dom": "^6.15.0",
"tailwind-merge": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.15", "@types/react": "^18.2.15",
@ -1416,11 +1419,32 @@
"node": ">= 6" "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": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" "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": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -4100,6 +4124,16 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/tailwindcss": {
"version": "3.4.14", "version": "3.4.14",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",

View File

@ -13,13 +13,16 @@
"@headlessui/react": "^1.7.19", "@headlessui/react": "^1.7.19",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"axios": "^1.7.7", "axios": "^1.7.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^11.18.2", "framer-motion": "^11.18.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router-dom": "^6.15.0" "react-router-dom": "^6.15.0",
"tailwind-merge": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.15", "@types/react": "^18.2.15",

View File

@ -1,6 +1,5 @@
// import './App.css' // import './App.css'
import { AuthProvider } from './hooks/useAuth'; import { AuthProvider } from './hooks/useAuth';
import Navbar from './components/navbar/Navbar'
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from './pages/homepage/Home' import Home from './pages/homepage/Home'
import About from './pages/aboutpage/About' import About from './pages/aboutpage/About'
@ -14,45 +13,50 @@ import Certificates from './components/Certificates/Certificates.jsx';
import Clients from './components/clients/clients'; import Clients from './components/clients/clients';
import AdminPanel from './components/adminPanel/AdminPanel'; import AdminPanel from './components/adminPanel/AdminPanel';
import Dashboard from './components/dashboard/Dashboard'; 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 ProtectedRoute from './components/protectedRoute/ProtectedRoute';
import { ThemeProvider } from './theme/ThemeProvider';
// import { Navbar } from './components/navbar/Navbar';
function App() { function App() {
return ( return (
<AuthProvider> <AuthProvider>
<div > <ThemeProvider>
<BrowserRouter > <div className="min-h-screen bg-neutral-50">
<Navbar /> <BrowserRouter>
<Routes> <Navbar />
<Route path='/' element={<Home />} /> <Routes>
<Route path='/contact' element={<Contact />} /> <Route path='/' element={<Home />} />
<Route path='/about' element={<About />} /> <Route path='/contact' element={<Contact />} />
<Route path='/consulting' element={<Consulting />} /> <Route path='/about' element={<About />} />
<Route path='/lab' element={<Lab />} /> <Route path='/consulting' element={<Consulting />} />
<Route path='/ultrasound' element={<UltraSound />} /> <Route path='/lab' element={<Lab />} />
<Route path='/gallery' element={<Gallery />} /> <Route path='/ultrasound' element={<UltraSound />} />
<Route path='/certificates' element={<Certificates />} /> <Route path='/gallery' element={<Gallery />} />
<Route path='/clients' element={ <Route path='/certificates' element={<Certificates />} />
<ProtectedRoute> <Route path='/clients' element={
<Clients /> <ProtectedRoute>
</ProtectedRoute> <Clients />
} /> </ProtectedRoute>
<Route path='/admin' element={ } />
<ProtectedRoute> <Route path='/admin' element={
<AdminPanel /> <ProtectedRoute>
</ProtectedRoute> <AdminPanel />
} /> </ProtectedRoute>
<Route path='/login' element={<Login />} /> } />
<Route path='/dashboard' element={ <Route path='/login' element={<Login />} />
<ProtectedRoute> <Route path='/dashboard' element={
<Dashboard /> <ProtectedRoute>
</ProtectedRoute> <Dashboard />
} /> </ProtectedRoute>
</Routes> } />
<Footer /> </Routes>
</BrowserRouter> <Footer />
</div> </BrowserRouter>
</div>
</ThemeProvider>
</AuthProvider> </AuthProvider>
) )
} }

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

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

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

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

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

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

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

View File

@ -0,0 +1,5 @@
export * from './Button';
export * from './Input';
export * from './Card';
export * from './Badge';
export * from './Table';

View File

@ -90,8 +90,8 @@ function AdminPanel() {
if (loading) { if (loading) {
return ( 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="flex items-center space-x-3 text-blue-500"> <div className="flex items-center space-x-3 text-primary-400">
<FiLoader className="w-6 h-6 animate-spin" /> <FiLoader className="w-6 h-6 animate-spin" />
<span className="text-lg font-medium">Loading...</span> <span className="text-lg font-medium">Loading...</span>
</div> </div>
@ -100,11 +100,11 @@ function AdminPanel() {
} }
return ( 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"> <div className="max-w-7xl mx-auto">
<header className="mb-8 mt-20"> <header className="mb-8 mt-20">
<h1 className="text-3xl font-bold text-white mb-2">Admin Dashboard</h1> <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> </header>
{/* Tabs */} {/* Tabs */}
@ -116,8 +116,8 @@ function AdminPanel() {
className={` className={`
px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors
${activeTab === id ${activeTab === id
? 'bg-white/10 text-white' ? 'bg-primary-600 text-white'
: 'text-gray-400 hover:bg-white/5 hover:text-white'} : 'text-neutral-400 hover:bg-primary-700/50 hover:text-white'}
`} `}
> >
<Icon className="w-4 h-4" /> <Icon className="w-4 h-4" />
@ -134,36 +134,36 @@ function AdminPanel() {
<div className="grid gap-6"> <div className="grid gap-6">
{activeTab === 'documents' && ( {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"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-gray-700"> <tr className="border-b border-primary-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-neutral-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-neutral-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-neutral-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-neutral-400">Shared With</th>
<th className="px-6 py-4 text-left text-sm text-gray-400">Created At</th> <th className="px-6 py-4 text-left text-sm text-neutral-400">Created At</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{documents.map((doc) => ( {documents.map((doc) => (
<tr key={doc.id} className="border-b border-gray-700/50 hover:bg-white/5"> <tr key={doc.id} className="border-b border-primary-700/50 hover:bg-primary-700/30">
<td className="px-6 py-4 text-gray-200">{doc.title}</td> <td className="px-6 py-4 text-white">{doc.title}</td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${ <span className={`px-3 py-1 rounded-full text-xs font-medium ${
doc.status === 'completed' ? 'bg-green-500/10 text-green-400' : doc.status === 'completed' ? 'bg-green-500/20 text-green-300' :
doc.status === 'pending' ? 'bg-yellow-500/10 text-yellow-400' : doc.status === 'pending' ? 'bg-yellow-500/20 text-yellow-300' :
doc.status === 'uploading' ? 'bg-blue-500/10 text-blue-400' : doc.status === 'uploading' ? 'bg-primary-500/20 text-primary-300' :
'bg-gray-500/10 text-gray-400' 'bg-neutral-500/20 text-neutral-300'
}`}> }`}>
{doc.status} {doc.status}
</span> </span>
</td> </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}) {doc.uploadedBy?.name} ({doc.uploadedBy?.email})
</td> </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 && doc.sharedWith.length > 0
? doc.sharedWith.map(user => ( ? doc.sharedWith.map(user => (
<div key={user.id} className="whitespace-nowrap"> <div key={user.id} className="whitespace-nowrap">
@ -172,7 +172,7 @@ function AdminPanel() {
)) ))
: 'None'} : 'None'}
</td> </td>
<td className="px-6 py-4 text-gray-400"> <td className="px-6 py-4 text-neutral-300">
{new Date(doc.createdAt).toLocaleString()} {new Date(doc.createdAt).toLocaleString()}
</td> </td>
</tr> </tr>
@ -185,7 +185,7 @@ function AdminPanel() {
{activeTab === 'users' && ( {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> <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"> <form onSubmit={handleCreateUser} className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<input <input
@ -193,7 +193,9 @@ function AdminPanel() {
placeholder="Name" placeholder="Name"
value={newUser.name} value={newUser.name}
onChange={(e) => setNewUser({ ...newUser, name: e.target.value })} 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 required
/> />
<input <input
@ -201,7 +203,9 @@ function AdminPanel() {
placeholder="Email" placeholder="Email"
value={newUser.email} value={newUser.email}
onChange={(e) => setNewUser({ ...newUser, email: e.target.value })} 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 required
/> />
<input <input
@ -209,7 +213,9 @@ function AdminPanel() {
placeholder="Password" placeholder="Password"
value={newUser.password} value={newUser.password}
onChange={(e) => setNewUser({ ...newUser, password: e.target.value })} 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 required
/> />
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -218,13 +224,16 @@ function AdminPanel() {
id="isAdmin" id="isAdmin"
checked={newUser.isAdmin} checked={newUser.isAdmin}
onChange={(e) => setNewUser({ ...newUser, isAdmin: e.target.checked })} 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> </div>
<button <button
type="submit" 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" /> <FiUserPlus className="w-4 h-4" />
<span>Create User</span> <span>Create User</span>
@ -232,23 +241,23 @@ function AdminPanel() {
</form> </form>
</div> </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"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-gray-700"> <tr className="border-b border-primary-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-neutral-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-neutral-400">Email</th>
<th className="px-6 py-4 text-left text-sm text-gray-400">Role</th> <th className="px-6 py-4 text-left text-sm text-neutral-400">Role</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{users.map((user) => ( {users.map((user) => (
<tr key={user.id} className="border-b border-gray-700/50 hover:bg-white/5"> <tr key={user.id} className="border-b border-primary-700/50 hover:bg-primary-700/30">
<td className="px-6 py-4 text-gray-200">{user.name}</td> <td className="px-6 py-4 text-white">{user.name}</td>
<td className="px-6 py-4 text-gray-200">{user.email}</td> <td className="px-6 py-4 text-white">{user.email}</td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<span className={`px-3 py-1 rounded-full text-xs font-medium <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'} {user.isAdmin ? 'Admin' : 'User'}
</span> </span>
</td> </td>
@ -261,7 +270,7 @@ function AdminPanel() {
)} )}
{activeTab === 'upload' && ( {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 /> <DocumentUpload />
</div> </div>
)} )}

View File

@ -66,8 +66,8 @@ function Dashboard() {
if (loading) { if (loading) {
return ( 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="flex items-center space-x-3 text-blue-500"> <div className="flex items-center space-x-3 text-primary-400">
<FiLoader className="w-6 h-6 animate-spin" /> <FiLoader className="w-6 h-6 animate-spin" />
<span className="text-lg font-medium">Loading documents...</span> <span className="text-lg font-medium">Loading documents...</span>
</div> </div>
@ -77,7 +77,7 @@ function Dashboard() {
if (error) { if (error) {
return ( 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"> <div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg text-red-200">
{error} {error}
</div> </div>
@ -86,11 +86,11 @@ function Dashboard() {
} }
return ( 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"> <div className="max-w-7xl mx-auto">
<header className="mb-8 mt-20"> <header className="mb-8 mt-20">
<h1 className="text-3xl font-bold text-white mb-2">Your Documents</h1> <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> </header>
<div className="grid gap-6"> <div className="grid gap-6">
@ -98,16 +98,18 @@ function Dashboard() {
Object.entries(documents).map(([folderName, docs]) => ( Object.entries(documents).map(([folderName, docs]) => (
<div <div
key={folderName} 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 <button
onClick={() => toggleFolder(folderName)} 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"> <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="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> </div>
<FiChevronRight <FiChevronRight
className={`w-5 h-5 transition-transform duration-200 ${ className={`w-5 h-5 transition-transform duration-200 ${
@ -117,19 +119,21 @@ function Dashboard() {
</button> </button>
{expandedFolders[folderName] && ( {expandedFolders[folderName] && (
<div className="border-t border-gray-700"> <div className="border-t border-neutral-800">
{docs.map((doc) => ( {docs.map((doc) => (
<div <div
key={doc.id} 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"> <div className="flex items-center space-x-3">
<FiFile className="w-4 h-4 text-gray-400" /> <FiFile className="w-4 h-4 text-neutral-400" />
<span className="text-gray-200">{doc.title}</span> <span className="text-neutral-200">{doc.title}</span>
</div> </div>
<button <button
onClick={() => handleDownload(doc.s3Key)} 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" title="Download document"
> >
<FiDownload className="w-4 h-4" /> <FiDownload className="w-4 h-4" />
@ -141,8 +145,9 @@ function Dashboard() {
</div> </div>
)) ))
) : ( ) : (
<div className="text-center py-12"> <div className="text-center py-12 bg-neutral-900/80 backdrop-blur-lg rounded-xl
<p className="text-gray-400">No documents available</p> border border-neutral-800">
<p className="text-neutral-400">No documents available</p>
</div> </div>
)} )}
</div> </div>

View File

@ -1,147 +1,149 @@
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { FiHome, FiMail, FiPhone, FiMapPin } from 'react-icons/fi'
import './footer.css' import './footer.css'
const Footer = () => { const Footer = () => {
return ( return (
<> <footer className="bg-gradient-to-br from-primary-900 to-primary-700 text-white">
<footer {/* Main Footer Content */}
className="text-center text-white bg-gradient-to-b from-cyan-900 to-cyan-800 lg:text-left"> <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>
{/* Services */}
<div className="mx-6 py-10 text-center md:text-left"> <div className="space-y-4">
<div className="grid-1 grid gap-8 md:grid-cols-2 lg:grid-cols-4"> <h6 className="text-lg font-semibold text-white">
<div className=""> Услуги
<h6 </h6>
className="mb-4 flex items-center justify-center font-semibold uppercase md:justify-start"> <div className="flex flex-col space-y-2">
<NavLink
Испитување материјали и консултантство to="/lab"
</h6> className="text-neutral-300 hover:text-primary-400 transition-colors"
<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>
</NavLink> </NavLink>
<NavLink to={'ultrasound'}> <NavLink
<p className="mb-4"> to="/ultrasound"
<a href="#!" className="text-neutral-600 dark:text-neutral-200" className="text-neutral-300 hover:text-primary-400 transition-colors"
>Испитување со ултразвук</a >
> Испитување со ултразвук
</p>
</NavLink> </NavLink>
<NavLink to={'consulting'}> <NavLink
<p className="mb-4"> to="/consulting"
<a href="#!" className="text-neutral-600 dark:text-neutral-200" className="text-neutral-300 hover:text-primary-400 transition-colors"
>Консултатски услуги</a >
> Консултатски услуги
</p>
</NavLink> </NavLink>
</div> </div>
<div className=""> </div>
<h6
className="mb-4 flex justify-center font-semibold uppercase md:justify-start"> {/* Useful Links */}
Корисни линкови <div className="space-y-4">
</h6> <h6 className="text-lg font-semibold text-white">
<p className="mb-4"> Корисни линкови
<a href="#!" className="text-neutral-600 dark:text-neutral-200" </h6>
>Безбедно градење</a <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> stanko@imk.mk
<p className="mb-4"> </a>
<a href="#!" className="text-neutral-600 dark:text-neutral-200" </div>
>Контрола на квалитет</a <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> info.imkmk@gmail.com
<NavLink to="/certificates" > </a>
<p className="mb-4"> </div>
<a>Сертификати</a> <div className="flex items-center space-x-3 text-neutral-300">
</p> <FiPhone className="h-5 w-5 text-primary-400" />
</NavLink> <a
href="tel:+38970279877"
</div> className="hover:text-primary-400 transition-colors"
<div> >
<h6 + 389 70 279 877
className="mb-4 flex justify-center font-semibold uppercase md:justify-start"> </a>
Контакт </div>
</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>
</div> </div>
</div> </div>
</div> </div>
</div>
<div className="bg-neutral-200 p-6 text-center dark:bg-neutral-700"> {/* Copyright Bar */}
<span>© 2023 Copyright:</span> <div className="border-t border-primary-800">
<a <div className="max-w-7xl mx-auto px-6 py-4">
className="font-semibold text-neutral-600 dark:text-neutral-400" <div className="flex flex-col md:flex justify-between items-center space-y-2 md:space-y-0">
href="https://tailwind-elements.com/" <div className="text-neutral-400 text-sm">
> IMK</a © {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> </div>
</footer > </div>
</footer>
</>
) )
} }

View File

@ -1,25 +1,30 @@
import React from 'react' import React from 'react'
import './gallery.css' import './gallery.css'
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
// import { FiZoomIn } from 'react-icons/fi'; import { motion, AnimatePresence } from 'framer-motion';
import { motion } from 'framer-motion'; import { XMarkIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { Button, Card } from '../../components/UI';
function Gallery() { function Gallery() {
const [selectedImage, setSelectedImage] = useState(null); const [selectedImage, setSelectedImage] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activeCategory, setActiveCategory] = useState('all');
const [filteredImages, setFilteredImages] = useState([]);
const images = [ const images = [
{ id: 1, src: "1.jpg", alt: "Laboratory Equipment 1", category: "Lab" }, { id: 1, src: "1.jpg", alt: "Laboratory Equipment 1", category: "Лабораторија" },
{ id: 2, src: "2.jpg", alt: "Laboratory Equipment 2", category: "Lab" }, { id: 2, src: "2.jpg", alt: "Laboratory Equipment 2", category: "Лабораторија" },
{ id: 3, src: "3.jpg", alt: "Laboratory Equipment 3", category: "Lab" }, { id: 3, src: "3.jpg", alt: "Laboratory Equipment 3", category: "Лабораторија" },
{ id: 4, src: "4.jpg", alt: "Testing Process 1", category: "Testing" }, { id: 4, src: "4.jpg", alt: "Testing Process 1", category: "Тестирање" },
{ id: 5, src: "5.jpg", alt: "Testing Process 2", category: "Testing" }, { id: 5, src: "5.jpg", alt: "Testing Process 2", category: "Тестирање" },
{ id: 6, src: "6.jpg", alt: "Testing Process 3", category: "Testing" }, { id: 6, src: "6.jpg", alt: "Testing Process 3", category: "Тестирање" },
{ id: 8, src: "8.jpg", alt: "Results 1", category: "Results" }, { id: 8, src: "8.jpg", alt: "Results 1", category: "Резултати" },
{ id: 9, src: "9.jpg", alt: "Results 2", category: "Results" }, { id: 9, src: "9.jpg", alt: "Results 2", category: "Резултати" },
{ id: 10, src: "10.jpg", alt: "Results 3", category: "Results" } { id: 10, src: "10.jpg", alt: "Results 3", category: "Резултати" }
]; ];
const categories = [...new Set(images.map(img => img.category))];
useEffect(() => { useEffect(() => {
window.scrollTo({ top: 0, behavior: "smooth" }) window.scrollTo({ top: 0, behavior: "smooth" })
// Simulate images loading // Simulate images loading
@ -38,6 +43,14 @@ function Gallery() {
loadImages(); loadImages();
}, []) }, [])
useEffect(() => {
setFilteredImages(
activeCategory === 'all'
? images
: images.filter(img => img.category === activeCategory)
);
}, [activeCategory]);
const containerVariants = { const containerVariants = {
hidden: { opacity: 0 }, hidden: { opacity: 0 },
visible: { visible: {
@ -60,85 +73,157 @@ function Gallery() {
}; };
return ( 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 */} {/* Hero Section */}
<div className="container mx-auto px-4 mb-16 my-20"> <section className="relative h-[20vh] overflow-hidden">
<div className="text-center"> {/* <div className="absolute inset-0 bg-pattern opacity-5"></div> */}
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6"> <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>
</h1> <motion.h1
<p className="text-xl text-gray-300 max-w-2xl mx-auto"> initial={{ opacity: 0, y: 20 }}
Погледнете ја нашата опрема и работен процес преку слики animate={{ opacity: 1, y: 0 }}
</p> 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>
</div> </section>
{loading ? ( {/* Gallery Section */}
<div className="flex justify-center items-center h-64"> <section className="py-16">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div> <div className="max-w-[90rem] mx-auto px-4 sm:px-6 lg:px-8">
</div> {/* Category Filter */}
) : ( <div className="flex flex-wrap gap-3 justify-center mb-12">
<motion.div <Button
className="container mx-auto px-4" variant={activeCategory === 'all' ? 'primary' : 'secondary'}
variants={containerVariants} onClick={() => setActiveCategory('all')}
initial="hidden" className={`min-w-[100px] ${
animate="visible" activeCategory === 'all'
> ? 'bg-primary-600 text-white'
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> : 'bg-white/10 text-white hover:bg-white/20'
{images.map((image) => ( }`}
<motion.div >
key={image.id} Сите
className="" </Button>
variants={itemVariants} {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"> {category}
<img </Button>
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>
))} ))}
</div> </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 */} {/* Image Modal */}
{selectedImage && ( <AnimatePresence>
<div {selectedImage && (
className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4" <motion.div
onClick={() => setSelectedImage(null)} initial={{ opacity: 0 }}
> animate={{ opacity: 1 }}
<div className="relative max-w-4xl w-full"> exit={{ opacity: 0 }}
<img className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4 md:p-8"
src={selectedImage.src} onClick={() => setSelectedImage(null)}
alt={selectedImage.alt} >
className="w-full h-auto rounded-xl" <motion.div
/> initial={{ scale: 0.9, opacity: 0 }}
<button animate={{ scale: 1, opacity: 1 }}
onClick={() => setSelectedImage(null)} exit={{ scale: 0.9, opacity: 0 }}
className="absolute top-4 right-4 text-white hover:text-blue-400" 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"> <img
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> src={selectedImage.src}
</svg> alt={selectedImage.alt}
</button> className="w-full h-auto rounded-lg shadow-2xl"
</div> />
</div> <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> </div>
); );
} }

View File

@ -2,6 +2,12 @@
@tailwind components; @tailwind components;
@tailwind utilities; @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 { .aspect-w-16 {
position: relative; position: relative;
padding-bottom: 75%; padding-bottom: 75%;

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

View File

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

View File

@ -1,15 +1,15 @@
import { useState } from "react"; import { useState } from "react";
import { Dialog } from "@headlessui/react"; import { Dialog } from "@headlessui/react";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline"; import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import { NavLink } from "react-router-dom"; import { NavLink, Link } from "react-router-dom";
import { Button } from '../UI';
const navigation = [ const navigation = [
{ name: "Дома", href: "/" }, { name: "Дома", href: "/" },
{ name: "За нас", href: "/about" }, { name: "За нас", href: "/about" },
{ name: "Галерија", href: "/gallery" }, { name: "Галерија", href: "/gallery" },
{ name: "Контакт", href: "/contact" }, { name: "Контакт", href: "/contact" },
// { name: "Клиенти", href: "/clients" }, // { name: "Админ", href: "/admin" },
{ name: "Админ", href: "/admin" },
{ name: "Логин", href: "/login" }, { name: "Логин", href: "/login" },
]; ];
@ -17,63 +17,60 @@ export default function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return ( return (
<div className="absolute w-full z-50 py-10"> <div className="w-full bg-gradient-to-b from-primary-900 to-primary-800 border-b border-primary-700/50">
<header className="relative bg-transparent"> <header className="relative">
<nav <nav className="container-base py-4" aria-label="Global">
className="container mx-auto flex items-center justify-between p-6 lg:px-8" <div className="flex items-center justify-between">
aria-label="Global" {/* Left Logo */}
> <Link to="/" className="p-2 hover:bg-white/20 transition-colors">
{/* Logo */}
<div className="flex lg:flex-1">
<a href="/" className="flex items-center -m-1.5 p-1.5">
<img <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" src="/imklogorgb.png"
alt="IMK logo" alt="IMK logo"
/> />
</a> </Link>
</div>
{/* Mobile menu button */} {/* Centered Desktop Navigation */}
<div className="flex lg:hidden"> <div className="hidden lg:flex flex-grow justify-center mx-8">
<button <div className="flex items-center gap-x-1">
type="button" {navigation.map((item) => (
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" <NavLink
onClick={() => setMobileMenuOpen(true)} key={item.name}
> to={item.href}
<span className="sr-only">Open main menu</span> className={({ isActive }) =>
<Bars3Icon className="h-6 w-6" aria-hidden="true" /> `px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
</button> ${isActive
</div> ? '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 */} {/* Right Side - TUF Logo & Mobile Menu */}
<div className="hidden lg:flex lg:gap-x-12"> <div className="flex items-center gap-x-4">
{navigation.map((item) => ( {/* TUF Logo */}
<NavLink <a href="/" className="hidden lg:flex p-2 hover:bg-white/20 transition-colors">
key={item.name} <img
to={item.href} className="h-10 w-auto transition-transform duration-300 hover:scale-105"
className={({ isActive }) => src="/tuf.png"
`text-base font-semibold leading-6 transition-all duration-300 alt="TUF logo"
${isActive />
? 'text-white border-b-2 border-white' </a>
: 'text-gray-200 hover:text-white hover:border-b-2 hover:border-white/50'
}` {/* Mobile menu button */}
} <Button
variant="ghost"
className="lg:hidden text-white hover:bg-primary-700/50"
onClick={() => setMobileMenuOpen(true)}
> >
{item.name} <span className="sr-only">Open main menu</span>
</NavLink> <Bars3Icon className="h-6 w-6" aria-hidden="true" />
))} </Button>
</div> </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>
</div> </div>
</nav> </nav>
@ -84,45 +81,43 @@ export default function Navbar() {
open={mobileMenuOpen} open={mobileMenuOpen}
onClose={setMobileMenuOpen} onClose={setMobileMenuOpen}
> >
<div className="fixed inset-0 z-50" /> <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-gradient-to-b from-blue-900 to-blue-800 px-6 py-6 sm:max-w-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"> <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 <img
className="h-8 w-auto" className="h-8 w-auto"
src="/imklogorgb.png" src="/imklogorgb.png"
alt="IMK logo" alt="IMK logo"
/> />
</a> </Link>
<button <Button
type="button" variant="ghost"
className="-m-2.5 rounded-md p-2.5 text-white hover:bg-white/10 transition-colors duration-300"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
className="text-white hover:bg-primary-700/50"
> >
<span className="sr-only">Close menu</span> <span className="sr-only">Close menu</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" /> <XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button> </Button>
</div> </div>
<div className="mt-6 flow-root"> <div className="mt-6 flow-root">
<div className="-my-6 divide-y divide-white/10"> <div className="space-y-1 py-6">
<div className="space-y-2 py-6"> {navigation.map((item) => (
{navigation.map((item) => ( <NavLink
<NavLink key={item.name}
key={item.name} to={item.href}
to={item.href} className={({ isActive }) =>
className={({ isActive }) => `block px-3 py-2 text-base font-medium rounded-lg transition-colors
`-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 transition-colors duration-300 ${isActive
${isActive ? 'bg-primary-700 text-white'
? 'text-white bg-white/10' : 'text-neutral-200 hover:bg-primary-700/50 hover:text-white'
: 'text-gray-200 hover:bg-white/5 hover:text-white' }`
}` }
} onClick={() => setMobileMenuOpen(false)}
onClick={() => setMobileMenuOpen(false)} >
> {item.name}
{item.name} </NavLink>
</NavLink> ))}
))}
</div>
</div> </div>
</div> </div>
</Dialog.Panel> </Dialog.Panel>

View File

@ -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 base;
@tailwind components; @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;
}
}

View File

@ -1,7 +1,6 @@
import { NavLink } from "react-router-dom";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { SectionHeader } from "../../shared/SectionHeader";
import { FiTrendingUp, FiAward, FiCheckCircle } from "react-icons/fi"; import { FiTrendingUp, FiAward, FiCheckCircle } from "react-icons/fi";
import { Button, Card } from "../../components/UI";
const milestones = [ const milestones = [
{ year: "2008", title: "Основање на компанијата" }, { year: "2008", title: "Основање на компанијата" },
@ -12,120 +11,157 @@ const milestones = [
{ year: "2023", title: "Проширување на тимот" }, { 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() { export default function About() {
return ( 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 */} {/* Hero Section */}
<div className="relative h-screen overflow-hidden"> <section className="relative h-screen overflow-hidden bg-gradient-to-br from-primary-900 to-primary-700">
<div className="absolute inset-0"> <div className="absolute inset-0 bg-black/20" />
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/90 to-blue-600/80" /> <div className="relative container-base h-full flex items-center">
</div>
<div className="relative container mx-auto px-4 h-full flex items-center">
<div className="max-w-3xl"> <div className="max-w-3xl">
<motion.h1 <motion.h1
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1, ease: "easeInOut" }} transition={{ duration: 0.8 }}
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> </motion.h1>
<motion.p <motion.p
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1, delay: 0.2, ease: "easeIn" }} transition={{ duration: 0.8, delay: 0.2 }}
className="text-xl text-gray-200 mb-8" className="text-xl text-neutral-100 mb-8"
> >
Повеќе од 15 години искуство во обезбедување квалитет Повеќе од 15 години искуство во обезбедување квалитет
</motion.p> </motion.p>
</div> </div>
</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="grid grid-cols-1 md:grid-cols-2 gap-8 mb-16">
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8"> <Card className="bg-gradient-to-br from-primary-900 to-primary-700 text-white">
<div className="flex items-center mb-4"> <div className="flex items-center mb-6">
<FiTrendingUp className="w-6 h-6 text-white mr-3" /> <FiTrendingUp className="w-8 h-8 mr-4" />
<h3 className="text-2xl font-semibold text-white">Визија</h3> <h3 className="text-2xl font-display font-semibold">Визија</h3>
</div> </div>
<p className="text-white text-xl"> <p className="text-neutral-100 text-lg leading-relaxed">
Нашата визија е да бидеме водечка компанија во областа на Нашата визија е да бидеме водечка компанија во областа на
испитување на материјали и контрола на квалитет во Македонија. испитување на материјали и контрола на квалитет во Македонија.
Стремиме кон постојано унапредување на нашите услуги преку Стремиме кон постојано унапредување на нашите услуги преку
имплементација на најсовремени технологии и методологии. Сакаме имплементација на најсовремени технологии и методологии.
да воспоставиме нови стандарди во индустријата и да допринесеме
за развојот на градежништвото и инженерството во регионот.
</p> </p>
</div> </Card>
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-8">
<div className="flex items-center mb-4"> <Card className="bg-gradient-to-br from-primary-800 to-primary-600 text-white">
<FiAward className="w-6 h-6 text-white mr-3" /> <div className="flex items-center mb-6">
<h3 className="text-2xl font-semibold text-white">Вредности</h3> <FiAward className="w-8 h-8 mr-4" />
<h3 className="text-2xl font-display font-semibold">Вредности</h3>
</div> </div>
<ul className="text-gray-300 space-y-3"> <ul className="space-y-4">
<li className="flex items-start text-xl"> {values.map((value, index) => (
<FiCheckCircle className="w-5 h-5 text-red mr-2 mt-1" /> <li key={index} className="flex items-start">
<div> <FiCheckCircle className="w-5 h-5 mr-3 mt-1" />
<strong className="text-white">Квалитет</strong> - Посветени <div>
сме на обезбедување највисок квалитет во сите наши услуги <strong className="block text-lg mb-1">{value.title}</strong>
</div> <span className="text-neutral-100">{value.description}</span>
</li> </div>
<li className="flex items-start"> </li>
<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> </ul>
</div> </Card>
</div> </div>
</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 */} {/* CTA Section */}
<div className="isolate bg-gray bg-gradient-to-b from-blue-700 to-blue-500 text-white py-24"> <section className="py-24 bg-gradient-to-br from-primary-900 to-primary-700">
<div className="container mx-auto px-4 text-center"> <div className="container-base text-center">
<h2 className="text-3xl md:text-4xl font-bold mb-8"> <h2 className="text-3xl md:text-4xl font-display font-bold text-white mb-8">
Започнете го вашиот проект со нас Започнете го вашиот проект со нас
</h2> </h2>
<NavLink <Button
to="/contact" 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>
</div> </section>
</div> </div>
); );
} }

View File

@ -1,115 +1,228 @@
import { useState } from 'react' 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' 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) { function classNames(...classes) {
return classes.filter(Boolean).join(' ') return classes.filter(Boolean).join(' ')
} }
export default function Contact() { 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 ( const handleSubmit = async (e) => {
<div className="isolate bg-gray px-6 bg-gradient-to-b from-cyan-900 to-cyan-800 sm:py-32 lg:px-8"> e.preventDefault()
<div setIsSubmitting(true)
aria-hidden="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>
<div className="mx-auto max-w-2xl text-center mt-10"> </div>
<h2 className="text-3xl font-bold tracking-tight text-gray-900 sm:text-3xl">Пишете ни порака | Закажете средба</h2> <div>
{/* <p className="mt-2 text-lg leading-8 text-gray-600"> <label
take a wheel or let it slide htmlFor="last-name"
className="block text-sm font-semibold leading-6 text-white"
</p> */} >
Презиме
</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> </div>
<form action="https://formsubmit.co/taratur@gmail.com" method="POST" className="mx-auto mt-16 max-w-xl sm:mt-20"> </div>
<div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2"> <div className="sm:col-span-2">
<div> <label
<label htmlFor="first-name" className="block text-sm font-semibold leading-6 text-white"> htmlFor="company"
Име className="block text-sm font-semibold leading-6 text-white"
</label> >
<div className="mt-2.5"> Компанија
<input </label>
type="text" <div className="mt-2.5">
name="first-name" <input
id="first-name" type="text"
autoComplete="given-name" name="company"
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" id="company"
/> autoComplete="organization"
</div> value={formData.company}
</div> onChange={handleChange}
<div> className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
<label htmlFor="last-name" className="block text-sm font-semibold leading-6 text-white"> ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
Презиме focus:ring-2 focus:ring-inset focus:ring-primary-600
</label> sm:text-sm sm:leading-6"
<div className="mt-2.5"> />
<input </div>
type="text" </div>
name="last-name" <div className="sm:col-span-2">
id="last-name" <label
autoComplete="family-name" htmlFor="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" className="block text-sm font-semibold leading-6 text-white"
/> >
</div> Мејл
</div> </label>
<div className="sm:col-span-2"> <div className="mt-2.5">
<label htmlFor="company" className="block text-sm font-semibold leading-6 text-white"> <input
Компанија type="email"
</label> name="email"
<div className="mt-2.5"> id="email"
<input autoComplete="email"
type="text" value={formData.email}
name="company" onChange={handleChange}
id="company" className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
autoComplete="organization" ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
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" focus:ring-2 focus:ring-inset focus:ring-primary-600
/> sm:text-sm sm:leading-6"
</div> />
</div> </div>
<div className="sm:col-span-2"> </div>
<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 className="sm:col-span-2"> <div className="sm:col-span-2">
<label htmlFor="message" className="block text-sm font-semibold leading-6 text-white"> <label
Порака htmlFor="message"
</label> className="block text-sm font-semibold leading-6 text-white"
<div className="mt-2.5"> >
<textarea Порака
name="message" </label>
id="message" <div className="mt-2.5">
rows={4} <textarea
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" name="message"
defaultValue={''} id="message"
/> rows={4}
</div> value={formData.message}
</div> onChange={handleChange}
<Switch.Group as="div" className="flex gap-x-4 sm:col-span-2"> className="block w-full rounded-md border-0 px-3.5 py-2 text-neutral-900 shadow-sm
</Switch.Group> ring-1 ring-inset ring-neutral-300 placeholder:text-neutral-400
</div> focus:ring-2 focus:ring-inset focus:ring-primary-600
<input type="hidden" name="_next" value="https://imk.mk/"></input> sm:text-sm sm:leading-6"
<div className="mt-10"> />
<button </div>
type="submit" </div>
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" <Switch.Group as="div" className="flex gap-x-4 sm:col-span-2">
> </Switch.Group>
Испрати
</button>
</div>
</form>
</div> </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>
)
} }

View File

@ -1,7 +1,8 @@
import { NavLink } from "react-router-dom"; import { Link } from "react-router-dom";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { ArrowRightIcon, CheckCircleIcon } from "@heroicons/react/24/outline"; import { ArrowRightIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
import { SectionHeader } from "../../shared/SectionHeader"; import { Button, Card } from "../../components/UI";
const serviceCards = [ const serviceCards = [
{ {
title: "Лабораториски услуги", title: "Лабораториски услуги",
@ -47,19 +48,17 @@ const serviceCards = [
export default function Home() { export default function Home() {
return ( 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 with Parallax */} {/* Hero Section */}
<div className="relative h-screen overflow-hidden"> <section className="relative h-screen overflow-hidden bg-gradient-to-br from-primary-900 to-primary-700">
<div className="absolute inset-0"> <div className="absolute inset-0 bg-black/20" />
<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"> <div className="relative container-base h-full flex items-center">
<div className="max-w-3xl"> <div className="max-w-3xl">
<motion.h1 <motion.h1
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} 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> </motion.h1>
@ -67,7 +66,7 @@ export default function Home() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }} 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 }} transition={{ delay: 0.4 }}
className="flex gap-4" className="flex gap-4"
> >
<NavLink <Button
to="/contact" 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> </Button>
<NavLink <Button
to="/about" 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> </motion.div>
</div> </div>
</div> </div>
</div> </section>
{/* 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> */}
{/* Services Section */} {/* Services Section */}
<div className="bg-white py-24"> <section className="bg-neutral-50 py-24">
<div className="container bg-gradient-to-b from-cyan-900 to-cyan-400 rounded-xl mx-auto p-20"> <div className="container-base">
{/* <div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-3xl md:text-4xl font-display font-bold text-neutral-900 mb-4">
Нашите Услуги Нашите Услуги
</h2> </h2>
<div className="w-24 h-1 bg-blue-500 mx-auto"></div> <div className="w-24 h-1 bg-primary-500 mx-auto mb-6"></div>
</div> */} <p className="text-lg text-neutral-600 max-w-2xl mx-auto">
<SectionHeader Нашиот тим е спремен да ви помогне со професионални решенија
title="Нашите Услуги" </p>
subtitle="Нашиот тим е спремен да ви помогне со нашите услуги" </div>
className="text-white"
/> <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
{serviceCards.map((card) => ( {serviceCards.map((card) => (
<NavLink <Link
key={card.title} key={card.title}
to={card.link} 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"> <Card
<img className="h-full hover:-translate-y-1 transition-all duration-300"
src={card.image} padding="none"
alt={card.title} >
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500" <div className="relative h-56 overflow-hidden">
/> <img
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" /> src={card.image}
<h3 className="absolute bottom-4 left-4 text-xl font-semibold text-white"> alt={card.title}
{card.title} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
</h3> />
</div> <div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="p-6"> <h3 className="absolute bottom-4 left-4 text-xl font-semibold text-white">
<p className="text-gray-600 mb-4">{card.description}</p> {card.title}
<ul className="space-y-2"> </h3>
{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" />
</div> </div>
</div> <div className="p-6">
</NavLink> <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> </div>
</div> </section>
{/* Featured Projects/Clients */} {/* Clients Section */}
<div className="container mx-auto px-4 py-24"> {/* <section className="bg-gradient-to-br from-primary-900 to-primary-700 py-24">
<div className="text-center mb-16"> <div className="container-base">
<h2 className="text-3xl md:text-4xl font-bold text-white mb-4"> <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-blue-500 mx-auto mb-8"></div> </h2>
<p className="text-white text-xl max-w-2xl mx-auto"> <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> </p>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-8"> <div className="grid grid-cols-2 md:grid-cols-4 gap-8">
{/* Add client logos here */} {[1, 2, 3, 4].map((index) => (
{[1, 2, 3, 4].map((index) => ( <Card
<div key={index}
key={index} className="p-8 hover:shadow-lg transition-shadow duration-300"
className="bg-white p-8 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-300" >
> <img
<img src={`/client-${index}.png`}
src={`/client-${index}.png`} alt={`Client ${index}`}
alt={`Client ${index}`} className="w-full h-20 object-contain grayscale hover:grayscale-0 transition-all duration-300"
className="w-full h-20 object-contain grayscale hover:grayscale-0 transition-all duration-300" />
/> </Card>
</div> ))}
))} </div>
</div> </div>
</div> </section> */}
{/* CTA Section */} {/* CTA Section */}
<div className="isolate bg-gray bg-gradient-to-b from-blue-700 to-blue-500 text-white py-24"> {/* <section className="bg-gradient-to-br from-primary-800 to-primary-600 py-24">
<div className="container mx-auto px-4 text-center"> <div className="container-base text-center">
<h2 className="text-3xl md:text-4xl font-bold mb-8"> <h2 className="text-3xl md:text-4xl font-display font-bold text-white mb-8">
Спремни сме да ви помогнеме во вашиот следен проект Спремни сме да ви помогнеме во вашиот следен проект
</h2> </h2>
<NavLink <Button
to="/contact" 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>
</div> </section> */}
</div> </div>
); );
} }

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

@ -0,0 +1,6 @@
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs) {
return twMerge(clsx(inputs));
}

View File

@ -1,8 +1,105 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: { 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: [], plugins: [],
}; };