diff --git a/README.md b/README.md index e40afdbb..b1100cb8 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,12 @@ Here are some useful links for getting started with Wix Headless and the availab Wix Rentals + + Benny Pets + A designed storefront with variant selection, cart, and checkout using Astro and Wix Headless. + Live demo + Wix Stores, Wix eCommerce, Wix Forms + Next.js diff --git a/astro/benny-pets/.gitignore b/astro/benny-pets/.gitignore new file mode 100644 index 00000000..dd0f070e --- /dev/null +++ b/astro/benny-pets/.gitignore @@ -0,0 +1,28 @@ +# wix integration +.wix/ + +# build output +dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.production +.env.local + +# macOS-specific files +.DS_Store + +# jetbrains setting folder +.idea/ diff --git a/astro/benny-pets/.vscode/extensions.json b/astro/benny-pets/.vscode/extensions.json new file mode 100644 index 00000000..22a15055 --- /dev/null +++ b/astro/benny-pets/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/astro/benny-pets/.vscode/launch.json b/astro/benny-pets/.vscode/launch.json new file mode 100644 index 00000000..d6422097 --- /dev/null +++ b/astro/benny-pets/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/astro/benny-pets/.vscode/settings.json b/astro/benny-pets/.vscode/settings.json new file mode 100644 index 00000000..54ecf2af --- /dev/null +++ b/astro/benny-pets/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.quickSuggestions": { + "strings": "on" + } +} diff --git a/astro/benny-pets/README.md b/astro/benny-pets/README.md new file mode 100644 index 00000000..472e0a67 --- /dev/null +++ b/astro/benny-pets/README.md @@ -0,0 +1,53 @@ +# Benny Pets — Wix Stores storefront + +A designed storefront on top of [Wix Stores](https://dev.wix.com/docs/sdk/backend-modules/stores/introduction) +and [Wix eCommerce](https://dev.wix.com/docs/sdk/backend-modules/ecom/introduction): a catalog, +a product page with variant selection, a cart, and checkout — plus a contact +form rendered from [Wix Forms](https://dev.wix.com/docs/sdk/backend-modules/forms/introduction). + +Everything on screen comes from Wix at request time. Every route is +server-rendered, and the page ships no framework runtime — the animation and +cart interactions are plain modules, so there are no islands to hydrate. + +## Wix apps used + +| App | What it powers | +|---|---| +| Wix Stores | The catalog: products, prices, images, options and variants | +| Wix eCommerce | The visitor's cart and the checkout session | +| Wix Forms | The footer contact form — fields come from the form definition | + +## Routes + +| Route | What it does | +|---|---| +| `/` | Home — best sellers, a catalog-driven gallery, and the contact form | +| `/store` | The full catalog | +| `/store/[slug]` | Product detail with option selection; unknown slugs 404 | +| `/cart` | The visitor's cart, with quantity, removal, and checkout | +| `POST /api/cart/add` | `{ productId, variantId?, quantity }` — refuses to guess a variant | +| `POST /api/cart/update` | `{ lineItemId, quantity }` | +| `POST /api/cart/remove` | `{ lineItemId }` | +| `POST /api/checkout` | Creates a checkout and returns its redirect-session URL | +| `POST /api/contact` | Submits the contact form, keyed by each field's Wix target | + +## Getting started + +```bash +npm install +npm run dev +``` + +The `@wix/astro` integration authenticates every SDK call from the visitor's +session, so there is no client to construct and no client id in the source. + +## Notes + +- **Prices** are the `formattedAmount` Wix returns, so the store's own currency + is always what shows. +- **Variants**: a product with options can't be added until one is chosen, and + the price updates to the chosen variant. Single-variant products get a + one-click add. +- **The contact form's id** lives in `src/lib/constants.ts`. It matches the form + this template's site provisions; point it at your own form if you connect + this code to a site you built yourself. diff --git a/astro/benny-pets/astro.config.mjs b/astro/benny-pets/astro.config.mjs new file mode 100644 index 00000000..b608ae8f --- /dev/null +++ b/astro/benny-pets/astro.config.mjs @@ -0,0 +1,40 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import wix from "@wix/astro"; +import wixPages from "@wix/astro-pages"; +import wixHostingAdapter from "@wix/astro-wix-hosting-adapter"; +import { transform } from "esbuild"; + +// Astro ships the SSR build unminified and `vite.build.minify` only reaches the client build. The +// deploy transport picks its path by server-bundle size, and past ~2.25MiB of server code it +// switches to a route that cannot authorize — so this is what keeps the template provisionable. +const minifyServerBundle = () => { + let isSsr = false; + return { + name: "benny-pets:minify-server-bundle", + apply: "build", + enforce: "post", + configResolved(config) { + isSsr = Boolean(config.build.ssr); + }, + async renderChunk(code) { + if (!isSsr) return null; + const { code: minified } = await transform(code, { + minify: true, format: "esm", target: "node20", keepNames: true, legalComments: "none", + }); + return { code: minified, map: null }; + }, + }; +}; + +// https://astro.build/config +export default defineConfig({ + // Every route renders per request: the catalog, the cart and the contact + // form are all read from Wix at request time. + output: "server", + adapter: wixHostingAdapter(), + integrations: [wix(), wixPages()], + security: { checkOrigin: false }, + + vite: { plugins: [minifyServerBundle()] }, +}); diff --git a/astro/benny-pets/package.json b/astro/benny-pets/package.json new file mode 100644 index 00000000..ed22e0c5 --- /dev/null +++ b/astro/benny-pets/package.json @@ -0,0 +1,29 @@ +{ + "name": "wix-astro-benny-pets", + "type": "module", + "version": "0.0.1", + "scripts": { + "astro": "astro", + "dev": "astro dev", + "build": "astro build" + }, + "dependencies": { + "@wix/astro": "^2.39.0", + "@wix/astro-pages": "^2.0.4", + "@wix/astro-wix-hosting-adapter": "^2.0.0", + "@wix/categories": "^1.0.216", + "@wix/ecom": "^1.0.2252", + "@wix/essentials": "^1.0.6", + "@wix/forms": "^1.0.462", + "@wix/redirects": "^1.0.118", + "@wix/sdk": "^1.21.5", + "@wix/stores": "^1.0.829", + "astro": "^5.8.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "typescript": "^5.8.3" + }, + "devDependencies": { + "esbuild": "^0.27.7" + } +} diff --git a/astro/benny-pets/public/favicon.ico b/astro/benny-pets/public/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/astro/benny-pets/public/favicon.ico differ diff --git a/astro/benny-pets/public/gallery1.jpg b/astro/benny-pets/public/gallery1.jpg new file mode 100644 index 00000000..6ac3af1b Binary files /dev/null and b/astro/benny-pets/public/gallery1.jpg differ diff --git a/astro/benny-pets/public/gallery2.jpg b/astro/benny-pets/public/gallery2.jpg new file mode 100644 index 00000000..0b859cb2 Binary files /dev/null and b/astro/benny-pets/public/gallery2.jpg differ diff --git a/astro/benny-pets/public/gallery3.jpg b/astro/benny-pets/public/gallery3.jpg new file mode 100644 index 00000000..4cfa79fc Binary files /dev/null and b/astro/benny-pets/public/gallery3.jpg differ diff --git a/astro/benny-pets/public/hero-bg.jpg b/astro/benny-pets/public/hero-bg.jpg new file mode 100644 index 00000000..1109f5b8 Binary files /dev/null and b/astro/benny-pets/public/hero-bg.jpg differ diff --git a/astro/benny-pets/src/components/ContactForm.astro b/astro/benny-pets/src/components/ContactForm.astro new file mode 100644 index 00000000..fc208fc1 --- /dev/null +++ b/astro/benny-pets/src/components/ContactForm.astro @@ -0,0 +1,103 @@ +--- +import { getContactFields, type ContactField } from "../lib/contact-form"; + +// The fields come from Wix Forms, so relabelling or adding a field in the +// dashboard changes this form with no code change. +const fields: ContactField[] = await getContactFields(); +--- + +{ + fields.length > 0 && ( +