Frontend Pages
Documentation for all frontend pages in the reseller platform.
Overview
The reseller platform adds the following pages to the AutoCom frontend:
/reseller-catalog
├── / # Catalog overview
├── /master-products # Master product management (super_admin)
├── /my-catalog # Available products (reseller)
└── /pricing # Pricing management
/reseller-orders
├── / # Chain orders dashboard
├── /incoming # Orders from sub-resellers
├── /forwarded # Orders sent to parent
└── /fulfillment # Orders to fulfill
/reseller-finance
├── / # Finance dashboard
├── /wallet # Wallet details
├── /settlements # Pending settlements
└── /transactions # Transaction history
Navigation Visibility
Pages are filtered by tenant type:
| Tenant Type | Visible Pages |
|---|---|
super_admin |
All pages including Master Products |
reseller |
All pages except Master Products, includes My Catalog |
standard |
None (reseller pages hidden) |
ResellerCatalog Pages
Catalog Overview
Route: /reseller-catalog
File: frontend/app/reseller-catalog/page.tsx
Features:
- Product count summary
- Recent product activity
- Quick links to sub-pages
- Pricing health indicators
Master Products (Super Admin)
Route: /reseller-catalog/master-products
File: frontend/app/reseller-catalog/master-products/page.tsx
Features:
- Product grid/list view
- Create new product modal
- Inline editing
- Bulk actions (activate, deactivate, delete)
- Stock management
- Image upload
- Filter by status, category
- Search by name, SKU
- Export to CSV
UI Components:
// Product card in grid view
<Card>
<CardHeader>
<img src={product.images[0]} />
<Badge>{product.status}</Badge>
</CardHeader>
<CardContent>
<h3>{product.name}</h3>
<p>SKU: {product.sku}</p>
<p>Base Cost: ₹{product.base_cost}</p>
<p>Stock: {product.stock_quantity}</p>
</CardContent>
<CardFooter>
<Button>Edit</Button>
<Button>Set Pricing</Button>
</CardFooter>
</Card>
My Catalog (Reseller)
Route: /reseller-catalog/my-catalog
File: frontend/app/reseller-catalog/my-catalog/page.tsx
Features:
- Products available at your cost
- Margin calculator
- Suggested selling price
- Stock availability
- Quick price setting for sub-resellers
Key UI:
// Product with pricing info
<Card>
<CardContent>
<h3>{product.name}</h3>
<div className="pricing">
<span className="cost">Your Cost: ₹{product.your_cost}</span>
<span className="suggested">Suggested: ₹{product.suggested_price}</span>
<Badge>Min Margin: {product.min_margin}%</Badge>
</div>
<div className="stock">
{product.available_stock > 0
? <Badge variant="success">In Stock ({product.available_stock})</Badge>
: <Badge variant="destructive">Out of Stock</Badge>
}
</div>
</CardContent>
</Card>
Pricing Management
Route: /reseller-catalog/pricing
File: frontend/app/reseller-catalog/pricing/page.tsx
Features:
- Set prices for sub-resellers
- Bulk pricing updates
- Margin analysis
- Price breakdown view
ResellerOrders Pages
Chain Orders Dashboard
Route: /reseller-orders
File: frontend/app/reseller-orders/page.tsx
Features:
- Order status overview cards
- Status breakdown chart
- Recent orders list
- Quick filters
- Action buttons
UI Layout:
<div className="grid gap-4 md:grid-cols-4">
<StatsCard title="Pending" value={stats.pending} icon={Clock} />
<StatsCard title="Processing" value={stats.processing} icon={Package} />
<StatsCard title="Shipped" value={stats.shipped} icon={Truck} />
<StatsCard title="Delivered" value={stats.delivered} icon={CheckCircle} />
</div>
<Card>
<CardHeader>
<CardTitle>Recent Chain Orders</CardTitle>
</CardHeader>
<CardContent>
<OrdersTable orders={recentOrders} />
</CardContent>
</Card>
Incoming Orders
Route: /reseller-orders/incoming
File: frontend/app/reseller-orders/incoming/page.tsx
Features:
- Orders from sub-resellers
- Accept/Forward actions
- Bulk operations
- Filter by status, sub-reseller, date
- Order details modal
Key Actions:
// Action buttons per order
<div className="actions">
<Button onClick={() => acceptOrder(order.id)}>
Accept
</Button>
<Button variant="outline" onClick={() => forwardOrder(order.id)}>
Forward to Parent
</Button>
<Button variant="ghost" onClick={() => viewDetails(order.id)}>
View Details
</Button>
</div>
Forwarded Orders
Route: /reseller-orders/forwarded
File: frontend/app/reseller-orders/forwarded/page.tsx
Features:
- Orders sent up the chain
- Track fulfillment progress
- Settlement status
- Filter by status, date
UI Elements:
// Order with fulfillment status
<Card>
<CardContent>
<div className="order-header">
<span>{order.origin_order_number}</span>
<Badge>{order.chain_status}</Badge>
</div>
<div className="fulfiller">
Fulfilling: {order.fulfiller_name || 'Pending'}
</div>
{order.tracking_number && (
<div className="tracking">
<Truck className="h-4 w-4" />
{order.carrier}: {order.tracking_number}
</div>
)}
<div className="settlement">
Your Margin: ₹{order.your_margin}
<Badge variant={order.settled ? 'success' : 'warning'}>
{order.settled ? 'Settled' : 'Pending'}
</Badge>
</div>
</CardContent>
</Card>
Fulfillment Queue
Route: /reseller-orders/fulfillment
File: frontend/app/reseller-orders/fulfillment/page.tsx
Features:
- Orders you need to ship
- Generate shipping labels
- Bulk ship functionality
- Print packing slips
- Carrier selection
Shipping Flow:
// Ship order modal
<Dialog>
<DialogContent>
<DialogHeader>
<DialogTitle>Ship Order #{order.origin_order_number}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<Select value={carrier} onValueChange={setCarrier}>
<SelectItem value="delhivery">Delhivery</SelectItem>
<SelectItem value="shiprocket">Shiprocket</SelectItem>
<SelectItem value="manual">Manual Entry</SelectItem>
</Select>
<Input
placeholder="Tracking Number"
value={trackingNumber}
onChange={(e) => setTrackingNumber(e.target.value)}
/>
</div>
<DialogFooter>
<Button onClick={handleShip}>Mark as Shipped</Button>
</DialogFooter>
</DialogContent>
</Dialog>
ResellerFinance Pages
Finance Dashboard
Route: /reseller-finance
File: frontend/app/reseller-finance/page.tsx
Features:
- Wallet balance overview
- Available vs total balance
- Pending settlements summary
- Net position indicator
- Quick links
Layout:
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader>
<CardTitle>Available Balance</CardTitle>
<Wallet className="h-4 w-4" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">
₹{wallet.available_balance.toLocaleString()}
</div>
<p className="text-sm text-muted">
Total: ₹{wallet.balance.toLocaleString()}
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Pending Credits</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
₹{wallet.pending_credits.toLocaleString()}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>On Hold</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-orange-600">
₹{wallet.pending_debits.toLocaleString()}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Net Position</CardTitle>
</CardHeader>
<CardContent>
<div className={`text-2xl font-bold ${
netPosition >= 0 ? 'text-green-600' : 'text-red-600'
}`}>
{netPosition >= 0 ? '+' : ''}₹{netPosition.toLocaleString()}
</div>
</CardContent>
</Card>
</div>
Wallet Details
Route: /reseller-finance/wallet
File: frontend/app/reseller-finance/wallet/page.tsx
Features:
- Detailed balance breakdown
- Period summary (week/month/quarter/year)
- Transaction breakdown by type
- Lifetime statistics
- Credit limit info
Period Selector:
<Select value={period} onValueChange={setPeriod}>
<SelectItem value="week">This Week</SelectItem>
<SelectItem value="month">This Month</SelectItem>
<SelectItem value="quarter">This Quarter</SelectItem>
<SelectItem value="year">This Year</SelectItem>
</Select>
Settlements
Route: /reseller-finance/settlements
File: frontend/app/reseller-finance/settlements/page.tsx
Features:
- To Pay tab - Remittances you owe
- To Receive tab - Remittances owed to you
- History tab - Completed settlements
- Process individual remittances
- Bulk payment
- Overdue indicators
Tabs Layout:
<Tabs defaultValue="to-pay">
<TabsList>
<TabsTrigger value="to-pay">
To Pay
{toPay.length > 0 && (
<Badge variant="destructive" className="ml-2">
{toPay.length}
</Badge>
)}
</TabsTrigger>
<TabsTrigger value="to-receive">
To Receive
{toReceive.length > 0 && (
<Badge className="ml-2 bg-green-600">
{toReceive.length}
</Badge>
)}
</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>
<TabsContent value="to-pay">
<RemittanceTable
remittances={toPay}
direction="outgoing"
onProcess={handleProcess}
/>
</TabsContent>
<TabsContent value="to-receive">
<RemittanceTable
remittances={toReceive}
direction="incoming"
/>
</TabsContent>
<TabsContent value="history">
<RemittanceTable
remittances={history}
showStatus
/>
</TabsContent>
</Tabs>
Process Remittance Dialog:
<AlertDialog>
<AlertDialogTrigger asChild>
<Button>Mark Paid</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirm Payment</AlertDialogTitle>
<AlertDialogDescription>
This will debit ₹{remittance.amount.toLocaleString()} from
your wallet and credit it to {remittance.to_tenant.name}.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={confirmPayment}>
Confirm Payment
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Transaction History
Route: /reseller-finance/transactions
File: frontend/app/reseller-finance/transactions/page.tsx
Features:
- Full transaction log
- Filter by type (credit/debit/hold/release)
- Filter by reference type
- Date range filter
- Search
- Export to CSV
- Pagination
Filters:
<div className="grid gap-4 md:grid-cols-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4" />
<Input placeholder="Search..." className="pl-9" />
</div>
<Select value={typeFilter} onValueChange={setTypeFilter}>
<SelectItem value="all">All Types</SelectItem>
<SelectItem value="credit">Credits</SelectItem>
<SelectItem value="debit">Debits</SelectItem>
<SelectItem value="hold">Holds</SelectItem>
<SelectItem value="release">Releases</SelectItem>
</Select>
<Select value={refFilter} onValueChange={setRefFilter}>
<SelectItem value="all">All References</SelectItem>
<SelectItem value="chain_order">Chain Orders</SelectItem>
<SelectItem value="remittance">Remittances</SelectItem>
</Select>
<Button variant="outline" onClick={exportCSV}>
<Download className="h-4 w-4 mr-2" />
Export
</Button>
</div>
Transaction Row:
<TableRow>
<TableCell>{formatDate(txn.created_at)}</TableCell>
<TableCell>
<div className="flex items-center gap-2">
{getTypeIcon(txn.type)}
<Badge variant={getTypeVariant(txn.type)}>
{txn.type}
</Badge>
</div>
</TableCell>
<TableCell className="max-w-xs truncate">
{txn.description}
</TableCell>
<TableCell>
<Badge variant="outline">
{txn.reference_type?.replace('_', ' ')}
</Badge>
</TableCell>
<TableCell className={`text-right font-medium ${
txn.type === 'credit' ? 'text-green-600' :
txn.type === 'debit' ? 'text-red-600' : ''
}`}>
{txn.type === 'credit' ? '+' : txn.type === 'debit' ? '-' : ''}
₹{txn.amount.toLocaleString()}
</TableCell>
<TableCell className="text-right">
₹{txn.balance_after.toLocaleString()}
</TableCell>
</TableRow>
Shared Components
Currency Formatter
const formatCurrency = (amount: number, currency = 'INR') => {
return new Intl.NumberFormat('en-IN', {
style: 'currency',
currency,
}).format(amount);
};
Status Badge
const StatusBadge = ({ status }: { status: string }) => {
const variants: Record<string, string> = {
pending_forward: 'bg-yellow-100 text-yellow-800',
forwarded: 'bg-blue-100 text-blue-800',
accepted: 'bg-purple-100 text-purple-800',
processing: 'bg-indigo-100 text-indigo-800',
shipped: 'bg-cyan-100 text-cyan-800',
delivered: 'bg-green-100 text-green-800',
cancelled: 'bg-red-100 text-red-800',
};
return (
<Badge className={variants[status] || 'bg-gray-100 text-gray-800'}>
{status.replace('_', ' ')}
</Badge>
);
};
Empty State
const EmptyState = ({
icon: Icon,
title,
description
}: {
icon: LucideIcon;
title: string;
description: string;
}) => (
<div className="text-center py-12">
<Icon className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
<h3 className="text-lg font-medium">{title}</h3>
<p className="text-muted-foreground">{description}</p>
</div>
);
State Management
Pages use React hooks for local state:
// Example: Settlements page state
const [toPay, setToPay] = useState<Remittance[]>([]);
const [toReceive, setToReceive] = useState<Remittance[]>([]);
const [loading, setLoading] = useState(true);
const [processing, setProcessing] = useState<string | null>(null);
useEffect(() => {
fetchSettlements();
}, []);
const fetchSettlements = async () => {
setLoading(true);
try {
const response = await api.get('/finance/settlements/pending');
setToPay(response.data.to_pay);
setToReceive(response.data.to_receive);
} catch (error) {
toast.error('Failed to load settlements');
} finally {
setLoading(false);
}
};
API Integration
All pages connect to the API using the configured client:
// lib/api.ts
import axios from 'axios';
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});
// Add tenant headers
api.interceptors.request.use((config) => {
const tenant = getTenantFromContext();
config.headers['X-Tenant'] = tenant.id;
// For context switching
const effectiveTenant = getEffectiveTenant();
if (effectiveTenant) {
config.headers['X-Effective-Tenant'] = effectiveTenant.id;
}
return config;
});
export default api;