From f263a9cc55df0185cdb2251c3c4321e65650b41a Mon Sep 17 00:00:00 2001 From: Cristian Cruz Date: Sun, 19 Jul 2026 19:37:41 -0500 Subject: [PATCH] feat(ecommerce): migrate Ari Shopping to Next.js 15 headless storefront v4 --- workloads/ecommerce/README.md | 59 ++ workloads/ecommerce/TECHNOLOGY.md | 29 + workloads/ecommerce/app/api/health/route.ts | 3 + workloads/ecommerce/app/catalog/page.tsx | 26 + workloads/ecommerce/app/globals.css | 20 + workloads/ecommerce/app/layout.tsx | 20 + workloads/ecommerce/app/not-found.tsx | 6 + workloads/ecommerce/app/page.tsx | 14 + .../ecommerce/app/product/[slug]/page.tsx | 30 + .../ecommerce/components/cart/cart-drawer.tsx | 58 ++ .../components/catalog/catalog-client.tsx | 38 ++ .../components/home/category-tiles.tsx | 14 + .../ecommerce/components/home/hero-bento.tsx | 25 + .../ecommerce/components/home/trust-bar.tsx | 12 + .../ecommerce/components/layout/footer.tsx | 11 + .../ecommerce/components/layout/navbar.tsx | 48 ++ .../components/product/mobile-add-to-cart.tsx | 11 + .../components/product/product-card.tsx | 30 + .../components/product/product-carousel.tsx | 9 + .../components/product/product-gallery.tsx | 11 + .../components/product/product-purchase.tsx | 14 + .../components/search/predictive-search.tsx | 32 + .../ecommerce/components/ui/accordion.tsx | 28 + workloads/ecommerce/components/ui/badge.tsx | 6 + workloads/ecommerce/components/ui/button.tsx | 36 ++ workloads/ecommerce/components/ui/dialog.tsx | 21 + workloads/ecommerce/components/ui/input.tsx | 12 + .../ecommerce/components/ui/progress.tsx | 12 + workloads/ecommerce/components/ui/sheet.tsx | 38 ++ workloads/ecommerce/data/products.json | 597 ++++++++++++++++++ workloads/ecommerce/lib/headless/client.ts | 24 + .../lib/headless/providers/medusa.ts | 31 + .../ecommerce/lib/headless/providers/mock.ts | 42 ++ .../lib/headless/providers/shopify.ts | 45 ++ workloads/ecommerce/lib/headless/types.ts | 45 ++ workloads/ecommerce/lib/utils.ts | 15 + workloads/ecommerce/next-env.d.ts | 2 + workloads/ecommerce/package.json | 38 ++ workloads/ecommerce/postcss.config.mjs | 7 + .../ecommerce/public/images/brand/favicon.png | Bin 0 -> 33603 bytes .../public/images/brand/logo-ari.png | Bin 0 -> 156305 bytes .../public/images/categories/figuras.webp | Bin 0 -> 61680 bytes .../public/images/categories/marvel.webp | Bin 0 -> 156726 bytes .../public/images/categories/mochilas.webp | Bin 0 -> 156726 bytes .../public/images/categories/peluches.webp | Bin 0 -> 89982 bytes .../public/images/collections/frozen.webp | Bin 0 -> 25678 bytes .../images/collections/intensamente.webp | Bin 0 -> 34368 bytes .../images/collections/lilo-stitch.webp | Bin 0 -> 19036 bytes .../public/images/collections/marvel.webp | Bin 0 -> 42370 bytes .../images/collections/mickey-minnie.webp | Bin 0 -> 21304 bytes .../public/images/collections/toy-story.webp | Bin 0 -> 31036 bytes .../public/images/hero/hero-real.webp | Bin 0 -> 109730 bytes .../products/ari-001-toy-story-5-rex.webp | Bin 0 -> 111334 bytes .../ari-002-toy-story-5-buzz-lightyear.webp | Bin 0 -> 195528 bytes .../products/ari-003-toy-story-5-jessie.webp | Bin 0 -> 199592 bytes .../products/ari-004-toy-story-5-woody.webp | Bin 0 -> 187640 bytes .../products/ari-005-intensamente-furia.webp | Bin 0 -> 154010 bytes .../ari-006-intensamente-tristeza.webp | Bin 0 -> 167484 bytes .../ari-007-minnie-y-mickey-mouse.webp | Bin 0 -> 171462 bytes .../products/ari-008-angel-lilo-y-stitch.webp | Bin 0 -> 142446 bytes ...-009-mochilas-disney-coleccion-marvel.webp | Bin 0 -> 301492 bytes ...hila-ligera-y-resistente-al-agua-de-d.webp | Bin 0 -> 254682 bytes ...hila-escolar-de-spider-man-de-disney-.webp | Bin 0 -> 351278 bytes ...ney-mochila-de-poliester-ultraligera-.webp | Bin 0 -> 167464 bytes .../ari-013-mochila-pequena-bob-esponja.webp | Bin 0 -> 128842 bytes ...hila-casual-de-gran-capacidad-con-est.webp | Bin 0 -> 115834 bytes ...hila-escolar-estilo-britanico-de-disn.webp | Bin 0 -> 47702 bytes ...ney-mochila-iron-man-de-poliester-ult.webp | Bin 0 -> 62396 bytes ...hila-de-disney-marvel-capitan-america.webp | Bin 0 -> 57066 bytes .../public/images/promo/hero-vibrant.webp | Bin 0 -> 125156 bytes .../public/images/promo/promo-backpacks.webp | Bin 0 -> 75942 bytes .../public/images/promo/promo-characters.webp | Bin 0 -> 64210 bytes .../public/images/promo/promo-marvel.webp | Bin 0 -> 62090 bytes .../ecommerce/scripts/validate-package.sh | 11 + workloads/ecommerce/store/cart-store.ts | 52 ++ workloads/ecommerce/tsconfig.json | 25 + 76 files changed, 1607 insertions(+) create mode 100644 workloads/ecommerce/README.md create mode 100644 workloads/ecommerce/TECHNOLOGY.md create mode 100644 workloads/ecommerce/app/api/health/route.ts create mode 100644 workloads/ecommerce/app/catalog/page.tsx create mode 100644 workloads/ecommerce/app/globals.css create mode 100644 workloads/ecommerce/app/layout.tsx create mode 100644 workloads/ecommerce/app/not-found.tsx create mode 100644 workloads/ecommerce/app/page.tsx create mode 100644 workloads/ecommerce/app/product/[slug]/page.tsx create mode 100644 workloads/ecommerce/components/cart/cart-drawer.tsx create mode 100644 workloads/ecommerce/components/catalog/catalog-client.tsx create mode 100644 workloads/ecommerce/components/home/category-tiles.tsx create mode 100644 workloads/ecommerce/components/home/hero-bento.tsx create mode 100644 workloads/ecommerce/components/home/trust-bar.tsx create mode 100644 workloads/ecommerce/components/layout/footer.tsx create mode 100644 workloads/ecommerce/components/layout/navbar.tsx create mode 100644 workloads/ecommerce/components/product/mobile-add-to-cart.tsx create mode 100644 workloads/ecommerce/components/product/product-card.tsx create mode 100644 workloads/ecommerce/components/product/product-carousel.tsx create mode 100644 workloads/ecommerce/components/product/product-gallery.tsx create mode 100644 workloads/ecommerce/components/product/product-purchase.tsx create mode 100644 workloads/ecommerce/components/search/predictive-search.tsx create mode 100644 workloads/ecommerce/components/ui/accordion.tsx create mode 100644 workloads/ecommerce/components/ui/badge.tsx create mode 100644 workloads/ecommerce/components/ui/button.tsx create mode 100644 workloads/ecommerce/components/ui/dialog.tsx create mode 100644 workloads/ecommerce/components/ui/input.tsx create mode 100644 workloads/ecommerce/components/ui/progress.tsx create mode 100644 workloads/ecommerce/components/ui/sheet.tsx create mode 100644 workloads/ecommerce/data/products.json create mode 100644 workloads/ecommerce/lib/headless/client.ts create mode 100644 workloads/ecommerce/lib/headless/providers/medusa.ts create mode 100644 workloads/ecommerce/lib/headless/providers/mock.ts create mode 100644 workloads/ecommerce/lib/headless/providers/shopify.ts create mode 100644 workloads/ecommerce/lib/headless/types.ts create mode 100644 workloads/ecommerce/lib/utils.ts create mode 100644 workloads/ecommerce/next-env.d.ts create mode 100644 workloads/ecommerce/package.json create mode 100644 workloads/ecommerce/postcss.config.mjs create mode 100644 workloads/ecommerce/public/images/brand/favicon.png create mode 100644 workloads/ecommerce/public/images/brand/logo-ari.png create mode 100644 workloads/ecommerce/public/images/categories/figuras.webp create mode 100644 workloads/ecommerce/public/images/categories/marvel.webp create mode 100644 workloads/ecommerce/public/images/categories/mochilas.webp create mode 100644 workloads/ecommerce/public/images/categories/peluches.webp create mode 100644 workloads/ecommerce/public/images/collections/frozen.webp create mode 100644 workloads/ecommerce/public/images/collections/intensamente.webp create mode 100644 workloads/ecommerce/public/images/collections/lilo-stitch.webp create mode 100644 workloads/ecommerce/public/images/collections/marvel.webp create mode 100644 workloads/ecommerce/public/images/collections/mickey-minnie.webp create mode 100644 workloads/ecommerce/public/images/collections/toy-story.webp create mode 100644 workloads/ecommerce/public/images/hero/hero-real.webp create mode 100644 workloads/ecommerce/public/images/products/ari-001-toy-story-5-rex.webp create mode 100644 workloads/ecommerce/public/images/products/ari-002-toy-story-5-buzz-lightyear.webp create mode 100644 workloads/ecommerce/public/images/products/ari-003-toy-story-5-jessie.webp create mode 100644 workloads/ecommerce/public/images/products/ari-004-toy-story-5-woody.webp create mode 100644 workloads/ecommerce/public/images/products/ari-005-intensamente-furia.webp create mode 100644 workloads/ecommerce/public/images/products/ari-006-intensamente-tristeza.webp create mode 100644 workloads/ecommerce/public/images/products/ari-007-minnie-y-mickey-mouse.webp create mode 100644 workloads/ecommerce/public/images/products/ari-008-angel-lilo-y-stitch.webp create mode 100644 workloads/ecommerce/public/images/products/ari-009-mochilas-disney-coleccion-marvel.webp create mode 100644 workloads/ecommerce/public/images/products/ari-010-mochila-ligera-y-resistente-al-agua-de-d.webp create mode 100644 workloads/ecommerce/public/images/products/ari-011-mochila-escolar-de-spider-man-de-disney-.webp create mode 100644 workloads/ecommerce/public/images/products/ari-012-disney-mochila-de-poliester-ultraligera-.webp create mode 100644 workloads/ecommerce/public/images/products/ari-013-mochila-pequena-bob-esponja.webp create mode 100644 workloads/ecommerce/public/images/products/ari-014-mochila-casual-de-gran-capacidad-con-est.webp create mode 100644 workloads/ecommerce/public/images/products/ari-015-mochila-escolar-estilo-britanico-de-disn.webp create mode 100644 workloads/ecommerce/public/images/products/ari-016-disney-mochila-iron-man-de-poliester-ult.webp create mode 100644 workloads/ecommerce/public/images/products/ari-017-mochila-de-disney-marvel-capitan-america.webp create mode 100644 workloads/ecommerce/public/images/promo/hero-vibrant.webp create mode 100644 workloads/ecommerce/public/images/promo/promo-backpacks.webp create mode 100644 workloads/ecommerce/public/images/promo/promo-characters.webp create mode 100644 workloads/ecommerce/public/images/promo/promo-marvel.webp create mode 100644 workloads/ecommerce/scripts/validate-package.sh create mode 100644 workloads/ecommerce/store/cart-store.ts create mode 100644 workloads/ecommerce/tsconfig.json diff --git a/workloads/ecommerce/README.md b/workloads/ecommerce/README.md new file mode 100644 index 0000000..c895be2 --- /dev/null +++ b/workloads/ecommerce/README.md @@ -0,0 +1,59 @@ +# ARI Shopping Headless Storefront v4.0 + +Código base moderno para la tienda online ARI Shopping. + +## Stack + +- Next.js 15 con App Router y Server Components. +- React 19 y TypeScript estricto. +- Tailwind CSS 4. +- Componentes accesibles estilo shadcn/ui sobre Radix UI. +- Motion for React para microinteracciones. +- Zustand con persistencia local para el carrito. +- Adaptadores headless para catálogo local, Shopify Storefront API o Medusa. +- `next/image` para optimización de imágenes. +- Docker multi-stage con salida standalone. + +## Funcionalidad incluida + +- Header promocional cyan/amarillo/magenta. +- Búsqueda predictiva. +- Hero promocional Bento. +- Carrusel horizontal de novedades. +- Catálogo con búsqueda y filtros sin recarga. +- PDP dinámica con galería, zoom, variantes y recomendados. +- Botón móvil persistente para añadir al carrito. +- Carrito lateral con Zustand y persistencia. +- SEO y metadata con App Router. +- Endpoint `/api/health`. +- Arquitectura preparada para Shopify o Medusa. + +## Desarrollo + +```bash +cp .env.example .env.local +npm install +npm run dev +``` + +## Variables headless + +El valor predeterminado es: + +```text +HEADLESS_PROVIDER=mock +``` + +Proveedores preparados: + +```text +mock +shopify +medusa +``` + +Los adaptadores Shopify y Medusa incluyen la conexión inicial y un punto claro para completar el mapeo al modelo `Product`. + +## Compatibilidad GitOps + +El contenedor Next.js escucha en el puerto `80`, por lo que mantiene compatibilidad con el Service y Deployment existentes que apuntaban al frontend Nginx. diff --git a/workloads/ecommerce/TECHNOLOGY.md b/workloads/ecommerce/TECHNOLOGY.md new file mode 100644 index 0000000..8c4938c --- /dev/null +++ b/workloads/ecommerce/TECHNOLOGY.md @@ -0,0 +1,29 @@ +# Arquitectura tecnológica + +```text +Browser + ↓ +Next.js 15 App Router + ├─ Server Components / SSR / metadata SEO + ├─ React Client Components + │ ├─ Motion microinteractions + │ ├─ Radix/shadcn UI + │ └─ Zustand persisted cart + ├─ Headless catalog interface + │ ├─ Local mock JSON + │ ├─ Shopify Storefront GraphQL + │ └─ Medusa Store REST + └─ next/image optimization + ↓ +Docker standalone :80 + ↓ +Gitea Actions → Registry → Argo CD → Kubernetes +``` + +## Decisiones + +- Mobile-first y componentes interactivos aislados como Client Components. +- Datos y layout principal renderizados desde Server Components. +- Modelo de dominio independiente del proveedor e-commerce. +- Carrito local persistente; posteriormente puede sincronizarse con checkout remoto. +- Las imágenes reales de Ari Shopping viven en `public/images`. diff --git a/workloads/ecommerce/app/api/health/route.ts b/workloads/ecommerce/app/api/health/route.ts new file mode 100644 index 0000000..80ffa12 --- /dev/null +++ b/workloads/ecommerce/app/api/health/route.ts @@ -0,0 +1,3 @@ +export function GET() { + return Response.json({ status: "ok", service: "ari-shopping-frontend", version: "4.0.0" }, { status: 200 }); +} diff --git a/workloads/ecommerce/app/catalog/page.tsx b/workloads/ecommerce/app/catalog/page.tsx new file mode 100644 index 0000000..4b9c963 --- /dev/null +++ b/workloads/ecommerce/app/catalog/page.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import { CatalogClient } from "@/components/catalog/catalog-client"; +import { getProducts } from "@/lib/headless/client"; + +export const metadata: Metadata = { title: "Catálogo", description: "Explora todos los productos de Ari Shopping." }; + +type CatalogPageProps = { + searchParams: Promise>; +}; + +function single(value: string | string[] | undefined): string { + return Array.isArray(value) ? value[0] ?? "" : value ?? ""; +} + +export default async function CatalogPage({ searchParams }: CatalogPageProps) { + const params = await searchParams; + const products = await getProducts(); + return ( + + ); +} diff --git a/workloads/ecommerce/app/globals.css b/workloads/ecommerce/app/globals.css new file mode 100644 index 0000000..00d3b12 --- /dev/null +++ b/workloads/ecommerce/app/globals.css @@ -0,0 +1,20 @@ +@import "tailwindcss"; + +@theme { + --color-ari-cyan: #22d3ee; + --color-ari-magenta: #ec4899; + --color-ari-yellow: #fde047; + --color-ari-blue: #0e7490; + --animate-accordion-down: accordion-down .2s ease-out; + --animate-accordion-up: accordion-up .2s ease-out; +} + +@keyframes accordion-down { from { height: 0; } to { height: var(--radix-accordion-content-height); } } +@keyframes accordion-up { from { height: var(--radix-accordion-content-height); } to { height: 0; } } + +:root { color-scheme: light; } +* { border-color: rgb(226 232 240); } +html { scroll-behavior: smooth; } +body { margin: 0; background: #fff; color: #0f172a; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +button, a { -webkit-tap-highlight-color: transparent; } +::selection { background: #fde047; color: #0f172a; } diff --git a/workloads/ecommerce/app/layout.tsx b/workloads/ecommerce/app/layout.tsx new file mode 100644 index 0000000..717e9f3 --- /dev/null +++ b/workloads/ecommerce/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata, Viewport } from "next"; +import "./globals.css"; +import { CartDrawer } from "@/components/cart/cart-drawer"; +import { Footer } from "@/components/layout/footer"; +import { Navbar } from "@/components/layout/navbar"; +import { getProducts } from "@/lib/headless/client"; + +export const metadata: Metadata = { + metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? "https://shop.cruzcloud.net"), + title: { default: "ARI Shopping | Juguetes, regalos y variedades", template: "%s | ARI Shopping" }, + description: "Tienda online de juguetes, personajes, mochilas y regalos con atención personalizada.", + openGraph: { title: "ARI Shopping", description: "Tesoros para cada aventura.", type: "website", locale: "es_CO" }, +}; + +export const viewport: Viewport = { width: "device-width", initialScale: 1, themeColor: "#22d3ee" }; + +export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + const products = await getProducts(); + return
{children}