diff --git a/.github/workflows/storefront-checks.yml b/.github/workflows/storefront-checks.yml new file mode 100644 index 0000000..5399b68 --- /dev/null +++ b/.github/workflows/storefront-checks.yml @@ -0,0 +1,36 @@ +name: Storefront checks + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: storefront-checks-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Lint, test and build + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.22.0 + cache: npm + - name: Install locked dependencies + run: npm ci + - name: Lint + run: npm run lint + - name: Test commerce rules and checkout + run: npm run test:run + - name: Build production storefront + run: npm run build diff --git a/README.md b/README.md index da55586..c1d8f9d 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,28 @@ -# My Shop +# FORMA · Next.js Storefront [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Storefront checks](https://github.com/SakuraUltra/my-shop/actions/workflows/storefront-checks.yml/badge.svg?branch=main)](https://github.com/SakuraUltra/my-shop/actions/workflows/storefront-checks.yml) -**A minimal Next.js storefront with a complete, no-keys-required demo shopping journey.** +**A boutique ecommerce storefront with a complete, no-keys-required demo shopping journey.** -Built as a portfolio project and a reusable storefront starter: browse a shared catalog, choose variants, save a cart, place a simulated order and explore its delivery timeline. +FORMA is the sample brand inside **my-shop**, a portfolio project and reusable Next.js, React and TypeScript storefront template. Warm ivory, deep olive and an editorial layout give everyday clothing and lifestyle pieces room to breathe. Browse a shared catalog, choose variants, save a cart, place a simulated order and explore its delivery timeline. -[中文说明](docs/README.zh-CN.md) · [Architecture & extension guide](docs/architecture.md) · [Deployment](docs/deployment.md) +[中文说明](docs/README.zh-CN.md) · [Project showcase](docs/showcase.md) · [Architecture & extension guide](docs/architecture.md) · [Deployment](docs/deployment.md) > This is a **demo storefront**. It does not accept payments, register accounts, send emails or ship products. Cart and order data live in the visitor's browser. Use the fictional address provided at checkout. +## Preview + +![FORMA desktop storefront: warm ivory, olive accents and editorial product photography](docs/media/desktop-home.jpg) + +| Mobile storefront | Saved demo order | +| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| FORMA mobile storefront | A saved FORMA demo order with item and amount details | + +[Watch the short storefront walkthrough](docs/media/storefront-walkthrough.mp4) · [Read the project story and demo script](docs/showcase.md) + +The walkthrough is assembled from captured screens of the working interface; it is not a real-time screen recording. + ## Quick start Use Node.js **22.22+** (Node 22 LTS recommended). @@ -25,7 +38,7 @@ Open [localhost:3000](http://localhost:3000). No `.env`, database, seed command, ## Try the complete journey -1. Open **Products**, filter by category/colour/price, or search with **⌘K / Ctrl+K**. +1. Open **The collection**, filter by category/colour/price, or search with **⌘K / Ctrl+K**. 2. Choose a product colour and size. Unavailable variants cannot be added. 3. Open the cart, adjust quantities and continue to checkout. 4. Keep the example shipping address and select **Place demo order**. There is no card form. @@ -35,13 +48,14 @@ Open [localhost:3000](http://localhost:3000). No `.env`, database, seed command, ## Features - **One catalog, everywhere:** home, search, filtered listings, sale items, all 12 product pages and sitemap share one data source. -- **Shareable discovery:** category, colour, price and sorting are reflected in the URL; “New arrivals” works on direct visits. +- **Shareable discovery:** category, colour, price and sorting are reflected in the URL; direct links such as `/products?sort=newest` preserve the **Newest** sort order. - **Persistent cart:** variant quantities survive reloads; integer validation, unavailable-product handling and per-variant stock limits. - **Guest checkout:** catalog-based price recalculation, consistent shipping thresholds, empty-cart protection and useful storage-error feedback. - **Saved demo orders:** unique IDs, item and price snapshots, order history, missing-order states and interactive simulated delivery. -- **Responsive interface:** mobile navigation and filters, image gallery, dark/light themes, labelled controls and a skip link. +- **Boutique visual identity:** an editorial home page, warm neutral surfaces, olive accents and a responsive layout with dark/light themes. +- **Accessible interactions:** labelled controls, keyboard search, a skip link, mobile navigation and filters. - **Configurable branding:** store name, description, currency, shipping and canonical URL in one file. -- **Validation:** domain and component tests plus lint, TypeScript and production build checks, with a ready-to-enable CI workflow. +- **Validation:** domain and component tests plus lint, TypeScript and production build checks; GitHub Actions configuration lives alongside the code. ## Make it your own @@ -65,7 +79,7 @@ npm run build npm run start ``` -`npm run test` starts watch mode. `npm run typecheck` performs a separate type check (the production build also checks types). A ready-to-enable GitHub Actions workflow is provided in `docs/storefront-checks.yml`. To activate it, copy it to `.github/workflows/ci.yml` using a GitHub connection with workflow-write permission. It will then check pull requests and pushes to `main`. +`npm run test` starts watch mode. `npm run typecheck` performs a separate type check (the production build also checks types). The [Storefront checks workflow](.github/workflows/storefront-checks.yml) installs locked dependencies, lints, tests and builds on pull requests and pushes to `main`. It can also be run manually from GitHub Actions. It uses a read-only repository token and does not deploy the site. Check the [Actions page](https://github.com/SakuraUltra/my-shop/actions/workflows/storefront-checks.yml) for the status of a particular commit. Tests cover stock and quantity limits, duplicate and stale cart data, price recalculation, shipping boundaries, unique order IDs, persistence, malformed saved orders, failed storage, missing orders and the guest checkout UI. diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 17f76b5..6f51803 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -1,9 +1,23 @@ -# My Shop · 可完整体验的电商作品集 +# FORMA · 可完整体验的电商作品集 -这是一个用 Next.js、React 和 TypeScript 构建的简洁商店,也是可继续扩展的店铺模板。重点是让访客完整体验“浏览 → 选规格 → 购物车 → 模拟下单 → 查看订单 → 模拟配送”。 +FORMA 是 **my-shop** 开源模板中的示例品牌,用 Next.js、React 和 TypeScript 构建。暖白、深橄榄色、商品大图与留白构成自然简约的精品店视觉。访客可以完整体验“浏览 → 选规格 → 购物车 → 模拟下单 → 查看订单 → 模拟配送”。 **这是演示项目:不收款、不发货、不发送邮件,也不需要注册。** 订单只保存在当前浏览器,请使用结算页提供的虚构地址。 +[English](../README.md) · [作品介绍与演示脚本](showcase.md) · [架构与扩展说明](architecture.md) + +## 效果预览 + +![FORMA 桌面首页:暖白底色、橄榄色点缀与商品大图](media/desktop-home.jpg) + +| 手机首页 | 模拟订单 | +| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| FORMA 手机首页 | FORMA 模拟订单的商品及金额详情 | + +[观看简短演示](media/storefront-walkthrough.mp4) + +演示视频由实际界面截图串联制作,用于展示购物流程,并非实时录屏。从顶部的 **The collection** 进入商品列表即可亲自体验。 + ## 本地启动 使用 Node.js 22.22 或更新版本(推荐 Node 22 LTS): @@ -17,7 +31,7 @@ npm run dev 打开 http://localhost:3000 。无需配置数据库、支付密钥或 `.env`。安装依赖、构建时加载 Google 字体,以及显示 Unsplash 图片需要联网。 -## 本次完善的内容 +## 功能与设计 - 首页、搜索、列表、促销和详情统一使用 12 件示例商品,价格与链接保持一致。 - 分类、颜色、价格和排序保存在网址中,直接打开“新品”链接也会正确排序。 @@ -26,7 +40,8 @@ npm run dev - 订单拥有独立编号和商品快照,可刷新查看;未知订单不会显示虚构的固定记录。 - 配送页面可以手动推进演示状态,不会冒充真实物流查询。 - 手机布局、主题切换、键盘搜索、空状态和错误提示。 -- 自动测试与待启用的 GitHub Actions 配置,帮助后续迭代。 +- 自然简约的 FORMA 品牌视觉,以及桌面、手机和订单展示素材。 +- 自动测试与 GitHub Actions 配置,帮助后续迭代。 ## 自定义入口 @@ -51,6 +66,8 @@ npm run start 参考[部署指南](deployment.md)和[架构与扩展说明](architecture.md)。数据库设计保留在 `prisma/schema.prisma` 供后续参考,目前应用不会连接数据库。 -下一阶段可以完善独立品牌视觉、产品内容编辑和自动化浏览器测试;真实登录、支付和共享库存应作为有明确后端边界的新阶段。 +下一阶段可以完善产品内容编辑和自动化浏览器测试;真实登录、支付和共享库存应作为有明确后端边界的新阶段。 + +自动检查配置位于 [`.github/workflows/storefront-checks.yml`](../.github/workflows/storefront-checks.yml),覆盖拉取请求和 `main` 分支更新,也支持手动运行。流程安装锁定依赖,然后执行代码检查、测试和生产构建;仓库令牌仅有读取权限。它不会自动部署网站,具体提交是否通过请以 [GitHub Actions 记录](https://github.com/SakuraUltra/my-shop/actions/workflows/storefront-checks.yml)为准。 -自动检查配置位于 `docs/storefront-checks.yml`。当前 GitHub 连接没有 `workflow` 权限,尚未启用;之后用具备该权限的连接将它放到 `.github/workflows/ci.yml` 即可。 +应用代码采用 [MIT 许可证](../LICENSE),方便保留署名后复用。示例照片来源与使用说明见英文 README。 diff --git a/docs/deployment.md b/docs/deployment.md index 922680c..d8ef597 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -29,7 +29,7 @@ This app also runs with `npm run build` followed by `npm run start`. Set the env ## Verify the deployed site - Home, product listing, all product details and sale pages load. -- `Products → choose variant → cart → checkout → Place demo order` produces the selected items and calculated total. +- `The collection → choose variant → cart → Checkout → Place demo order` produces the selected items and calculated total. - Refreshing the order URL restores the order in the same browser. - An order URL opened in a different browser shows the missing-order message. - A cart below the shipping threshold has the configured demo shipping charge; one at or above it ships free. diff --git a/docs/media/demo-order.jpg b/docs/media/demo-order.jpg new file mode 100644 index 0000000..0abdc05 Binary files /dev/null and b/docs/media/demo-order.jpg differ diff --git a/docs/media/desktop-home.jpg b/docs/media/desktop-home.jpg new file mode 100644 index 0000000..8109566 Binary files /dev/null and b/docs/media/desktop-home.jpg differ diff --git a/docs/media/mobile-home.jpg b/docs/media/mobile-home.jpg new file mode 100644 index 0000000..c805274 Binary files /dev/null and b/docs/media/mobile-home.jpg differ diff --git a/docs/media/storefront-walkthrough.mp4 b/docs/media/storefront-walkthrough.mp4 new file mode 100644 index 0000000..c0b27cf Binary files /dev/null and b/docs/media/storefront-walkthrough.mp4 differ diff --git a/docs/showcase.md b/docs/showcase.md new file mode 100644 index 0000000..322ba48 --- /dev/null +++ b/docs/showcase.md @@ -0,0 +1,71 @@ +# FORMA storefront · Project showcase + +[Repository](https://github.com/SakuraUltra/my-shop) · [Desktop preview](media/desktop-home.jpg) · [Mobile preview](media/mobile-home.jpg) · [Walkthrough](media/storefront-walkthrough.mp4) + +## Project summary + +**FORMA is a responsive ecommerce storefront and reusable Next.js template for a fictional everyday clothing and lifestyle brand.** Its warm ivory and deep olive visual identity sits around a complete guest shopping journey: discovery, variant selection, a persistent cart, simulated checkout, saved orders and an interactive delivery timeline. + +The project focuses on making the experience consistent. A shared catalog supplies every shopping surface; catalog prices and variant availability are checked again at checkout. Orders preserve their own item and amount snapshots, so later catalog changes do not rewrite saved history. Failed browser storage leaves the cart intact and gives the visitor a recoverable error. + +The default installation needs no database, payment keys or user account. That makes it easy to run as a portfolio demo or adapt as a storefront starter. Production authentication, payment processing, shared inventory and fulfillment are future server integrations, not features of this demo. + +## Problem → implementation + +| Problem | Implementation | +| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| A generic shop layout does not communicate a clear brand | A restrained FORMA identity, editorial home page, warm neutral palette and responsive product presentation | +| Separate product data makes search, listings and details disagree | One typed catalog across home, search, filters, sale pages and product details | +| A cart can contain stale prices, invalid quantities or unavailable variants | Restore-time repair, per-variant limits and catalog-based checkout validation | +| A checkout demo can end at a disconnected confirmation screen | Unique local order snapshots, history and a manually advanced delivery simulation | +| Browser persistence can fail or contain malformed data | Validated reads, explicit error states and saving the order before clearing the cart | +| A reusable template needs a clear starting point | No-key setup, centralized store configuration, MIT licensing and bilingual documentation | + +## Suggested CV entry + +**FORMA — Next.js ecommerce storefront** · React, TypeScript, Tailwind CSS, Zustand, Vitest + +- Built a responsive storefront spanning product discovery, variant selection, a persistent cart and guest demo checkout, with a shared typed catalog across the shopping journey. +- Implemented catalog-based price and quantity validation, persistent order snapshots, storage-failure handling and an interactive simulated delivery timeline. +- Created a reusable MIT-licensed template with centralized branding, bilingual documentation and a GitHub Actions workflow for lint, tests and production builds. + +Describe this as a **frontend commerce demo**. Do not describe it as a live payment platform or claim customer, revenue, conversion or performance results without measurements. + +## 演示与作品集介绍 + +**FORMA 是一个以日常服饰和生活用品为主题的电商前端作品,同时也是可复用的 Next.js 开源模板。** 视觉采用暖白与深橄榄色,通过商品大图、排版和留白呈现自然简约的精品店风格。 + +项目打通了浏览、搜索筛选、选择规格、购物车、模拟结算、保存订单与模拟配送的完整体验。核心设计是让整个购物过程使用同一份商品数据,并在恢复购物车和结算时重新验证价格与数量。订单以独立快照保存在浏览器中,保存失败时不会清空购物车。 + +无需数据库、支付密钥或注册账号即可运行,适合用于作品展示和继续开发。真实支付、登录权限、共享库存和物流不在当前演示范围内。 + +### 中文简历条目 + +**FORMA 电商前端与开源模板** · Next.js / React / TypeScript / Tailwind CSS / Zustand / Vitest + +- 构建响应式电商体验,覆盖商品搜索与筛选、规格选择、持久化购物车、游客模拟结算及订单查看,并统一商品数据来源。 +- 实现商品目录驱动的价格与数量校验、订单快照、浏览器存储错误处理和可交互的模拟配送流程。 +- 完善品牌视觉、集中配置、中英文文档与 MIT 开源许可,并配置 GitHub Actions 自动执行代码检查、测试和生产构建。 + +## One-minute demo script / 一分钟演示脚本 + +| Time | Show | Explain | +| ------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| 0–10 s | Desktop home page, then mobile layout | FORMA's visual identity and responsive shopping experience / 品牌视觉与手机布局 | +| 10–20 s | The collection, a category filter and one product | The shared catalog keeps discovery and product detail consistent / 商品数据统一,列表和详情一致 | +| 20–30 s | Select an available variant and open the cart | Quantities persist across reloads and respect sample stock / 购物车刷新恢复与规格数量限制 | +| 30–45 s | Checkout using the provided fictional address | Simulated checkout recalculates prices; no payment details / 重新核算金额,无需输入付款信息 | +| 45–60 s | Saved order and delivery simulation | Order snapshots persist locally; delivery advances only on interaction / 订单保存在当前浏览器,配送由用户手动演示 | + +Keep the demo label visible and use only fictional customer details. The bundled walkthrough is assembled from captured screens of the working interface, not a real-time screen recording. The table above is a script for a live presentation, not a performance benchmark. + +仓库中的演示视频由实际界面截图串联制作,并非实时录屏。上表可用于现场演示,时间安排不代表页面加载性能。 + +## Review the engineering + +- [Architecture and data lifecycle](architecture.md) +- [Tests and local validation](../README.md#development) +- [GitHub Actions runs](https://github.com/SakuraUltra/my-shop/actions/workflows/storefront-checks.yml) +- [Deployment instructions and current hosting boundary](deployment.md) + +See [`src/lib/store-config.ts`](../src/lib/store-config.ts) to change the sample brand, currency and shipping configuration. The repository remains named `my-shop`; FORMA is its example storefront identity. diff --git a/docs/storefront-checks.yml b/docs/storefront-checks.yml deleted file mode 100644 index 31c5c26..0000000 --- a/docs/storefront-checks.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Storefront checks -on: - push: - branches: [main] - pull_request: -permissions: - contents: read -jobs: - validate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - run: npm ci - - run: npm run lint - - run: npm run test:run - - run: npm run build diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 925d14d..464d5e8 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -4,19 +4,19 @@ import { storeConfig } from "@/lib/store-config"; export const metadata = { title: "About the demo" }; export default function AboutPage() { return ( -
-

+

+

An interactive storefront

-

+

Small details. A complete journey.

-

+

{storeConfig.name} is a portfolio project and a starting point for building your own storefront. Explore the collection, choose a variant, fill a cart and follow a demo order through delivery.

-
+
{[ `${products.length} sample products`, "No account needed", @@ -24,30 +24,30 @@ export default function AboutPage() { ].map((text) => (
{text}
))}
-

How to try it

-
    +

    How to try it

    +
    1. Pick a product, colour and size.
    2. Add it to your cart and proceed to checkout.
    3. Keep the example address and place a demo order.
    4. Open demo delivery to advance the timeline.
    -

    +

    Your data stays in this browser

    -

    +

    Cart contents, demo orders and theme preferences are saved in browser storage. Use the fictional address provided; please do not enter real personal information. Orders do not sync across devices. You can clear demo order history from the orders page and remove cart items using the cart controls.

    -

    +

    Product photographs load from Unsplash through the image service. Hosting providers may receive normal web request information. This application does not include analytics, account registration, payment @@ -55,13 +55,13 @@ export default function AboutPage() {

    Explore the collection + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c3b48ff..07972d0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -48,7 +48,7 @@ export default function RootLayout({ Skip to content diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx new file mode 100644 index 0000000..af31bd9 --- /dev/null +++ b/src/app/opengraph-image.tsx @@ -0,0 +1,74 @@ +import { ImageResponse } from "next/og"; +import { storeConfig } from "@/lib/store-config"; + +export const alt = `${storeConfig.name} — everyday pieces, thoughtfully chosen. An open-source storefront demo.`; +export const size = { width: 1200, height: 630 }; +export const contentType = "image/png"; + +export default function OpenGraphImage() { + return new ImageResponse( +
    +
    + EVERYDAY, CONSIDERED. + STOREFRONT DEMO +
    +
    +
    + + {storeConfig.name} + + + Everyday pieces. A complete shopping journey. + +
    +
    +
    +
    + Browse · Choose · Demo checkout · Track + Next.js / TypeScript / MIT +
    +
    , + size, + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index da6a756..6d1fb2c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,27 +1,28 @@ +import CollectionStory from "@/components/home/CollectionStory"; import FeaturedCollection from "@/components/home/FeaturedCollection"; import HeroBanner from "@/components/home/HeroBanner"; import OnSale from "@/components/home/OnSale"; import TrustSignals from "@/components/home/TrustSignals"; -import FadeIn from "@/components/ui/FadeIn"; import { products, saleProducts } from "@/lib/catalog"; export default function Home() { - const featuredProducts = products.slice(0, 4); + const featuredSlugs = [ + "classic-cotton-tee", + "linen-button-down", + "canvas-tote-bag", + "minimalist-watch", + ]; + const featuredProducts = featuredSlugs.flatMap((slug) => + products.filter((product) => product.slug === slug), + ); return (
    - - - - - - - - - - - - + + + + +
    ); } diff --git a/src/components/cart/CartSheet.tsx b/src/components/cart/CartSheet.tsx index c5da7c6..ee5733e 100644 --- a/src/components/cart/CartSheet.tsx +++ b/src/components/cart/CartSheet.tsx @@ -42,7 +42,7 @@ export default function CartSheet() { return ( @@ -51,26 +51,29 @@ export default function CartSheet() { key={count} initial={{ scale: 1.5 }} animate={{ scale: 1 }} - className="absolute -right-1.5 -top-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-black text-[10px] font-medium text-white" + className="absolute -right-1.5 -top-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground" > {count} )} - - Your cart + + + Your cart + {/* Cart items */} {mounted && items.length === 0 ? (
    -

    - Your cart is empty -

    +

    Your cart is empty

    setOpen(false)} - className="text-sm font-medium text-black underline underline-offset-4 hover:text-neutral-600 dark:text-white dark:hover:text-neutral-300" + className="text-sm font-medium text-primary underline underline-offset-4 hover:text-foreground" > Continue shopping @@ -78,16 +81,17 @@ export default function CartSheet() { ) : ( <>
    -
      +
        {items.map((item) => ( -
      • +
      • {/* Thumbnail */} -
        +
        {item.image ? ( {item.productName} ) : ( @@ -96,13 +100,13 @@ export default function CartSheet() {
        {/* Info */} -
        -
        +
        +

        {item.productName}

        -

        +

        {item.color} / {item.size}

        @@ -111,8 +115,8 @@ export default function CartSheet() { removeItem(item.variantId); toast("Item removed"); }} - className="cursor-pointer text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200" - aria-label="Remove item" + className="cursor-pointer rounded-sm p-1 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring" + aria-label={`Remove ${item.productName}`} > @@ -124,12 +128,12 @@ export default function CartSheet() { {/* Quantity */} -
        +
        {/* Footer */} -
        +
        - - Subtotal - + Subtotal {formatMoney(totalPrice())} @@ -171,10 +173,13 @@ export default function CartSheet() { setOpen(false)} - className="mt-4 block w-full rounded-lg bg-black py-3 text-center text-sm font-medium text-white hover:bg-neutral-800 dark:bg-white dark:text-black dark:hover:bg-neutral-200" + className="mt-5 block w-full rounded-md bg-primary py-3.5 text-center text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring" > Checkout +

        + Demo checkout. No payment is taken. +

        )} diff --git a/src/components/checkout/CheckoutContent.tsx b/src/components/checkout/CheckoutContent.tsx index 258dcc3..6d8a1e4 100644 --- a/src/components/checkout/CheckoutContent.tsx +++ b/src/components/checkout/CheckoutContent.tsx @@ -1,5 +1,6 @@ "use client"; import Link from "next/link"; +import Image from "next/image"; import { useRouter } from "next/navigation"; import { useRef, useState } from "react"; import { useCartStore } from "@/store/cart"; @@ -42,30 +43,37 @@ export default function CheckoutContent() { ); if (!items.length && !submitting) return ( -
        -

        Your cart is empty

        +
        +

        + Your cart is empty +

        Find something you like before checking out.

        - + Browse products
        ); return ( -
        +
        ← Continue shopping -

        Demo checkout

        -

        +

        + Demo checkout +

        +

        Try the full experience. No payment, emails or shipments.

        { event.preventDefault(); if (submitted.current) return; @@ -89,12 +97,12 @@ export default function CheckoutContent() { }} >
        -
        -

        Shipping details

        +
        +

        Shipping details

        @@ -115,14 +123,15 @@ export default function CheckoutContent() { onChange={(event) => setAddress({ ...address, [field]: event.target.value }) } - className="mt-2 block w-full rounded-lg border bg-background px-3 py-2.5" + className="mt-2 block w-full rounded-md border border-border bg-background px-3 py-3 text-sm outline-none transition-shadow focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring/30" /> ))}
        -
        +

        - Simulated payment + Simulated + payment

        No card details needed. Placing a demo order will never charge @@ -130,21 +139,29 @@ export default function CheckoutContent() {

        -
        -

        Order summary

        -
          +
          +

          Order summary

          +
            {items.map((item) => ( -
          • -
            -

            {item.productName}

            +
          • + {item.image && ( +
            + {item.productName} +
            + )} +
            +

            {item.productName}

            {item.color} / {item.size} · Qty {item.quantity}

            - + {formatMoney(item.price * item.quantity)}
          • @@ -159,7 +176,7 @@ export default function CheckoutContent() {
            Demo shipping
            {totals.shipping ? formatMoney(totals.shipping) : "Free"}
        -
        +
        Demo total
        {formatMoney(totals.total)}
        @@ -178,7 +195,7 @@ export default function CheckoutContent() { )}
      {/* Right: Icons */} -
      +
      @@ -87,29 +87,34 @@ export default function Navbar() { - + {/* Mobile: Hamburger menu */}
      - + - - Menu -
    ) : ( /* No results */ -

    +

    No products found for “{query}”

    )} diff --git a/src/components/layout/ThemeToggle.tsx b/src/components/layout/ThemeToggle.tsx index 318fbed..cec7dde 100644 --- a/src/components/layout/ThemeToggle.tsx +++ b/src/components/layout/ThemeToggle.tsx @@ -8,19 +8,19 @@ export default function ThemeToggle() { const { resolvedTheme: theme, setTheme } = useTheme(); const mounted = useClientReady(); - if (!mounted) return
    ; + if (!mounted) return
    ; return ( ); diff --git a/src/components/orders/OrdersContent.tsx b/src/components/orders/OrdersContent.tsx index e14feb5..b3b1ea9 100644 --- a/src/components/orders/OrdersContent.tsx +++ b/src/components/orders/OrdersContent.tsx @@ -34,17 +34,17 @@ export default function OrdersContent({ ); const order = orders.find((saved) => saved.id === id); return ( -
    +
    ← {id ? "All demo orders" : "Continue shopping"} -

    +

    {id ? (tracking ? "Demo delivery" : "Demo order") : "Your demo orders"}

    -

    +

    Saved on this browser. No real purchase or delivery takes place.

    {error && ( @@ -54,8 +54,8 @@ export default function OrdersContent({ )} {id ? ( !order ? ( -
    -

    +
    +

    Order not found in this browser

    @@ -68,9 +68,9 @@ export default function OrdersContent({

    ) : ( <> -
    +
    - + {orderStages[order.stage]} · Demo
    {tracking ? ( -
    -

    +
    +

    Explore the delivery timeline

    @@ -100,13 +100,15 @@ export default function OrdersContent({ className={`flex items-center gap-3 ${index > order.stage ? "text-muted-foreground" : ""}`} > {index <= order.stage ? ( - + ) : ( )} {stage} {index === order.stage && ( - Current demo stage + + Current demo stage + )} ))} @@ -114,14 +116,14 @@ export default function OrdersContent({ Order details @@ -129,7 +131,7 @@ export default function OrdersContent({

    ) : ( <> -
      +
        {order.items.map((item) => (

    -
    +
    Demo total · No charge
    {formatMoney(order.total, order.currency)}
    Explore demo delivery @@ -193,7 +195,7 @@ export default function OrdersContent({
  1. @@ -250,12 +252,12 @@ export default function OrdersContent({ ) : (
    -

    No orders yet

    +

    No orders yet

    Place a demo order to see your items and try the delivery timeline.

    Browse products diff --git a/src/components/product/ImageGallery.tsx b/src/components/product/ImageGallery.tsx index 9dc4590..a80eadb 100644 --- a/src/components/product/ImageGallery.tsx +++ b/src/components/product/ImageGallery.tsx @@ -48,7 +48,7 @@ export default function ImageGallery({ if (images.length === 0) { return ( -
    +
    ); } @@ -56,17 +56,18 @@ export default function ImageGallery({ <> {/* ── Mobile carousel ──────────────────────────────── */}
    -
    +
    {images.map((img, idx) => (
    {`${name} @@ -85,8 +86,8 @@ export default function ImageGallery({ onClick={() => scrollTo(idx)} className={`h-2 w-2 rounded-full transition-colors ${ idx === selectedIndex - ? "bg-black dark:bg-white" - : "bg-neutral-300 dark:bg-neutral-600" + ? "bg-primary" + : "bg-muted-foreground/30" }`} /> ))} @@ -98,12 +99,14 @@ export default function ImageGallery({
    @@ -136,7 +142,7 @@ export default function ImageGallery({ {/* ── Fullscreen lightbox dialog ───────────────────── */} - + {name}
    @@ -144,6 +150,7 @@ export default function ImageGallery({ src={images[selectedIndex]} alt={name} fill + sizes="(max-width: 1024px) 90vw, 960px" className="object-contain" /> @@ -152,7 +159,7 @@ export default function ImageGallery({ @@ -163,7 +170,7 @@ export default function ImageGallery({ @@ -177,9 +184,11 @@ export default function ImageGallery({ diff --git a/src/components/product/OnSaleContent.tsx b/src/components/product/OnSaleContent.tsx index d6f0380..21aacba 100644 --- a/src/components/product/OnSaleContent.tsx +++ b/src/components/product/OnSaleContent.tsx @@ -28,37 +28,37 @@ export default function OnSaleContent() { }, [sort]); return ( -
    +
    {/* Sale banner */} -
    -

    +

    +

    Selected favourites, reduced prices

    {/* Header */} -
    +
    -

    On Sale

    -

    - Limited time offers on selected items +

    + On Sale +

    +

    + A few good finds, at a little less.

    - + {sorted.length} products
    {/* Sort */} -
    - - Sort by - +
    + Sort by v && set("sort", v)} > - + { sortOptions.find((option) => option.value === filters.sort) @@ -204,13 +215,13 @@ export default function ProductFilters({ filters, onChange }: Props) { {activeFilters.map((f) => ( {f.label} @@ -218,7 +229,7 @@ export default function ProductFilters({ filters, onChange }: Props) { ))} @@ -242,16 +253,16 @@ function FilterDropdown({ }) { return ( - + {label} - + {options.map((opt) => ( onSelect(opt)} - className={value === opt ? "font-medium" : ""} + className={value === opt ? "font-medium text-primary" : ""} > {opt} @@ -281,10 +292,9 @@ function MobileFilterSection({ diff --git a/src/lib/store-config.ts b/src/lib/store-config.ts index 3ab1f53..5fd446e 100644 --- a/src/lib/store-config.ts +++ b/src/lib/store-config.ts @@ -1,8 +1,8 @@ // Edit this file to give the template your own identity. Prices use minor units (cents). export const storeConfig = { - name: "MY SHOP", + name: "FORMA", description: - "A minimal fashion storefront. Browse, build a cart and try a complete demo checkout.", + "Everyday pieces, thoughtfully brought together. Explore FORMA, a boutique storefront demo with a complete guest shopping journey.", currency: "USD", locale: "en-US", shipping: { freeFrom: 4900, standard: 499 },