Skip to content

Commit 732ff65

Browse files
feat: spa-guest-checkout (#420)
* feat: create spa-guest-checkout-example * feat: improve add to cart error surfacing * feat: add a simple shipping option selector * feat: add a basic checkout step * feat: add clear cart functionality * feat: handle checkout error * feat: tidy form rendering, add delivbery instructions * feat: improve readme * feat: add next-account-checkout * fix: package.json * feat: clear unintentional commit * feat: refine types... * build: add to ignore list --------- Co-authored-by: Robert Field <[email protected]>
1 parent b3035ff commit 732ff65

22 files changed

+4288
-1
lines changed

.changeset/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"spa-authentication",
1919
"authentication-local-storage",
2020
"authentication-server-cookie",
21-
"list-products"
21+
"list-products",
22+
"spa-guest-checkout"
2223
]
2324
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

examples/spa-guest-checkout/README.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Guest Checkout SPA Example
2+
3+
This example showcases how to implement a **guest checkout** flow with **Elastic Path Commerce Cloud** in a Single-Page Application (SPA) written in React + Vite.
4+
5+
> **Heads-up:** This project **extends** the [Shopping Cart Management Example](../spa-cart). We only cover the additional checkout logic here—cart creation, item manipulation, promotions, etc. are documented in that README.
6+
7+
Key capabilities demonstrated:
8+
9+
1. **Cart Persistence** – automatically creates / retrieves a cart via the Shopper SDK.
10+
2. **Guest Checkout Form** – captures billing & shipping addresses in a single component with:
11+
_Same as billing_ toggle
12+
• Optional _Delivery Instructions_ field (shipping only)
13+
3. **Order Creation** – converts a cart to an order using `checkoutApi` _without_ requiring a customer account or payment integration.
14+
4. **Event-driven Cart Refresh** – broadcasts a `cart:updated` custom event so any part of the app can update after checkout.
15+
16+
> The example purposefully omits payment collection to keep the focus on the guest checkout mechanics. After an order is created the cart is cleared and a lightweight confirmation screen is displayed.
17+
18+
---
19+
20+
## Project Structure
21+
22+
```
23+
spa-guest-checkout/
24+
├── index.html # Vite entry point
25+
├── src/
26+
│ ├── App.tsx # Routes between Cart ↔︎ Checkout
27+
│ ├── components/
28+
│ │ ├── CartView.tsx # Cart listing & promo code support
29+
│ │ └── CheckoutView.tsx # Guest checkout form (billing + shipping)
30+
│ └── auth/CartProvider.tsx # Initializes / persists cart
31+
└── README.md # ← you are here
32+
```
33+
34+
## How It Works
35+
36+
### 1. Cart Initialization
37+
38+
`initializeCart()` from `@epcc-sdk/sdks-shopper` is executed on app load. If a cart already exists in local-storage its ID is reused, otherwise a new cart is created.
39+
40+
```tsx
41+
// src/auth/CartProvider.tsx
42+
useEffect(() => {
43+
initializeCart()
44+
}, [])
45+
```
46+
47+
### 2. Collecting Guest Details
48+
49+
`CheckoutView` keeps a nested `form` state:
50+
51+
```ts
52+
{
53+
billing: { firstName, lastName, email, line1, city, region, postcode, country },
54+
shipping: { firstName, lastName, line1, city, region, postcode, country, instructions },
55+
sameAsBilling: true
56+
}
57+
```
58+
59+
If **Same as billing** is checked the shipping fields are hidden and the billing address is re-used.
60+
61+
### 3. Converting Cart → Order
62+
63+
Upon submit the component calls:
64+
65+
```ts
66+
await checkoutApi({
67+
path: { cartID },
68+
body: {
69+
data: {
70+
customer: { email, name },
71+
billing_address: { ... },
72+
shipping_address: { ... }
73+
}
74+
}
75+
})
76+
```
77+
78+
The resulting order ID, total, and status are shown on a confirmation screen.
79+
80+
### 4. Clearing Cart & Refreshing UI
81+
82+
After a successful checkout:
83+
84+
```ts
85+
window.dispatchEvent(new Event("cart:updated"))
86+
```
87+
88+
Any listeners (e.g. `CartView`) reload their data ensuring an empty cart is reflected immediately.
89+
90+
---
91+
92+
## Running the Example Locally
93+
94+
1. **Install deps** (from the repo root):
95+
96+
```bash
97+
pnpm i # or npm install / yarn
98+
```
99+
100+
2. **Set environment variables** – create a `.env` file in `examples/spa-guest-checkout` (or export in your shell):
101+
102+
```
103+
VITE_APP_EPCC_ENDPOINT_URL=https://YOUR_EP_DOMAIN.elasticpath.com
104+
VITE_APP_EPCC_CLIENT_ID=YOUR_CLIENT_ID
105+
```
106+
107+
3. **Start Vite dev server**:
108+
109+
```bash
110+
pnpm --filter spa-guest-checkout dev
111+
```
112+
113+
## Key Components
114+
115+
- `CartProvider`: Initializes the cart on application load
116+
- `CartView`: Displays cart contents and manages cart operations
117+
- Cart utilities from SDK: `initializeCart`, `getCartId`
118+
119+
## Getting Started
120+
121+
Follow the setup instructions in the [SPA Authentication Example](../spa-authentication) README for authentication configuration.
122+
123+
### Environment Variables
124+
125+
This example uses the same environment variables as the SPA Authentication example:
126+
127+
```
128+
VITE_APP_EPCC_ENDPOINT_URL=your_endpoint_url
129+
VITE_APP_EPCC_CLIENT_ID=your_client_id
130+
```
131+
132+
## Learn More
133+
134+
- [Cart Management with Elastic Path](https://elasticpath.dev/docs/api/carts/cart-management)
135+
- [Promotions in Elastic Path](https://elasticpath.dev/docs/api/promotions-builder/rule-promotions-api)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import js from '@eslint/js'
2+
import globals from 'globals'
3+
import reactHooks from 'eslint-plugin-react-hooks'
4+
import reactRefresh from 'eslint-plugin-react-refresh'
5+
import tseslint from 'typescript-eslint'
6+
7+
export default tseslint.config(
8+
{ ignores: ['dist'] },
9+
{
10+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
11+
files: ['**/*.{ts,tsx}'],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
globals: globals.browser,
15+
},
16+
plugins: {
17+
'react-hooks': reactHooks,
18+
'react-refresh': reactRefresh,
19+
},
20+
rules: {
21+
...reactHooks.configs.recommended.rules,
22+
'react-refresh/only-export-components': [
23+
'warn',
24+
{ allowConstantExport: true },
25+
],
26+
},
27+
},
28+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en" class="h-full">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>SPA Guest Checkout Example - Elastic Path</title>
8+
</head>
9+
<body class="h-full bg-gray-50">
10+
<div id="root" class="h-full"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "spa-guest-checkout",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc -b && vite build",
9+
"lint": "eslint .",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"@epcc-sdk/sdks-shopper": "workspace:^",
14+
"react": "^19.1.0",
15+
"react-dom": "^19.1.0"
16+
},
17+
"devDependencies": {
18+
"@eslint/js": "^9.25.0",
19+
"@tailwindcss/vite": "^4.1.7",
20+
"@types/react": "^19.1.2",
21+
"@types/react-dom": "^19.1.2",
22+
"@vitejs/plugin-react": "^4.4.1",
23+
"autoprefixer": "^10.4.14",
24+
"eslint": "^9.25.0",
25+
"eslint-plugin-react-hooks": "^5.2.0",
26+
"eslint-plugin-react-refresh": "^0.4.19",
27+
"globals": "^16.0.0",
28+
"postcss": "^8.5.3",
29+
"tailwindcss": "^4.1.7",
30+
"typescript": "~5.8.3",
31+
"typescript-eslint": "^8.30.1",
32+
"vite": "^6.3.5"
33+
}
34+
}

0 commit comments

Comments
 (0)