Skip to content
29 changes: 28 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import RootLayout from './layouts/RootLayout';
import { BuyerSidebar, SellerSidebar, AdminSidebar } from './components/RoleBasedSidebar';
import {
BuyerSidebar,
SellerSidebar,
AdminSidebar,
} from './components/RoleBasedSidebar';
import WorkspaceView from './components/WorkspaceView';

import { CartProvider } from './context/CartContext';
Expand All @@ -18,27 +22,50 @@ import Orders from './pages/buyer/Orders';
// Seller Pages
import Dashboard from './pages/seller/dashboard';
import Catalog from './pages/seller/catalog';
import Inventory from './pages/seller/inventory';
import SellerOrders from './pages/seller/orders';
import Earnings from './pages/seller/earnings';

// Admin Pages
import Overview from './pages/admin/overview';
import Verify from './pages/admin/verify';
import Moderate from './pages/admin/moderate';
import Users from './pages/admin/users';
import Logs from './pages/admin/logs';
import Disputes from './pages/admin/disputes';

const App = () => (
<CartProvider>
<BrowserRouter>
<Routes>
{/* Admin Routes */}
<Route
path="/admin/*"
element={<RootLayout sidebarContent={<AdminSidebar />} />}
>
<Route path="overview" element={<Overview />} />
<Route path="verify" element={<Verify />} />
<Route path="moderate" element={<Moderate />} />
<Route path="users" element={<Users />} />
<Route path="logs" element={<Logs />} />
<Route path="disputes" element={<Disputes />} />
<Route path="*" element={<WorkspaceView />} />
</Route>

{/* Seller Routes */}
<Route
path="/seller/*"
element={<RootLayout sidebarContent={<SellerSidebar />} />}
>
<Route path="dashboard" element={<Dashboard />} />
<Route path="catalog" element={<Catalog />} />
<Route path="inventory" element={<Inventory />} />
<Route path="orders" element={<SellerOrders />} />
<Route path="earnings" element={<Earnings />} />
<Route path="*" element={<WorkspaceView />} />
</Route>

{/* Buyer Routes */}
<Route
path="/*"
element={<RootLayout sidebarContent={<BuyerSidebar />} />}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/RoleBasedSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export const SellerSidebar = () => (
icon={Store}
/>
<SidebarNavItem
to="/seller/fulfillment"
to="/seller/orders"
label="Order Fulfilment"
icon={Truck}
/>
Expand All @@ -133,7 +133,7 @@ export const AdminSidebar = () => (
<div className="flex flex-col pb-6 pt-4">
<ul className="flex flex-col">
<SidebarNavItem
to="/admin"
to="/admin/overview"
exact
label="System Overview"
icon={LayoutDashboard}
Expand Down
64 changes: 64 additions & 0 deletions frontend/src/components/seller/ActionModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useEffect } from 'react';

Check failure on line 1 in frontend/src/components/seller/ActionModal.jsx

View workflow job for this annotation

GitHub Actions / Frontend Build & Lint

'React' is defined but never used
import { X, Info } from 'lucide-react';

const ActionModal = ({ isOpen, onClose, title, message }) => {
// Prevent scrolling when modal is open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);

if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6">
{/* Backdrop */}
<div
className="absolute inset-0 bg-neutral-dark/40 backdrop-blur-sm transition-opacity"
onClick={onClose}
/>

{/* Modal Dialog */}
<div className="relative bg-background rounded-2xl shadow-xl w-full max-w-md overflow-hidden transform transition-all">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-neutral-dark/10">
<h3 className="text-lg font-headline font-bold text-neutral-dark flex items-center gap-2">
<Info className="w-5 h-5 text-[#8C5233]" />
{title}
</h3>
<button
onClick={onClose}
className="text-neutral-dark/50 hover:text-neutral-dark hover:bg-neutral-dark/5 rounded-full p-2 transition-colors focus:outline-none focus:ring-2 focus:ring-[#8C5233]"
>
<X className="w-5 h-5" />
</button>
</div>

{/* Content */}
<div className="p-6">
<p className="text-[14px] font-sans text-neutral-dark/70 leading-relaxed">
{message || "This feature is currently under development. Check back later!"}
</p>
</div>

{/* Footer */}
<div className="flex justify-end p-6 bg-neutral-dark/5 border-t border-neutral-dark/10">
<button
onClick={onClose}
className="px-6 py-2.5 rounded-md bg-[#8C5233] hover:bg-[#7E4A2E] text-white text-[13px] font-sans font-bold tracking-wide transition-colors shadow-sm"
>
Got it
</button>
</div>
</div>
</div>
);
};

export default ActionModal;
44 changes: 44 additions & 0 deletions frontend/src/components/seller/AlertBanner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';

Check failure on line 1 in frontend/src/components/seller/AlertBanner.jsx

View workflow job for this annotation

GitHub Actions / Frontend Build & Lint

'React' is defined but never used
import { TriangleAlert } from 'lucide-react';

const AlertBanner = ({ title, message, buttonText, onClick, variant = 'danger', icon: Icon = TriangleAlert }) => {
// Danger variant (Red/Pink)
let containerClass = "bg-[#FCF1F0] border border-[#EAC2BE]";
let iconContainerClass = "bg-[#F8E2DF]";
let iconColor = "text-destructive";
let buttonClass = "bg-[#C85746] hover:bg-destructive text-white";

// Warning variant (Beige/Brown)
if (variant === 'warning') {
containerClass = "bg-[#F3EBE3] border border-[#E3D4C4]";
iconContainerClass = "bg-[#EBDDD0]";
iconColor = "text-[#8C5233]";
buttonClass = "bg-[#8C5233] hover:bg-[#7E4A2E] text-white";
}

return (
<div className={`${containerClass} rounded-lg p-5 flex flex-col md:flex-row items-start md:items-center justify-between gap-4 w-full`}>
<div className="flex items-start gap-4">
<div className={`${iconContainerClass} p-2 rounded-md shrink-0 mt-0.5`}>
<Icon className={`w-5 h-5 ${iconColor}`} />
</div>
<div>
<h4 className="text-[13px] font-sans font-bold text-neutral-dark mb-1">
{title}
</h4>
<p className="text-[13px] font-sans text-neutral-dark/70">
{message}
</p>
</div>
</div>
<button
onClick={onClick}
className={`shrink-0 px-5 py-2.5 rounded-md text-[11px] font-bold tracking-widest uppercase transition-colors ${buttonClass}`}
>
{buttonText}
</button>
</div>
);
};

export default AlertBanner;
44 changes: 44 additions & 0 deletions frontend/src/components/seller/DailyShipmentsChart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';

Check failure on line 1 in frontend/src/components/seller/DailyShipmentsChart.jsx

View workflow job for this annotation

GitHub Actions / Frontend Build & Lint

'React' is defined but never used

const DailyShipmentsChart = ({ data }) => {
// data is expected to be an array of objects: { label: 'MON', value: 40, isHighlighted: false }
const maxVal = Math.max(...data.map(d => d.value), 1);

return (
<div className="card-custom !p-8 flex flex-col h-full group hover:card-custom-hover min-h-[320px]">
<div className="mb-8 text-center sm:text-left">
<h3 className="text-[11px] font-sans font-bold tracking-widest text-neutral-dark/60 uppercase max-w-[60%] leading-relaxed mx-auto sm:mx-0">
DAILY SHIPMENTS
</h3>
<p className="text-[13px] font-sans text-[#8C5233] font-bold mt-1">
Last 7 Days
</p>
</div>

{/* Bar Chart Area */}
<div className="flex-1 flex flex-col justify-end relative mt-4">
<div className="flex justify-between items-end h-full gap-2 px-2 pb-8">
{data.map((item, i) => {
const heightPercent = (item.value / maxVal) * 100;
return (
<div
key={i}
className={`w-full transition-colors duration-300 rounded-t-sm ${item.isHighlighted ? 'bg-[#8C5233]' : 'bg-[#EBE5D9] hover:bg-[#DED7C9]'}`}
style={{ height: `${heightPercent}%` }}
/>
);
})}
</div>

{/* X-Axis Labels */}
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-3 text-[8px] font-sans font-bold tracking-widest text-neutral-dark/40 uppercase">
{data.map((item, i) => (
<span key={i} className="w-full text-center">{item.label}</span>
))}
</div>
</div>
</div>
);
};

export default DailyShipmentsChart;
7 changes: 5 additions & 2 deletions frontend/src/components/seller/DashboardFAB.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';
import { Plus } from 'lucide-react';

const DashboardFAB = () => {
const DashboardFAB = ({ onClick }) => {
return (
<button className="fixed bottom-8 right-8 w-14 h-14 bg-[#8C5233] hover:bg-[#7E4A2E] text-white rounded-full flex items-center justify-center shadow-[0_8px_20px_rgba(140,82,51,0.4)] transition-transform hover:scale-105 active:scale-95 outline-none focus-visible:ring-4 focus-visible:ring-primary/40 z-50">
<button
onClick={onClick}
className="fixed bottom-8 right-8 w-14 h-14 bg-[#8C5233] hover:bg-[#7E4A2E] text-white rounded-full flex items-center justify-center shadow-[0_8px_20px_rgba(140,82,51,0.4)] transition-transform hover:scale-105 active:scale-95 outline-none focus-visible:ring-4 focus-visible:ring-primary/40 z-50"
>
<Plus className="w-6 h-6" />
</button>
);
Expand Down
34 changes: 24 additions & 10 deletions frontend/src/components/seller/DashboardHeader.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import React from 'react';
import { Search, Bell, MessageSquare } from 'lucide-react';
import { Search, Bell, MessageSquare, Settings } from 'lucide-react';

const DashboardHeader = () => {
const DashboardHeader = ({
user = {
name: 'Habib Mangoranda',
role: 'Master Potter',
initials: 'HM'
},
searchPlaceholder = 'Search orders, products...',
showSettings = false
}) => {
return (
<header className="flex items-center justify-between py-6">
{/* Search Bar */}
Expand All @@ -12,7 +20,7 @@ const DashboardHeader = () => {
<input
type="text"
className="input-search rounded-full"
placeholder="Search orders, products..."
placeholder={searchPlaceholder}
/>
</div>

Expand All @@ -21,20 +29,26 @@ const DashboardHeader = () => {
<button className="text-neutral-dark/70 hover:text-neutral-dark transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-full">
<Bell className="w-5 h-5" />
</button>
<button className="text-neutral-dark/70 hover:text-neutral-dark transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-full">
<MessageSquare className="w-5 h-5" />
</button>
{showSettings ? (
<button className="text-neutral-dark/70 hover:text-neutral-dark transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-full">
<Settings className="w-5 h-5" />
</button>
) : (
<button className="text-neutral-dark/70 hover:text-neutral-dark transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-full">
<MessageSquare className="w-5 h-5" />
</button>
)}

{/* User Profile */}
<div className="flex items-center gap-3 pl-6 border-l border-neutral-dark/10 cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-lg p-1">
<div className="text-right hidden sm:block">
<div className="text-sm font-semibold text-neutral-dark">Habib Mangoranda</div>
<div className="text-xs font-label text-neutral-dark/60">Master Potter</div>
<div className="text-sm font-semibold text-neutral-dark">{user.name}</div>
<div className="text-xs font-label text-neutral-dark/60">{user.role}</div>
</div>
<div className="w-10 h-10 rounded-full overflow-hidden border border-neutral-dark/10">
<img
src="https://placehold.co/100x100/362E25/F5F0E8?text=ER"
alt="Elena Rodriguez"
src={`https://placehold.co/100x100/362E25/F5F0E8?text=${user.initials}`}
alt={user.name}
className="w-full h-full object-cover"
/>
</div>
Expand Down
46 changes: 46 additions & 0 deletions frontend/src/components/seller/DeliveryPerformance.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { Clock } from 'lucide-react';

const DeliveryPerformance = ({ onTimeRate, avgFulfillmentTime, performanceMessage }) => {
return (
<div className="card-custom !p-8 flex flex-col h-full group hover:card-custom-hover mt-6 lg:mt-0">
<h3 className="text-[11px] font-sans font-bold tracking-widest text-neutral-dark/60 uppercase leading-relaxed mb-6">
DELIVERY PERFORMANCE
</h3>

{/* On-Time Rate */}
<div className="mb-8">
<div className="flex justify-between items-end mb-2">
<span className="text-[13px] font-sans text-neutral-dark/70 font-medium">On-Time Delivery Rate</span>
<span className="text-[13px] font-sans font-bold text-[#8C5233]">{onTimeRate}%</span>
</div>
<div className="h-1.5 w-full bg-[#EBE5D9] rounded-full overflow-hidden">
<div
className="h-full bg-[#8C5233] rounded-full transition-all duration-1000"
style={{ width: `${onTimeRate}%` }}
/>
</div>
</div>

{/* Avg Fulfillment Time */}
<div className="flex items-center justify-between mb-8">
<div>
<div className="text-[13px] font-sans text-neutral-dark/70 font-medium mb-1">Avg. Fulfillment Time</div>
<div className="text-[13px] font-sans font-bold text-neutral-dark">{avgFulfillmentTime} Days</div>
</div>
<div className="w-10 h-10 rounded-full flex items-center justify-center bg-[#F5EDE8] shrink-0">
<Clock className="w-5 h-5 text-[#8C5233]" />
</div>
</div>

{/* Message */}
<div className="mt-auto text-center pt-6 border-t border-neutral-dark/10">
<p className="text-[13px] font-sans italic text-neutral-dark/60">
"{performanceMessage}"
</p>
</div>
</div>
);
};

export default DeliveryPerformance;
25 changes: 25 additions & 0 deletions frontend/src/components/seller/InventoryOptimizer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { Sparkles } from 'lucide-react';

const InventoryOptimizer = ({ title, description, buttonText, onOptimize }) => {
return (
<div className="bg-[#8C5233] text-white rounded-[1.75rem] p-8 flex flex-col h-full shadow-[0_10px_30px_-5px_rgba(140,82,51,0.2)]">
<h3 className="text-[15px] font-sans font-bold mb-3">
{title}
</h3>
<p className="text-[13px] font-sans text-white/80 leading-relaxed mb-auto">
{description}
</p>

<button
onClick={onOptimize}
className="mt-8 w-full bg-white text-[#8C5233] hover:bg-neutral-light hover:scale-[1.02] active:scale-[0.98] transition-all py-3 rounded-md flex items-center justify-center gap-2 text-[12px] font-bold tracking-widest uppercase shadow-sm"
>
<Sparkles className="w-4 h-4" />
{buttonText}
</button>
</div>
);
};

export default InventoryOptimizer;
Loading
Loading