The Challenge
The client was running a B2B e-commerce operation that had grown to hundreds of daily orders. Their admin interface was a collection of cobbled-together tools: legacy PHP pages with inline styles, Bootstrap 3 grids that broke when product names were longer than expected, and a purchase order template that had been designed around early 2000s HTML email table techniques.
Every time a rep needed to quote shipping, they opened three browser tabs — one for FedEx, one for UPS, one for USPS — manually entered the package details, and compared the numbers. On a busy day, that was 20+ minutes of wasted time just on shipping lookups.
The goal: A single, modern admin dashboard that works the way their team actually works. No off-the-shelf compromises, no legacy debt — built specifically for their product catalog, their carriers, and their workflow.
Real-Time Shipping Calculator
The shipping calculator was the most technically complex piece. Each of the three major carriers now uses OAuth 2.0-based REST APIs — not the old XML/SOAP interfaces most tutorials still document. We built an object-oriented carrier abstraction where each carrier is its own class implementing a common interface:
interface ShippingCarrier {
public function getOAuthToken(): string;
public function getRates(ShipmentRequest $req): array;
public function createShipment(ShipmentRequest $req): Label;
}
// Each carrier is its own class
class FedExCarrier implements ShippingCarrier { ... }
class UPSCarrier implements ShippingCarrier { ... }
class USPSCarrier implements ShippingCarrier { ... }
// Get rates from all carriers simultaneously
$rates = array_merge(
$fedex->getRates($request),
$ups->getRates($request),
$usps->getRates($request)
);
usort($rates, fn($a,$b) => $a->price <=> $b->price);
OAuth 2.0 Client Credentials
Ground, Express, 2Day
Ground, 3-Day Select
Next Day Air Saver
Priority Mail
First Class, Media Mail
OAuth tokens are cached in the database with expiry timestamps — no unnecessary re-authentication on every rate request. When a token is within 60 seconds of expiring, it's refreshed proactively in the background, so the user never sees an authentication delay.
Purchase Order Template Redesign
The existing PO template was built with nested HTML tables and inline font tags — a relic of the era before CSS was reliable. It printed inconsistently across browsers, the column widths shifted based on content, and it couldn't be styled without touching every cell individually.
The redesign used CSS Grid for the PO line-item table and modern print CSS for letter-size output:
<tr><td width="50%">
<font size="2">
Item name here...
</font>
</td>
<td align="right">
<font size="2">$99.99</font>
</td></tr>
</table>
display: grid;
grid-template-columns:
1fr 80px 80px 100px;
}
@media print {
@page { size: letter; }
.po-lines { font-size: 11px; }
.no-print { display: none; }
}
Fixing the Product Grid
The product listing used Bootstrap 3's float-based grid. When a product name was unusually long or an image failed to load, it would push subsequent tiles out of alignment — creating a "staircase" effect where every other column was offset. This is a fundamental limitation of float-based layouts.
The fix was converting the grid from floats to flexbox with a fixed tile height and align-items: stretch. Each tile is now a flex column with the image at top, name in the middle with flex-grow: 1 to absorb varying lengths, and the price/action buttons pinned to the bottom:
.product-grid { overflow: hidden; }
.product-tile { float: left; width: 25%; }
/* After: flexbox — all tiles same height, no skipping */
.product-grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
}
.product-tile {
display: flex;
flex-direction: column;
width: calc(25% - 16px);
}
.product-tile .name {
flex-grow: 1; /* absorbs varying name lengths */
}
Sidebar Navigation with Permissions
The admin panel serves multiple roles: account managers see customer data and orders, warehouse staff see inventory and shipping labels, admins see everything including configuration and user management. The sidebar nav renders dynamically based on the logged-in user's permission set — no navigation items appear that the user can't access, preventing both accidental misuse and role confusion.
Dark/Light Theme
The entire UI uses CSS custom properties for colors. Switching themes is a single class toggle on the <html> element — no page reload, no JavaScript framework required. Theme preference is persisted in localStorage and remembered across sessions.
:root {
--bg: #ffffff;
--text: #0f172a;
--border: #e2e8f0;
}
/* Dark theme — one selector, zero duplication */
html[data-theme="dark"] {
--bg: #0f172a;
--text: #f1f5f9;
--border: #1e293b;
}
// JS — toggle with localStorage persistence
document.documentElement.dataset.theme =
localStorage.getItem('theme') || 'light';
Feature Summary
📦 Shipping Calculator
FedEx, UPS, USPS rates side-by-side in real time. OAuth tokens cached with auto-refresh. Sorted cheapest-first.
📄 Purchase Orders
CSS Grid layout, letter-size print-ready, consistent column widths regardless of content length.
📷 Product Grid
Flexbox tiles — no "skipped" products. Uniform height. Name overflow handled gracefully.
🔒 Permission System
Role-based sidebar nav. Users only see sections they have access to. Admin, manager, and warehouse roles.
🌙 Dark/Light Mode
CSS custom properties + localStorage. Zero-reload theme switching. Persistent across sessions.
📱 Fully Responsive
Works on tablets for warehouse use. Mobile-first breakpoints. Collapsible sidebar on small screens.
Tech Stack
PHP 8.1 • MySQL 8.0 • FedEx REST API (OAuth 2.0) • UPS REST API (OAuth 2.0) • USPS Web Tools API • CSS Grid • CSS Custom Properties • Vanilla JavaScript • Print CSS