feat: add admin-web application with React and Vite

feat(admin-web): implement basic layout and navigation
feat(admin-web): implement dashboard page with key metrics
feat(admin-web): implement user management page
feat(admin-web): implement analytics page with user stats
feat(admin-web): implement logs explorer page
style(admin-web): add tailwind css and shadcn/ui components
build(admin-web): configure eslint and prettier for code quality

This commit introduces the admin-web application, a React-based
administration console built with Vite.

- The application provides a central interface for managing
 users, monitoring platform health, and troubleshooting issues.
- It includes a basic layout with navigation, a dashboard
 displaying key metrics, a user management page, an analytics
 page with user statistics, and a logs explorer page.
- Tailwind CSS and shadcn/ui components are used for styling.
- ESLint and Prettier are configured to ensure code quality.
This commit is contained in:
bwnyasse
2025-11-15 17:38:48 -05:00
parent edf3dc4042
commit 9db589d43f
23 changed files with 5760 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import React from "react";
const topUsers = [
{ email: "ian.gomez@example.com", visits: 6950 },
{ email: "admin@krow.com", visits: 193 },
{ email: "paula.orte@example.com", visits: 81 },
{ email: "test.user@example.com", visits: 50 },
];
const topPages = [
{ name: "VendorManagement", visits: 718 },
{ name: "Dashboard", visits: 600 },
{ name: "unknown", visits: 587 },
{ name: "ClientDashboard", visits: 475 },
{ name: "Events", visits: 456 },
];
export default function Analytics() {
return (
<div>
<h1 className="text-3xl font-bold text-slate-800">Analytics</h1>
<p className="mt-2 text-base text-slate-600">
An overview of your application's data.
</p>
<div className="mt-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white shadow-lg rounded-2xl p-6">
<h3 className="text-lg font-medium text-gray-900">Total Unique Users</h3>
<p className="mt-2 text-4xl font-bold text-blue-600">3</p>
</div>
</div>
</div>
<div className="mt-8 grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<h3 className="text-xl font-semibold text-gray-900">Top Users</h3>
<ul className="mt-4 space-y-4">
{topUsers.map((user) => (
<li key={user.email} className="bg-white shadow-lg rounded-2xl p-4">
<p className="font-semibold text-slate-800">{user.email}</p>
<p className="text-slate-500">{user.visits.toLocaleString()} visits</p>
</li>
))}
</ul>
</div>
<div>
<h3 className="text-xl font-semibold text-gray-900">Top Pages</h3>
<ul className="mt-4 space-y-4">
{topPages.map((page) => (
<li key={page.name} className="bg-white shadow-lg rounded-2xl p-4 flex justify-between items-center">
<p className="font-semibold text-slate-800">{page.name}</p>
<p className="text-slate-500 font-medium">{page.visits.toLocaleString()}</p>
</li>
))}
</ul>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,38 @@
import React from "react";
import { Users, Building2, Mail, FileText } from "lucide-react";
const stats = [
{ name: "Total Users", stat: "15", icon: Users, color: "bg-blue-500" },
{ name: "Active Vendors", stat: "8", icon: Building2, color: "bg-green-500" },
{ name: "Pending Invitations", stat: "3", icon: Mail, color: "bg-yellow-500" },
{ name: "Open Orders", stat: "12", icon: FileText, color: "bg-purple-500" },
];
export default function Dashboard() {
return (
<div>
<h1 className="text-3xl font-bold text-slate-800">Admin Dashboard</h1>
<p className="mt-2 text-base text-slate-600">
Welcome to the Krow Admin Console. Here is a quick overview of the platform.
</p>
<div className="mt-8">
<dl className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
{stats.map((item) => (
<div key={item.name} className="relative overflow-hidden rounded-2xl bg-white px-4 py-5 shadow-lg sm:px-6 sm:py-6">
<dt>
<div className={`absolute rounded-xl p-3 ${item.color}`}>
<item.icon className="h-8 w-8 text-white" aria-hidden="true" />
</div>
<p className="ml-20 truncate text-sm font-medium text-gray-500">{item.name}</p>
</dt>
<dd className="ml-20 flex items-baseline">
<p className="text-3xl font-semibold text-gray-900">{item.stat}</p>
</dd>
</div>
))}
</dl>
</div>
</div>
);
}

View File

@@ -0,0 +1,74 @@
import React from "react";
import { Link, useLocation } from "react-router-dom";
import { Building2, Users, BarChart3, Terminal, LayoutDashboard, LogOut } from "lucide-react";
const navigation = [
{ name: "Dashboard", href: "/admin-web/", icon: LayoutDashboard },
{ name: "User Management", href: "/admin-web/user-management", icon: Users },
{ name: "Analytics", href: "/admin-web/analytics", icon: BarChart3 },
{ name: "Logs Explorer", href: "/admin-web/logs", icon: Terminal },
];
export default function Layout({ children }) {
const location = useLocation();
return (
<div className="flex h-screen bg-slate-50">
{/* Sidebar */}
<div className="hidden md:flex md:flex-shrink-0">
<div className="flex flex-col w-64">
{/* Sidebar header */}
<div className="flex items-center h-16 flex-shrink-0 px-4 bg-white border-b border-slate-200">
<Building2 className="h-8 w-8 text-blue-600" />
<span className="ml-3 font-bold text-xl text-slate-800">Krow Admin</span>
</div>
{/* Sidebar content */}
<div className="flex-1 flex flex-col overflow-y-auto bg-white">
<nav className="flex-1 px-4 py-4">
{navigation.map((item) => (
<Link
key={item.name}
to={item.href}
className={`
${
location.pathname === item.href
? "bg-blue-50 text-blue-600"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900"
}
group flex items-center px-3 py-3 text-sm font-medium rounded-lg
`}
>
<item.icon
className="mr-3 h-6 w-6"
aria-hidden="true"
/>
{item.name}
</Link>
))}
</nav>
<div className="px-4 py-4 border-t border-slate-200">
<Link
to="#"
className="group flex items-center px-3 py-3 text-sm font-medium rounded-lg text-slate-600 hover:bg-slate-100 hover:text-slate-900"
>
<LogOut className="mr-3 h-6 w-6" />
Logout
</Link>
</div>
</div>
</div>
</div>
{/* Main content */}
<div className="flex flex-col w-0 flex-1 overflow-hidden">
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none">
<div className="py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
{children}
</div>
</div>
</main>
</div>
</div>
);
}

View File

@@ -0,0 +1,54 @@
import React from "react";
const logs = [
{ level: "INFO", message: "User admin@krow.com logged in.", timestamp: "2024-05-21T10:00:00Z" },
{ level: "WARN", message: "API response time is slow: 2500ms for /api/vendors", timestamp: "2024-05-21T10:01:00Z" },
{ level: "ERROR", message: "Failed to process payment for invoice INV-12345.", timestamp: "2024-05-21T10:02:00Z" },
{ level: "INFO", message: "New vendor 'New Staffing Co' invited by admin@krow.com.", timestamp: "2024-05-21T10:05:00Z" },
];
export default function Logs() {
return (
<div>
<h1 className="text-3xl font-bold text-slate-800">Logs Explorer</h1>
<p className="mt-2 text-base text-slate-600">
Monitor system logs across your application.
</p>
<div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-2xl">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-slate-50">
<tr>
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Level</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Message</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Timestamp</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{logs.map((log, index) => (
<tr key={index}>
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
<span className={`inline-flex items-center px-3 py-1 rounded-full text-xs font-medium ${
log.level === 'ERROR' ? 'bg-red-100 text-red-800' :
log.level === 'WARN' ? 'bg-yellow-100 text-yellow-800' :
'bg-green-100 text-green-800'
}`}>
{log.level}
</span>
</td>
<td className="px-3 py-4 text-sm text-gray-500 font-mono">{log.message}</td>
<td className="px-3 py-4 text-sm text-gray-500">{new Date(log.timestamp).toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,146 @@
import React, { useState } from "react";
import { Plus, Send } from "lucide-react";
const users = [
{ name: "Alex Johnson", email: "alex.j@krow.com", role: "Admin", status: "Active", avatar: "https://randomuser.me/api/portraits/men/1.jpg" },
{ name: "Samantha Lee", email: "samantha.lee@vendor.com", role: "Vendor", status: "Active", avatar: "https://randomuser.me/api/portraits/women/2.jpg" },
{ name: "Michael Chen", email: "michael.chen@client.com", role: "Client", status: "Pending", avatar: "https://randomuser.me/api/portraits/men/3.jpg" },
];
function InviteUserModal({ isOpen, onClose }) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity">
<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div className="relative transform overflow-hidden rounded-2xl bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<h3 className="text-xl font-semibold leading-6 text-slate-900">Send Invitation</h3>
<p className="mt-2 text-sm text-slate-500">The user will receive an email with a link to set up their account.</p>
<div className="mt-6 space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email address
</label>
<input
type="email"
name="email"
id="email"
className="mt-2 block w-full rounded-lg border-0 py-2.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
placeholder="colleague@company.com"
/>
</div>
<div>
<label htmlFor="role" className="block text-sm font-medium text-gray-700">
Access level
</label>
<select
id="role"
name="role"
className="mt-2 block w-full rounded-lg border-0 py-2.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6"
defaultValue="User"
>
<option>Admin</option>
<option>Vendor</option>
<option>Client</option>
<option>User</option>
</select>
</div>
</div>
</div>
<div className="bg-slate-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button
type="button"
className="inline-flex w-full items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 sm:ml-3 sm:w-auto"
onClick={onClose}
>
<Send className="h-5 w-5 mr-2" />
Send Invitation
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-lg bg-white px-4 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={onClose}
>
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
);
}
export default function UserManagement() {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<div className="sm:flex sm:items-center">
<div className="sm:flex-auto">
<h1 className="text-3xl font-bold text-slate-800">User Management</h1>
<p className="mt-2 text-base text-slate-600">
A list of all the users in your account including their name, email and role.
</p>
</div>
<div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
<button
type="button"
className="inline-flex items-center justify-center rounded-lg border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 sm:w-auto"
onClick={() => setIsModalOpen(true)}
>
<Plus className="h-5 w-5 mr-2" />
Invite User
</button>
</div>
</div>
<div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-2xl">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-slate-50">
<tr>
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Name</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Email</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Role</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{users.map((user) => (
<tr key={user.email}>
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
<div className="flex items-center">
<div className="h-11 w-11 flex-shrink-0">
<img className="h-11 w-11 rounded-full" src={user.avatar} alt="" />
</div>
<div className="ml-4">
<div className="font-medium text-gray-900">{user.name}</div>
</div>
</div>
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{user.email}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{user.role}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
user.status === 'Active' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
}`}>
{user.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
<InviteUserModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
</div>
);
}