Files
reseller-platform/frontend/src/pages/Home.tsx
drjones b09dcfd929 Add Polaris frontend: storefront + admin console (React 18/Vite/TS/Tailwind)
- Shared src/lib/api.ts fetch wrapper -> /api with Cache-Control: no-store
- Storefront: home hero, shop grid (category filter + search), product detail,
  cart, checkout (POST /api/orders -> confirmation + order number), order tracking
- Admin: JWT login, today KPI dashboard (revenue/orders/gross/net/margin/aov/refunds),
  top products, supplier performance, product import, lifecycle view, order list
  with tracking simulation
- Multi-stage Dockerfile (Vite build -> nginx static + /api proxy -> api:8000)
- nginx.conf with SPA fallback and no-store HTML caching
- mock/mock_api.py for local verification (excluded from image)
2026-08-25 20:28:26 -07:00

130 lines
5.2 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { api, asList } from '../lib/api';
import type { Product } from '../lib/types';
import ProductCard from '../components/ProductCard';
import { EmptyState } from '../components/EmptyState';
import { Spinner } from '../components/Spinner';
import {
IconArrowRight,
IconBoxes,
IconShield,
IconSparkles,
IconTruck,
IconZap,
} from '../components/Icons';
const pillars = [
{ icon: IconZap, title: 'Automated sourcing', body: 'Suppliers are vetted, scored and routed automatically.' },
{ icon: IconSparkles, title: 'Dynamic pricing', body: 'Every listing is priced against the market, continuously.' },
{ icon: IconShield, title: 'Quality checked', body: 'Products pass a lifecycle gate before they ever go live.' },
{ icon: IconTruck, title: 'Tracked fulfillment', body: 'Live tracking on every order, from supplier to doorstep.' },
];
const steps = [
{ n: '01', title: 'Browse & order', body: 'Shop curated products at market-fair prices.' },
{ n: '02', title: 'Automated fulfillment', body: 'Polaris routes your order to the best-scoring supplier.' },
{ n: '03', title: 'Track to your door', body: 'Follow the shipment with live status and tracking.' },
];
export default function Home() {
const [featured, setFeatured] = useState<Product[] | null>(null);
useEffect(() => {
api
.get<Product[]>('/products?status=published&limit=4')
.then((res) => setFeatured(asList(res)))
.catch(() => setFeatured([]));
}, []);
return (
<div className="mx-auto max-w-7xl px-4 sm:px-6">
{/* ── Hero ── */}
<section className="relative py-20 text-center sm:py-28">
<div className="pointer-events-none absolute left-1/2 top-0 h-[420px] w-[720px] -translate-x-1/2 rounded-full bg-indigo-600/20 blur-[120px]" />
<span className="chip">
<IconSparkles className="h-3.5 w-3.5 text-amber-300" />
The North Star of automated e-commerce
</span>
<h1 className="mx-auto mt-6 max-w-3xl text-4xl font-extrabold leading-tight tracking-tight text-white sm:text-6xl">
Curated products.
<br />
<span className="text-gradient">Automated</span> commerce.
</h1>
<p className="mx-auto mt-5 max-w-xl text-base text-slate-400 sm:text-lg">
Polaris sources, prices and fulfills products from vetted suppliers — so every order
ships fast, at a fair price, with tracking you can follow.
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link to="/shop" className="btn-primary !px-6 !py-3 text-base">
Shop the collection
<IconArrowRight className="h-4 w-4" />
</Link>
<Link to="/track" className="btn-ghost !px-6 !py-3 text-base">
<IconTruck className="h-4 w-4" />
Track an order
</Link>
</div>
</section>
{/* ── Pillars ── */}
<section className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{pillars.map((p) => (
<div key={p.title} className="glass glass-hover p-5">
<p.icon className="h-6 w-6 text-indigo-300" />
<h3 className="mt-3 font-semibold text-white">{p.title}</h3>
<p className="mt-1 text-sm leading-relaxed text-slate-400">{p.body}</p>
</div>
))}
</section>
{/* ── Featured ── */}
<section className="py-16">
<div className="mb-6 flex items-end justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-white">Featured now</h2>
<p className="mt-1 text-sm text-slate-400">Fresh from the pipeline, live today.</p>
</div>
<Link to="/shop" className="btn-ghost !py-2 text-sm">
View all
<IconArrowRight className="h-4 w-4" />
</Link>
</div>
{featured === null ? (
<div className="flex justify-center py-16">
<Spinner className="h-7 w-7 text-indigo-400" />
</div>
) : featured.length === 0 ? (
<EmptyState
icon={<IconBoxes className="h-10 w-10" />}
title="Nothing published yet"
hint="The product pipeline is warming up — check back soon."
/>
) : (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{featured.map((p) => (
<ProductCard key={p.id} product={p} />
))}
</div>
)}
</section>
{/* ── How it works ── */}
<section className="pb-20">
<div className="glass p-8 sm:p-10">
<h2 className="text-center text-2xl font-bold text-white">How Polaris works</h2>
<div className="mt-8 grid gap-8 sm:grid-cols-3">
{steps.map((s) => (
<div key={s.n} className="text-center">
<div className="text-gradient mx-auto text-3xl font-black">{s.n}</div>
<h3 className="mt-2 font-semibold text-white">{s.title}</h3>
<p className="mx-auto mt-1 max-w-[240px] text-sm text-slate-400">{s.body}</p>
</div>
))}
</div>
</div>
</section>
</div>
);
}