first commit

This commit is contained in:
ghazall-ag
2025-10-23 21:52:07 +03:30
commit d63a5b8d99
15218 changed files with 1639961 additions and 0 deletions

View File

@@ -0,0 +1,191 @@
import React, { useState } from 'react';
import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
const DataTable = ({
data = [],
columns = [],
loading = false,
searchable = false,
onSearch,
className = ''
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(10);
const filteredData = data.filter(item => {
if (!searchTerm) return true;
return Object.values(item).some(value =>
String(value).toLowerCase().includes(searchTerm.toLowerCase())
);
});
const totalPages = Math.ceil(filteredData.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentData = filteredData.slice(startIndex, endIndex);
const handleSearch = (e) => {
const value = e.target.value;
setSearchTerm(value);
if (onSearch) {
onSearch(value);
}
setCurrentPage(1); // Reset to first page when searching
};
const handlePageChange = (page) => {
setCurrentPage(page);
};
const getStatusBadge = (status) => {
const statusClasses = {
success: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300',
failed: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300',
pending: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300',
};
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusClasses[status] || statusClasses.pending}`}>
{status}
</span>
);
};
const formatCurrency = (amount, currency = 'USD') => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(amount);
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
if (loading) {
return (
<div className="card p-6">
<div className="animate-pulse">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/4 mb-4"></div>
<div className="space-y-3">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-4 bg-gray-200 dark:bg-gray-700 rounded"></div>
))}
</div>
</div>
</div>
);
}
return (
<div className={`card ${className}`}>
{searchable && (
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={handleSearch}
className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
/>
</div>
</div>
)}
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
{columns.map((column, index) => (
<th
key={index}
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
{column.header}
</th>
))}
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
{currentData.length === 0 ? (
<tr>
<td
colSpan={columns.length}
className="px-6 py-12 text-center text-sm text-gray-500 dark:text-gray-400"
>
No data available
</td>
</tr>
) : (
currentData.map((row, rowIndex) => (
<tr key={rowIndex} className="hover:bg-gray-50 dark:hover:bg-gray-800">
{columns.map((column, colIndex) => (
<td key={colIndex} className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{column.render ? (
column.render(row[column.key], row)
) : (
row[column.key]
)}
</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>
{totalPages > 1 && (
<div className="px-6 py-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div className="text-sm text-gray-700 dark:text-gray-300">
Showing {startIndex + 1} to {Math.min(endIndex, filteredData.length)} of {filteredData.length} results
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
className="p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronLeft className="h-4 w-4" />
</button>
<div className="flex space-x-1">
{[...Array(totalPages)].map((_, i) => (
<button
key={i}
onClick={() => handlePageChange(i + 1)}
className={`px-3 py-1 text-sm rounded-md ${
currentPage === i + 1
? 'bg-primary-100 text-primary-700 dark:bg-primary-900 dark:text-primary-300'
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'
}`}
>
{i + 1}
</button>
))}
</div>
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className="p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
</div>
)}
</div>
);
};
export default DataTable;

113
src/components/Navbar.jsx Normal file
View File

@@ -0,0 +1,113 @@
import React, { useState } from 'react';
import { Menu, Sun, Moon, User, LogOut, ChevronDown } from 'lucide-react';
import { useAuth } from '../context/AuthContext';
const Navbar = ({ onSidebarToggle }) => {
const { user, logout } = useAuth();
const [isDarkMode, setIsDarkMode] = useState(() => {
return localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
});
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
// Apply theme to document
React.useEffect(() => {
if (isDarkMode) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
}
}, [isDarkMode]);
const toggleTheme = () => {
setIsDarkMode(!isDarkMode);
};
const handleLogout = () => {
logout();
setIsUserMenuOpen(false);
};
return (
<header className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 h-16 flex items-center justify-between px-4 lg:px-6 relative z-20">
{/* Left side */}
<div className="flex items-center space-x-4">
<button
onClick={onSidebarToggle}
className="lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800"
>
<Menu className="h-5 w-5" />
</button>
<h1 className="text-lg font-semibold text-gray-900 dark:text-white lg:hidden">
Payment Admin
</h1>
</div>
{/* Right side */}
<div className="flex items-center space-x-4">
{/* Theme toggle */}
<button
onClick={toggleTheme}
className="p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
aria-label="Toggle theme"
>
{isDarkMode ? (
<Sun className="h-5 w-5" />
) : (
<Moon className="h-5 w-5" />
)}
</button>
{/* User menu */}
<div className="relative">
<button
onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
className="flex items-center space-x-2 p-2 rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition-colors duration-200"
>
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900 rounded-full flex items-center justify-center">
<User className="h-4 w-4 text-blue-600 dark:text-blue-400" />
</div>
<span className="hidden sm:block text-sm font-medium">
{user?.name || 'Admin'}
</span>
<ChevronDown className="h-4 w-4" />
</button>
{/* Dropdown menu */}
{isUserMenuOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50">
<div className="px-4 py-2 border-b border-gray-200 dark:border-gray-700">
<p className="text-sm font-medium text-gray-900 dark:text-white">
{user?.name || 'Admin User'}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{user?.email || 'admin@example.com'}
</p>
</div>
<button
onClick={handleLogout}
className="w-full flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors duration-200"
>
<LogOut className="mr-2 h-4 w-4" />
Sign out
</button>
</div>
)}
</div>
</div>
{/* Click outside to close user menu */}
{isUserMenuOpen && (
<div
className="fixed inset-0 z-40"
onClick={() => setIsUserMenuOpen(false)}
/>
)}
</header>
);
};
export default Navbar;

View File

@@ -0,0 +1,81 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import {
LayoutDashboard,
CreditCard,
Settings,
Menu,
X
} from 'lucide-react';
const Sidebar = ({ isOpen, onToggle }) => {
const navigation = [
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
{ name: 'Transactions', href: '/transactions', icon: CreditCard },
{ name: 'Settings', href: '/settings', icon: Settings },
];
return (
<>
{/* Mobile backdrop */}
{isOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
onClick={onToggle}
/>
)}
{/* Sidebar */}
<div className={`
fixed inset-y-0 left-0 z-30 w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 transform transition-transform duration-300 ease-in-out
${isOpen ? 'translate-x-0' : '-translate-x-full'}
lg:translate-x-0 lg:static lg:inset-0 lg:z-auto
`}>
<div className="flex items-center justify-between h-16 px-6 border-b border-gray-200 dark:border-gray-700">
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
Payment Admin
</h1>
<button
onClick={onToggle}
className="lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800"
>
<X className="h-5 w-5" />
</button>
</div>
<nav className="mt-8 px-4">
<ul className="space-y-2">
{navigation.map((item) => {
const Icon = item.icon;
return (
<li key={item.name}>
<NavLink
to={item.href}
className={({ isActive }) =>
`group flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors duration-200 ${
isActive
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'
}`
}
onClick={() => {
// Close sidebar on mobile when navigating
if (window.innerWidth < 1024) {
onToggle();
}
}}
>
<Icon className="mr-3 h-5 w-5" />
{item.name}
</NavLink>
</li>
);
})}
</ul>
</nav>
</div>
</>
);
};
export default Sidebar;