diff --git a/.github/hooks/hooks.json b/.github/hooks/hooks.json new file mode 100644 index 000000000..12f43c2cb --- /dev/null +++ b/.github/hooks/hooks.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "hooks": { + "postToolUse": [ + { + "type": "command", + "bash": "node .github/hooks/scripts/biome-check.mjs", + "cwd": ".", + "timeoutSec": 60 + } + ] + } +} diff --git a/.github/hooks/scripts/biome-check.mjs b/.github/hooks/scripts/biome-check.mjs new file mode 100755 index 000000000..af1d52290 --- /dev/null +++ b/.github/hooks/scripts/biome-check.mjs @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +import { execSync } from "node:child_process" +import { createInterface } from "node:readline" + +const SUPPORTED_EXTENSIONS = /\.(ts|tsx|js|jsx|json)$/ +const FILE_TOOLS = new Set(["edit", "create"]) + +async function readStdin() { + return new Promise((resolve) => { + const rl = createInterface({ input: process.stdin, terminal: false }) + const lines = [] + rl.on("line", (line) => lines.push(line)) + rl.on("close", () => resolve(lines.join("\n"))) + }) +} + +const raw = await readStdin() +const input = JSON.parse(raw) + +if (!FILE_TOOLS.has(input.toolName)) process.exit(0) + +const toolArgs = JSON.parse(input.toolArgs) +const filePath = toolArgs.path + +if (!filePath || !SUPPORTED_EXTENSIONS.test(filePath)) process.exit(0) + +try { + execSync(`pnpm biome check "${filePath}"`, { + stdio: "inherit", + }) +} catch { + process.exit(1) +} diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index aa8057203..d70501805 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -1,11 +1,7 @@ name: Deploy to Pages on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab + # Temporarily disabled - re-enable by adding push trigger back workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages @@ -25,17 +21,17 @@ jobs: steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: pnpm 🧰 - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@v6 with: - version: 8 + version: 10 - name: Node 🧰 - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: - node-version: 'latest' + node-version: '20.x' cache: "pnpm" - name: Install 📦 diff --git a/.github/workflows/pgk-pr-new.yaml b/.github/workflows/pgk-pr-new.yaml index 6d67e3b4e..9a33dc85e 100644 --- a/.github/workflows/pgk-pr-new.yaml +++ b/.github/workflows/pgk-pr-new.yaml @@ -19,17 +19,17 @@ jobs: steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: pnpm 🧰 - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@v6 with: - version: 9.x + version: 10 - name: Node 🧰 - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 22.x @@ -37,8 +37,8 @@ jobs: run: pnpm install --frozen-lockfile - name: Build elements 🛠 - run: pnpm build + run: pnpm --filter @commercelayer/core --filter @commercelayer/hooks --filter @commercelayer/react-components build - name: Publish 🚀 pkg.pr.new run: | - npx pkg-pr-new publish './packages/react-components' --no-template \ No newline at end of file + npx pkg-pr-new publish './packages/react-components' './packages/core' './packages/hooks' --no-template \ No newline at end of file diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index cae645b00..b8b844dd2 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -14,15 +14,15 @@ jobs: steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: pnpm 🧰 - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10 - name: Node 🧰 - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: 20.x registry-url: https://registry.npmjs.org diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 077a5670f..4a679f5ea 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v4 - name: Create a draft GitHub release 🎁 - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: token: ${{ secrets.COMMERCELAYER_CI_TOKEN }} draft: true diff --git a/.gitignore b/.gitignore index d412007f9..fe210fbcc 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,7 @@ dist .DS_Store # Storybook -storybook-static \ No newline at end of file +storybook-static + +# Blog articles +episode-* diff --git a/.husky/pre-commit b/.husky/pre-commit index 9c96ce935..3cff7d0ed 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -pnpm build +pnpm build && pnpm test diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 000000000..d974312fa --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "storybook": { + "command": "pnpm", + "args": ["--filter", "document", "run", "mcp"] + } + } +} diff --git a/.ncurc.cjs b/.ncurc.cjs index 02031b6ee..dc2a84d8e 100644 --- a/.ncurc.cjs +++ b/.ncurc.cjs @@ -1,6 +1,3 @@ module.exports = { - reject: [ - 'pnpm', - 'iframe-resizer' - ] -} \ No newline at end of file + reject: ["pnpm", "iframe-resizer"], +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 6eb5b69da..60221547c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,9 @@ { - "typescript.tsdk": "node_modules/typescript/lib", "editor.formatOnSave": true, - "javascript.format.enable": true, - "eslint.workingDirectories": [ - "packages/react-components", - "packages/docs", - ], - "eslint.validate": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], - "css.validate": false, - "less.validate": false, - "scss.validate": false, + "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - } -} \ No newline at end of file + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, + "biome.configurationPath": "./biome.json" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..0a8788145 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +## Agent skills + +### Issue tracker + +Issues live in GitHub Issues at `commercelayer/commercelayer-react-components`. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Default five-role vocabulary (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`. + +### Domain docs + +Single-context repo: one `CONTEXT.md` + `docs/adr/` at the root. See `docs/agents/domain.md`. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 000000000..e83ae7172 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,36 @@ +# React Components — Payment + +This context covers the React components and state that let a storefront select a payment method and attach a payment source to an order during checkout. + +## Language + +**Payment Method**: +A selectable payment option on an order (a configured gateway option, e.g. "Stripe"). Backed by the `payment_method` resource. +_Avoid_: gateway (as a synonym), payment type + +**Payment Source**: +The concrete payment instrument record attached to a single order (e.g. `stripe_payment`, `adyen_payment`, `wire_transfer`). Created per order from the selected Payment Method. +_Avoid_: payment, card, token + +**Customer Payment Source**: +A Payment Source saved to a Customer for reuse across orders (a stored card). Selected via the order's `_customer_payment_source_id`. +_Avoid_: saved card (informal), wallet + +**Payment Gateway**: +In this codebase, the React component that wires a specific gateway's UI/SDK and drives Payment Source creation for that gateway (e.g. `StripeGateway`, `AdyenGateway`). +_Avoid_: using "gateway" to mean the Payment Method + +## Relationships + +- An **Order** has at most one **Payment Method** and one **Payment Source** +- A **Payment Source** is created from the selected **Payment Method** +- A **Customer Payment Source** belongs to a **Customer**; selecting one sets the **Order**'s Payment Source + +## Example dialogue + +> **Dev:** "When the shopper picks Stripe, do we create the **Payment Source** in the `StripePayment` component?" +> **Domain expert:** "No — Stripe has no dedicated creation component. The `StripeGateway`/`PaymentGateway` effect creates the **Payment Source** once the **Payment Method** is selected. Adyen and Braintree, by contrast, create it inside their own components." + +## Flagged ambiguities + +- "set payment source" was used to mean both the async operation that creates/attaches a Payment Source *and* the reducer action that stores it in state — resolved: the operation is `setPaymentSource(...)`, the reducer action is `dispatch({ type: "setPaymentSource" })`. diff --git a/biome.json b/biome.json index f44ba47bc..db23a36d8 100644 --- a/biome.json +++ b/biome.json @@ -1,41 +1,63 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", "vcs": { - "enabled": false, + "enabled": true, "clientKind": "git", - "useIgnoreFile": false + "useIgnoreFile": true }, "files": { - "ignoreUnknown": false, - "includes": ["**"] + "ignoreUnknown": true, + "includes": [ + "**", + "!dist", + "!coverage", + "!*.min.js", + "!**/mocks/resources/orders/customer-orders-full.ts" + ] }, "formatter": { "enabled": true, - "indentStyle": "space" + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } }, - "assist": { "actions": { "source": { "organizeImports": "on" } } }, "linter": { "enabled": true, "rules": { - "recommended": true, - "style": { - "noParameterAssign": "error", - "useAsConstAssertion": "error", - "useDefaultParameterLast": "error", - "useEnumInitializers": "error", - "useSelfClosingElements": "error", - "useSingleVarDeclarator": "error", - "noUnusedTemplateLiteral": "error", - "useNumberNamespace": "error", - "noInferrableTypes": "error", - "noUselessElse": "error" - } + "recommended": true } }, "javascript": { "formatter": { "quoteStyle": "double", + "trailingCommas": "es5", "semicolons": "asNeeded" } + }, + "json": { + "formatter": { + "enabled": true, + "trailingCommas": "none" + }, + "linter": { + "enabled": true + } + }, + "css": { + "formatter": { + "enabled": true, + "quoteStyle": "double" + }, + "linter": { + "enabled": true + } } } diff --git a/docs/adr/0001-coalesce-payment-source-requests.md b/docs/adr/0001-coalesce-payment-source-requests.md new file mode 100644 index 000000000..8dcdc8019 --- /dev/null +++ b/docs/adr/0001-coalesce-payment-source-requests.md @@ -0,0 +1,19 @@ +# Coalesce concurrent payment-source requests + +## Context + +`setPaymentSource` (in `PaymentMethodReducer.ts`) is a plain async function driven by React effects — notably the `PaymentGateway` effect, which fires it fire-and-forget from a 23-dependency effect. Before the first `create`/`update`/`updateOrder` request resolves, both `paymentSource` (context state) and `order.payment_source` are still null, so any dependency change re-runs the effect and fires a **second** request. On the Stripe / single-payment-method / new-source flow this produced two `stripe_payments.create` calls and two payment source records. + +## Decision + +De-duplicate genuinely-concurrent calls with a module-level `Map` keyed by `` `${order.id}:${paymentResource}:${customerPaymentSourceId ?? paymentSourceId ?? 'create'}` ``. The first call stores its in-flight promise; concurrent calls with the same key return that same promise; the entry is deleted in a `finally` once it settles. This coalesces duplicates within a burst without permanently memoizing, so a later legitimate re-create still runs. It backs up the narrower in-flight `useRef` guard in the `PaymentGateway` effect and protects every gateway, not just Stripe. + +## Considered options + +- **Effect-only `useRef` guard** — kept as the first line of defense, but too narrow: only protects the `PaymentGateway` caller, not other gateways or future callers. +- **Idempotency check against `order.payment_source`** — unreliable: the order is stale until `getOrder` refreshes after the first request. +- **Hashing `attributes` into the key** — rejected as fragile; attributes are effectively constant for a given order + resource + path within a burst. + +## Consequences + +The reducer module now holds mutable global state, which reads as surprising for an otherwise-stateless function — this ADR is the "why." The key deliberately separates the create, update/retrieve, and saved-source (`updateOrder`) paths so coalescing never merges semantically different operations. diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 000000000..b6b56b66e --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,39 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the codebase. + +## Before exploring, read these + +- **`CONTEXT.md`** at the repo root +- **`docs/adr/`** — read ADRs that touch the area you're about to work in + +If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The producer skill (`/grill-with-docs`) creates them lazily when terms or decisions actually get resolved. + +## File structure + +Single-context repo: + +``` +/ +├── CONTEXT.md +├── docs/adr/ +│ ├── 0001-*.md +│ └── ... +└── packages/ + ├── core/ + ├── hooks/ + ├── react-components/ + └── document/ +``` + +## Use the glossary's vocabulary + +When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. + +If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/grill-with-docs`). + +## Flag ADR conflicts + +If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0007 (…) — but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 000000000..cce77ecbb --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,22 @@ +# Issue tracker: GitHub + +Issues and PRDs for this repo live as GitHub issues. Use the `gh` CLI for all operations. + +## Conventions + +- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies. +- **Read an issue**: `gh issue view --comments`, filtering comments by `jq` and also fetching labels. +- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters. +- **Comment on an issue**: `gh issue comment --body "..."` +- **Apply / remove labels**: `gh issue edit --add-label "..."` / `--remove-label "..."` +- **Close**: `gh issue close --comment "..."` + +Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone. + +## When a skill says "publish to the issue tracker" + +Create a GitHub issue. + +## When a skill says "fetch the relevant ticket" + +Run `gh issue view --comments`. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 000000000..b716855d4 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,15 @@ +# Triage Labels + +The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker. + +| Label in mattpocock/skills | Label in our tracker | Meaning | +| -------------------------- | -------------------- | ---------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | +| `needs-info` | `needs-info` | Waiting on reporter for more information | +| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | +| `ready-for-human` | `ready-for-human` | Requires human implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table. + +Edit the right-hand column to match whatever vocabulary you actually use. diff --git a/examples/_app.tsx b/examples/_app.tsx deleted file mode 100644 index d6c0b77c8..000000000 --- a/examples/_app.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import '../styles/styles.css' -import { AppProps } from 'next/app' - -function MyApp({ Component, pageProps }: AppProps) { - return -} - -export default MyApp diff --git a/examples/cart.tsx b/examples/cart.tsx deleted file mode 100644 index f8f06b6a4..000000000 --- a/examples/cart.tsx +++ /dev/null @@ -1,278 +0,0 @@ -import { useState, useEffect, Fragment } from 'react' -import { getSalesChannelToken } from '@commercelayer/js-auth' -import { - CommerceLayer, - OrderContainer, - Price, - PricesContainer, - AddToCartButton, - LineItemsContainer, - LineItem, - LineItemImage, - LineItemName, - LineItemQuantity, - LineItemAmount, - LineItemRemoveLink, - LineItemsCount, - LineItemsEmpty, - CheckoutLink, - SubTotalAmount, - QuantitySelector, - TotalAmount, - DiscountAmount, - ShippingAmount, - TaxesAmount, - GiftCardAmount, - AvailabilityContainer, - AvailabilityTemplate, - ItemContainer, - Errors, - OrderStorage, - SkusContainer, - SkuField, - Skus, -} from 'packages/react-components/src' - -const clientId = process.env.NEXT_PUBLIC_CLIENT_ID as string -const endpoint = process.env.NEXT_PUBLIC_ENDPOINT as string -const scope = process.env.NEXT_PUBLIC_MARKET_ID as string - -const skus = [ - 'BABYONBU000000E63E7412MX', - 'CANVASAU000000FFFFFF1824', - 'BABYONBU000000E63E746MXX', -] - -export default function Cart() { - const [token, setToken] = useState('') - useEffect(() => { - const getToken = async () => { - const token = await getSalesChannelToken({ - clientId, - endpoint, - scope, - }) - if (token) setToken(token.accessToken) - } - getToken() - }, []) - return ( - - -
- - - - - - - - - -
-
- -
-
- - - -
-
-
-
-
- -

Shopping Bag

- -

- Your shopping bag contains{' '} - {' '} - items -

-
- - -
- - - - - - -
-
- -
- - - - - -
-
- -
- - - - - -
-
- -
- - - - - -
-
-
-
-
-
-
-

Subtotal

-
-
- -
-
-
-
-

Discount

-
-
- -
-
-
-
-

Shipping

-
-
- -
-
-
-
-

- Taxes (included) -

-
-
- -
-
-
-
-

Gift card

-
-
- -
-
-
-
-

Total

-
-
- -
-
-
-
- -
-
-
-
-
-
- ) -} diff --git a/examples/categoryOrder.tsx b/examples/categoryOrder.tsx deleted file mode 100644 index 0f49bc176..000000000 --- a/examples/categoryOrder.tsx +++ /dev/null @@ -1,280 +0,0 @@ -import React, { useState, useEffect, Fragment } from 'react' -import { getSalesChannelToken } from '@commercelayer/js-auth' -import CommerceLayer from '../#components/auth/CommerceLayer' -import OrderContainer from '../#components/OrderContainer' -import PriceContainer from '../#components/prices/PricesContainer' -import Price from '../#components/Price' -import AddToCart from '../#components/orders/AddToCartButton' -import LineItemsContainer from '../#components/LineItemsContainer' -import LineItem from '../#components/line_items/LineItem' -import LineItemImage from '../#components/LineItemImage' -import LineItemName from '../#components/LineItemName' -import LineItemQuantity from '../#components/LineItemQuantity' -import LineItemAmount from '../#components/LineItemAmount' -import LineItemRemoveLink from '../#components/LineItemRemoveLink' -import CheckoutLink from '../#components/orders/CheckoutLink' -import SubTotalAmount from '../#components/SubTotalAmount' -import QuantitySelector from '../#components/skus/QuantitySelector' -import LineItemsCount from '../#components/LineItemsCount' -import TotalAmount from '../#components/orders/TotalAmount' -import DiscountAmount from '../#components/orders/DiscountAmount' -import ShippingAmount from '../#components/orders/ShippingAmount' -import TaxesAmount from '../#components/TaxesAmount' -import GiftCardAmount from '../#components/orders/GiftCardAmount' -import AvailabilityContainer from '../#components/AvailabilityContainer' -import AvailabilityTemplate from '../#components/skus/AvailabilityTemplate' -import ItemContainer from '../#components/orders/ItemContainer' - -const clientId = process.env.NEXT_PUBLIC_CLIENT_ID as string -const endpoint = process.env.NEXT_PUBLIC_ENDPOINT as string -const scope = process.env.NEXT_PUBLIC_MARKET_ID as string -// const username = process.env.NEXT_PUBLIC_CUSTOMER_USERNAME as string -// const password = process.env.NEXT_PUBLIC_CUSTOMER_PASSWORD as string - -export default function Order() { - const [token, setToken] = useState('') - useEffect(() => { - const getToken = async () => { - const auth = await getSalesChannelToken({ - clientId, - endpoint, - scope, - }) - setToken(auth?.accessToken as string) - } - getToken() - }, []) - return ( - - -
- - - -
-
-
- -
-
-

- Black Baby Onesie Short Sleeve with Pink Logo (6 Months) -

-
-
-

- BABYONBU000000E63E746MXX -

-
-
- -
-
- -
-
- -
-
- - - -
-
-
-
- -
-
-

- White Long Sleeve T-shirt with Black Logo (L) -

-
-
-

- LSLEEVMMFFFFFF000000LXXX -

-
-
- -
-
- -
-
- -
-
- - - -
-
-
-
- -
-
-

- Pink Canvas with White Logo (18x24) -

-
-
-

- CANVASAUE63E74FFFFFF1824FAKE -

-
-
- -
-
- -
-
- -
-
- - - -
-
-
-
-
-

Shopping Bag

- -

- Your shopping bag contains{' '} - items -

- -
- - - - - -
-
-
-
-
-
-

Subtotal

-
-
- -
-
-
-
-

Discount

-
-
- -
-
-
-
-

Shipping

-
-
- -
-
-
-
-

- Taxes (included) -

-
-
- -
-
-
-
-

Gift card

-
-
- -
-
-
-
-

Total

-
-
- -
-
-
-
- -
-
-
-
-
- ) -} diff --git a/examples/checkout/addresses.tsx b/examples/checkout/addresses.tsx deleted file mode 100644 index e6e33b44a..000000000 --- a/examples/checkout/addresses.tsx +++ /dev/null @@ -1,811 +0,0 @@ -import React, { useState, useEffect, Fragment } from 'react' -import { getSalesChannelToken } from '@commercelayer/js-auth' -import { Nav } from '..' -import Head from 'next/head' -import { - CommerceLayer, - OrderContainer, - Errors, - AddressesContainer, - BillingAddressForm, - AddressInput, - AddressCountrySelector, - SaveAddressesButton, - ShippingAddressForm, - CustomerContainer, - CustomerInput, - SaveCustomerButton, - AddressStateSelector, -} from 'packages/react-components/src' -import { useRouter } from 'next/router' -import getSdk from '#utils/getSdk' - -const clientId = process.env.NEXT_PUBLIC_CLIENT_ID as string -const endpoint = process.env.NEXT_PUBLIC_ENDPOINT as string -const scope = process.env.NEXT_PUBLIC_MARKET_ID as string - -let orderId = '' - -export default function Main() { - const [token, setToken] = useState('') - const [customerEmail, setCustomerEmail] = useState('') - const [shipToDifferentAddress, setShipToDifferentAddress] = useState(false) - const [saveOnBlur, setSaveOnBlur] = useState(false) - const [isBusiness, setIsBusiness] = useState(false) - const [hideAddress, setHideAddress] = useState(false) - const [billingAddress, setBillingAddress] = useState({}) - const [shippingAddress, setShippingAddress] = useState({}) - const { query } = useRouter() - if (query.orderId) { - orderId = query.orderId as string - } - const getOrder = async () => { - if (token && orderId) { - const config = { accessToken: token, endpoint } - const sdk = getSdk(config) - try { - const order = await sdk.orders.retrieve(orderId, { - include: ['billing_address', 'shipping_address'], - }) - if (order?.billing_address) { - setBillingAddress(order.billing_address) - } - if (order.shipping_address) { - setShippingAddress(order.shipping_address) - } - if (order.customer_email) { - setCustomerEmail(order.customer_email) - } - } catch (error) { - console.error(error) - } - } - } - useEffect(() => { - const getToken = async () => { - // @ts-ignore - const token = await getSalesChannelToken({ - clientId, - endpoint, - scope, - }) - if (token) setToken(token.accessToken) - } - if (!token) { - getToken() - } else { - getOrder() - } - }, [token]) - const messages: any = [ - { - code: 'EMPTY_ERROR', - resource: 'billingAddress', - field: 'firstName', - message: `Can't be blank`, - }, - { - code: 'VALIDATION_ERROR', - resource: 'billingAddress', - field: 'email', - message: `Must be valid email`, - }, - ] - const handleOnSave = async () => { - if (token) { - try { - await getOrder() - } catch (error) { - console.error(error) - } - } - } - const handleClick = async () => { - await getOrder() - } - return ( - - - - -