@cimplify/sdk 0.37.0 → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatcher.mjs +72 -2
- package/dist/mock/cli.mjs +13 -5
- package/dist/mock/library.js +13 -5
- package/dist/mock/library.mjs +13 -5
- package/dist/mock/msw.js +13 -5
- package/dist/mock/msw.mjs +13 -5
- package/package.json +3 -2
- package/templates/storefront/.env.example +2 -0
- package/templates/storefront/README.md +49 -0
- package/templates/storefront/index.html +12 -0
- package/templates/storefront/package.json +29 -0
- package/templates/storefront/src/App.tsx +118 -0
- package/templates/storefront/src/main.tsx +28 -0
- package/templates/storefront/src/styles.css +65 -0
- package/templates/storefront/tsconfig.json +19 -0
- package/templates/storefront/vite.config.ts +8 -0
package/dist/dispatcher.mjs
CHANGED
|
@@ -4,6 +4,7 @@ var HELP = `cimplify \u2014 Cimplify SDK CLI
|
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
6
|
bunx @cimplify/sdk Boot the local mock (alias for \`mock\`)
|
|
7
|
+
bunx @cimplify/sdk init [dir] Scaffold a working storefront (uses SDK components)
|
|
7
8
|
bunx @cimplify/sdk mock [flags] Boot the local Cimplify API mock
|
|
8
9
|
bunx @cimplify/sdk add <component> Install a UI component from the registry
|
|
9
10
|
bunx @cimplify/sdk list List available registry components
|
|
@@ -41,9 +42,11 @@ async function main() {
|
|
|
41
42
|
await import(join(here, "mock", "cli.mjs"));
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
45
|
+
case "init":
|
|
46
|
+
await runInit(args);
|
|
47
|
+
return;
|
|
44
48
|
case "add":
|
|
45
|
-
case "list":
|
|
46
|
-
case "init": {
|
|
49
|
+
case "list": {
|
|
47
50
|
process.argv = [process.argv[0], process.argv[1], sub, ...args];
|
|
48
51
|
const { fileURLToPath } = await import('url');
|
|
49
52
|
const { dirname, join } = await import('path');
|
|
@@ -57,6 +60,73 @@ async function main() {
|
|
|
57
60
|
process.exit(1);
|
|
58
61
|
}
|
|
59
62
|
}
|
|
63
|
+
async function runInit(args) {
|
|
64
|
+
const { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, copyFileSync } = await import('fs');
|
|
65
|
+
const { fileURLToPath } = await import('url');
|
|
66
|
+
const { dirname, join, resolve } = await import('path');
|
|
67
|
+
const { spawnSync } = await import('child_process');
|
|
68
|
+
const dir = args.find((a) => !a.startsWith("-")) ?? "my-storefront";
|
|
69
|
+
const target = resolve(process.cwd(), dir);
|
|
70
|
+
if (existsSync(target) && readdirSync(target).length > 0) {
|
|
71
|
+
console.error(`cimplify init: directory ${target} is not empty`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
mkdirSync(target, { recursive: true });
|
|
75
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
76
|
+
const candidates = [
|
|
77
|
+
resolve(here, "..", "templates", "storefront"),
|
|
78
|
+
resolve(here, "..", "..", "templates", "storefront"),
|
|
79
|
+
resolve(here, "templates", "storefront")
|
|
80
|
+
];
|
|
81
|
+
const source = candidates.find((p) => existsSync(p));
|
|
82
|
+
if (!source) {
|
|
83
|
+
console.error("cimplify init: template not found in any of:");
|
|
84
|
+
for (const c of candidates) console.error(" " + c);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const placeholderName = dir.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
88
|
+
const SUBSTITUTABLE = /* @__PURE__ */ new Set([".json", ".tsx", ".ts", ".html", ".md", ".css", ".env.example"]);
|
|
89
|
+
function copyTree(srcDir, dstDir) {
|
|
90
|
+
mkdirSync(dstDir, { recursive: true });
|
|
91
|
+
for (const entry of readdirSync(srcDir)) {
|
|
92
|
+
const s = join(srcDir, entry);
|
|
93
|
+
const d = join(dstDir, entry);
|
|
94
|
+
const stat = statSync(s);
|
|
95
|
+
if (stat.isDirectory()) {
|
|
96
|
+
copyTree(s, d);
|
|
97
|
+
} else {
|
|
98
|
+
const ext = entry.includes(".env") ? ".env.example" : entry.slice(entry.lastIndexOf("."));
|
|
99
|
+
if (SUBSTITUTABLE.has(ext)) {
|
|
100
|
+
const content = readFileSync(s, "utf8").replaceAll("__STOREFRONT_NAME__", placeholderName);
|
|
101
|
+
writeFileSync(d, content);
|
|
102
|
+
} else {
|
|
103
|
+
copyFileSync(s, d);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
copyTree(source, target);
|
|
109
|
+
const envExample = join(target, ".env.example");
|
|
110
|
+
if (existsSync(envExample)) {
|
|
111
|
+
copyFileSync(envExample, join(target, ".env.local"));
|
|
112
|
+
}
|
|
113
|
+
console.log(`\u2713 Scaffolded ${placeholderName} in ${target}`);
|
|
114
|
+
console.log("");
|
|
115
|
+
console.log("Installing dependencies...");
|
|
116
|
+
const installer = spawnSync(process.env.npm_execpath ? "bun" : "bun", ["install"], {
|
|
117
|
+
cwd: target,
|
|
118
|
+
stdio: "inherit"
|
|
119
|
+
});
|
|
120
|
+
if (installer.status !== 0) {
|
|
121
|
+
console.warn("(bun install failed; run it manually)");
|
|
122
|
+
}
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log("Next steps:");
|
|
125
|
+
console.log(` cd ${dir}`);
|
|
126
|
+
console.log(" bun dev # boots both the mock API and the storefront");
|
|
127
|
+
console.log("");
|
|
128
|
+
console.log("Then open http://localhost:5173");
|
|
129
|
+
}
|
|
60
130
|
async function runDispatcherFlag(flag) {
|
|
61
131
|
if (flag === "-h" || flag === "--help" || flag === "help") {
|
|
62
132
|
console.log(HELP);
|
package/dist/mock/cli.mjs
CHANGED
|
@@ -7839,11 +7839,19 @@ function createMockApp(options = {}) {
|
|
|
7839
7839
|
app.use("*", async (c, next) => {
|
|
7840
7840
|
const origin = c.req.header("origin") ?? "";
|
|
7841
7841
|
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7842
|
+
const headers = {
|
|
7843
|
+
"Access-Control-Allow-Origin": allow,
|
|
7844
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7845
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7846
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7847
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
7848
|
+
"Access-Control-Max-Age": "86400",
|
|
7849
|
+
Vary: "Origin"
|
|
7850
|
+
};
|
|
7851
|
+
if (c.req.method === "OPTIONS") {
|
|
7852
|
+
return new Response(null, { status: 204, headers });
|
|
7853
|
+
}
|
|
7854
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7847
7855
|
await next();
|
|
7848
7856
|
});
|
|
7849
7857
|
}
|
package/dist/mock/library.js
CHANGED
|
@@ -7207,11 +7207,19 @@ function createMockApp(options = {}) {
|
|
|
7207
7207
|
app.use("*", async (c, next) => {
|
|
7208
7208
|
const origin = c.req.header("origin") ?? "";
|
|
7209
7209
|
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7210
|
+
const headers = {
|
|
7211
|
+
"Access-Control-Allow-Origin": allow,
|
|
7212
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7213
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7214
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7215
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
7216
|
+
"Access-Control-Max-Age": "86400",
|
|
7217
|
+
Vary: "Origin"
|
|
7218
|
+
};
|
|
7219
|
+
if (c.req.method === "OPTIONS") {
|
|
7220
|
+
return new Response(null, { status: 204, headers });
|
|
7221
|
+
}
|
|
7222
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7215
7223
|
await next();
|
|
7216
7224
|
});
|
|
7217
7225
|
}
|
package/dist/mock/library.mjs
CHANGED
|
@@ -7205,11 +7205,19 @@ function createMockApp(options = {}) {
|
|
|
7205
7205
|
app.use("*", async (c, next) => {
|
|
7206
7206
|
const origin = c.req.header("origin") ?? "";
|
|
7207
7207
|
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7208
|
+
const headers = {
|
|
7209
|
+
"Access-Control-Allow-Origin": allow,
|
|
7210
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7211
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7212
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7213
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
7214
|
+
"Access-Control-Max-Age": "86400",
|
|
7215
|
+
Vary: "Origin"
|
|
7216
|
+
};
|
|
7217
|
+
if (c.req.method === "OPTIONS") {
|
|
7218
|
+
return new Response(null, { status: 204, headers });
|
|
7219
|
+
}
|
|
7220
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7213
7221
|
await next();
|
|
7214
7222
|
});
|
|
7215
7223
|
}
|
package/dist/mock/msw.js
CHANGED
|
@@ -7205,11 +7205,19 @@ function createMockApp(options = {}) {
|
|
|
7205
7205
|
app.use("*", async (c, next) => {
|
|
7206
7206
|
const origin = c.req.header("origin") ?? "";
|
|
7207
7207
|
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7208
|
+
const headers = {
|
|
7209
|
+
"Access-Control-Allow-Origin": allow,
|
|
7210
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7211
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7212
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7213
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
7214
|
+
"Access-Control-Max-Age": "86400",
|
|
7215
|
+
Vary: "Origin"
|
|
7216
|
+
};
|
|
7217
|
+
if (c.req.method === "OPTIONS") {
|
|
7218
|
+
return new Response(null, { status: 204, headers });
|
|
7219
|
+
}
|
|
7220
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7213
7221
|
await next();
|
|
7214
7222
|
});
|
|
7215
7223
|
}
|
package/dist/mock/msw.mjs
CHANGED
|
@@ -7203,11 +7203,19 @@ function createMockApp(options = {}) {
|
|
|
7203
7203
|
app.use("*", async (c, next) => {
|
|
7204
7204
|
const origin = c.req.header("origin") ?? "";
|
|
7205
7205
|
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7206
|
+
const headers = {
|
|
7207
|
+
"Access-Control-Allow-Origin": allow,
|
|
7208
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7209
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7210
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7211
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
7212
|
+
"Access-Control-Max-Age": "86400",
|
|
7213
|
+
Vary: "Origin"
|
|
7214
|
+
};
|
|
7215
|
+
if (c.req.method === "OPTIONS") {
|
|
7216
|
+
return new Response(null, { status: 204, headers });
|
|
7217
|
+
}
|
|
7218
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7211
7219
|
await next();
|
|
7212
7220
|
});
|
|
7213
7221
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cimplify/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"description": "Cimplify Commerce SDK for storefronts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cimplify",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
|
-
"registry"
|
|
21
|
+
"registry",
|
|
22
|
+
"templates"
|
|
22
23
|
],
|
|
23
24
|
"main": "./dist/index.js",
|
|
24
25
|
"module": "./dist/index.mjs",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# __STOREFRONT_NAME__
|
|
2
|
+
|
|
3
|
+
A Cimplify storefront scaffolded from the SDK template. Talks to the Cimplify mock API by default — swap one env var to point at production.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
bun dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Two things start in parallel:
|
|
13
|
+
|
|
14
|
+
- `bunx @cimplify/sdk` — the Cimplify mock API on `http://127.0.0.1:8787` (Akua's Bakery seeded by default).
|
|
15
|
+
- `vite` — this storefront on `http://localhost:5173`.
|
|
16
|
+
|
|
17
|
+
Open the storefront in your browser. Edit `src/App.tsx` to start customising. The SDK ships full pages (`<CataloguePage>`, `<ProductPage>`, `<CartPage>`, `<CheckoutPage>`) and layouts (`<DefaultProductLayout>`, `<FoodProductLayout>`, etc.) that you can swap in.
|
|
18
|
+
|
|
19
|
+
## Switch seed
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bunx @cimplify/sdk --seed restaurant # Mama's Kitchen
|
|
23
|
+
bunx @cimplify/sdk --seed retail # Currents Electronics
|
|
24
|
+
bunx @cimplify/sdk --seed services # Serene Spa
|
|
25
|
+
bunx @cimplify/sdk --seed grocery # FreshMart
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Update `VITE_CIMPLIFY_BUSINESS_ID` in `.env.local` to match the seeded business.
|
|
29
|
+
|
|
30
|
+
## Go live
|
|
31
|
+
|
|
32
|
+
```diff
|
|
33
|
+
# .env.local
|
|
34
|
+
- VITE_CIMPLIFY_API_URL=http://127.0.0.1:8787
|
|
35
|
+
+ VITE_CIMPLIFY_API_URL=https://api.cimplify.io
|
|
36
|
+
+ VITE_CIMPLIFY_API_KEY=<your tenant key>
|
|
37
|
+
- VITE_CIMPLIFY_BUSINESS_ID=bus_default_akua_bakery
|
|
38
|
+
+ VITE_CIMPLIFY_BUSINESS_ID=<your business id>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Add components
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bunx @cimplify/sdk add product-card
|
|
45
|
+
bunx @cimplify/sdk add cart-summary
|
|
46
|
+
bunx @cimplify/sdk list
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
These copy component sources directly into `src/components/` — yours to edit.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>__STOREFRONT_NAME__</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__STOREFRONT_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "concurrently -k -n mock,vite -c blue,magenta \"bunx @cimplify/sdk --quiet\" \"vite\"",
|
|
8
|
+
"dev:storefront": "vite",
|
|
9
|
+
"dev:mock": "bunx @cimplify/sdk",
|
|
10
|
+
"build": "tsc -b && vite build",
|
|
11
|
+
"preview": "vite preview"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@cimplify/sdk": "^0.38.1",
|
|
15
|
+
"react": "^19.2.5",
|
|
16
|
+
"react-dom": "^19.2.5",
|
|
17
|
+
"react-router-dom": "^7.9.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@tailwindcss/vite": "^4.2.4",
|
|
21
|
+
"@types/react": "^19.2.14",
|
|
22
|
+
"@types/react-dom": "^19.2.3",
|
|
23
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
24
|
+
"concurrently": "^9.0.0",
|
|
25
|
+
"tailwindcss": "^4.2.4",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vite": "^7.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Routes, Route, NavLink, useNavigate } from "react-router-dom";
|
|
3
|
+
import { CataloguePage, CartPage, CheckoutPage, useCimplifyClient } from "@cimplify/sdk/react";
|
|
4
|
+
import type { Product, Category } from "@cimplify/sdk";
|
|
5
|
+
|
|
6
|
+
function CartPill({ count }: { count: number }) {
|
|
7
|
+
return <span className="cimplify-storefront__cart-pill">Cart · {count}</span>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function Catalogue() {
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const { client } = useCimplifyClient();
|
|
13
|
+
const [products, setProducts] = useState<Product[] | undefined>(undefined);
|
|
14
|
+
const [categories, setCategories] = useState<Category[] | undefined>(undefined);
|
|
15
|
+
const [loading, setLoading] = useState(true);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
let cancelled = false;
|
|
19
|
+
void (async () => {
|
|
20
|
+
try {
|
|
21
|
+
const [p, c] = await Promise.all([
|
|
22
|
+
client.catalogue.getProducts({ limit: 50 }),
|
|
23
|
+
client.catalogue.getCategories(),
|
|
24
|
+
]);
|
|
25
|
+
if (cancelled) return;
|
|
26
|
+
if (p.ok) setProducts(p.value.items);
|
|
27
|
+
if (c.ok) setCategories(c.value);
|
|
28
|
+
} finally {
|
|
29
|
+
if (!cancelled) setLoading(false);
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
return () => {
|
|
33
|
+
cancelled = true;
|
|
34
|
+
};
|
|
35
|
+
}, [client]);
|
|
36
|
+
|
|
37
|
+
if (loading) return <p>Loading shop…</p>;
|
|
38
|
+
return (
|
|
39
|
+
<CataloguePage
|
|
40
|
+
title="Shop"
|
|
41
|
+
products={products}
|
|
42
|
+
categories={categories}
|
|
43
|
+
onProductClick={(p: Product) => navigate(`/products/${p.id}`)}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ProductDetail() {
|
|
49
|
+
return (
|
|
50
|
+
<div>
|
|
51
|
+
<p>Product detail page — wire up your favourite ProductPage layout here.</p>
|
|
52
|
+
<p>The SDK ships <code><ProductPage></code>, <code><DefaultProductLayout></code>, <code><FoodProductLayout></code> etc. — pick one.</p>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Cart() {
|
|
58
|
+
const navigate = useNavigate();
|
|
59
|
+
return <CartPage onCheckout={() => navigate("/checkout")} />;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Checkout() {
|
|
63
|
+
const navigate = useNavigate();
|
|
64
|
+
return (
|
|
65
|
+
<CheckoutPage
|
|
66
|
+
onSuccess={(orderId: string) => navigate(`/orders/${orderId}`)}
|
|
67
|
+
onCancel={() => navigate("/cart")}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function OrderConfirmation() {
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<h2>Thanks — your order is confirmed.</h2>
|
|
76
|
+
<p>This is the page shoppers land on after checkout.</p>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function App() {
|
|
82
|
+
const [cartCount, setCartCount] = useState(0);
|
|
83
|
+
const { client } = useCimplifyClient();
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
void client.cart.getCount().then((r) => {
|
|
87
|
+
if (r.ok) setCartCount(r.value);
|
|
88
|
+
});
|
|
89
|
+
}, [client]);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="cimplify-storefront">
|
|
93
|
+
<header className="cimplify-storefront__nav">
|
|
94
|
+
<NavLink to="/"><h1>cimplify-store</h1></NavLink>
|
|
95
|
+
<nav>
|
|
96
|
+
<NavLink to="/" end>Shop</NavLink>
|
|
97
|
+
<NavLink to="/cart">Cart</NavLink>
|
|
98
|
+
<CartPill count={cartCount} />
|
|
99
|
+
</nav>
|
|
100
|
+
</header>
|
|
101
|
+
|
|
102
|
+
<main className="cimplify-storefront__main">
|
|
103
|
+
<Routes>
|
|
104
|
+
<Route path="/" element={<Catalogue />} />
|
|
105
|
+
<Route path="/products/:productId" element={<ProductDetail />} />
|
|
106
|
+
<Route path="/cart" element={<Cart />} />
|
|
107
|
+
<Route path="/checkout" element={<Checkout />} />
|
|
108
|
+
<Route path="/orders/:orderId" element={<OrderConfirmation />} />
|
|
109
|
+
</Routes>
|
|
110
|
+
</main>
|
|
111
|
+
|
|
112
|
+
<footer className="cimplify-storefront__footer">
|
|
113
|
+
Built with <a href="https://github.com/cimplify/cimplify">@cimplify/sdk</a> ·{" "}
|
|
114
|
+
Mock API at <code>{import.meta.env.VITE_CIMPLIFY_API_URL ?? "http://127.0.0.1:8787"}</code>
|
|
115
|
+
</footer>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import { BrowserRouter } from "react-router-dom";
|
|
4
|
+
import { createCimplifyClient } from "@cimplify/sdk";
|
|
5
|
+
import { CimplifyProvider } from "@cimplify/sdk/react";
|
|
6
|
+
import { App } from "./App";
|
|
7
|
+
|
|
8
|
+
import "@cimplify/sdk/styles.css";
|
|
9
|
+
import "./styles.css";
|
|
10
|
+
|
|
11
|
+
const baseUrl = import.meta.env.VITE_CIMPLIFY_API_URL ?? "http://127.0.0.1:8787";
|
|
12
|
+
const publicKey = import.meta.env.VITE_CIMPLIFY_PUBLIC_KEY ?? "mock-dev";
|
|
13
|
+
|
|
14
|
+
const client = createCimplifyClient({
|
|
15
|
+
baseUrl,
|
|
16
|
+
publicKey,
|
|
17
|
+
suppressPublicKeyWarning: true,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
21
|
+
<React.StrictMode>
|
|
22
|
+
<BrowserRouter>
|
|
23
|
+
<CimplifyProvider client={client}>
|
|
24
|
+
<App />
|
|
25
|
+
</CimplifyProvider>
|
|
26
|
+
</BrowserRouter>
|
|
27
|
+
</React.StrictMode>,
|
|
28
|
+
);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
html, body {
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
|
|
7
|
+
background: #fafaf9;
|
|
8
|
+
color: #0f172a;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
* { box-sizing: border-box; }
|
|
12
|
+
a { color: inherit; text-decoration: none; }
|
|
13
|
+
|
|
14
|
+
.cimplify-storefront {
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.cimplify-storefront__nav {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
padding: 16px 24px;
|
|
25
|
+
background: white;
|
|
26
|
+
border-bottom: 1px solid #e2e8f0;
|
|
27
|
+
position: sticky;
|
|
28
|
+
top: 0;
|
|
29
|
+
z-index: 10;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.cimplify-storefront__nav h1 { font-size: 18px; margin: 0; }
|
|
33
|
+
.cimplify-storefront__nav nav a { margin-left: 16px; font-size: 14px; }
|
|
34
|
+
.cimplify-storefront__nav nav a.active { color: #2563eb; font-weight: 600; }
|
|
35
|
+
.cimplify-storefront__cart-pill {
|
|
36
|
+
margin-left: 16px;
|
|
37
|
+
padding: 6px 12px;
|
|
38
|
+
border-radius: 999px;
|
|
39
|
+
background: #0f172a;
|
|
40
|
+
color: white;
|
|
41
|
+
font-size: 13px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.cimplify-storefront__main {
|
|
46
|
+
flex: 1;
|
|
47
|
+
padding: 24px;
|
|
48
|
+
max-width: 1200px;
|
|
49
|
+
margin: 0 auto;
|
|
50
|
+
width: 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.cimplify-storefront__footer {
|
|
54
|
+
padding: 24px;
|
|
55
|
+
text-align: center;
|
|
56
|
+
font-size: 12px;
|
|
57
|
+
color: #64748b;
|
|
58
|
+
border-top: 1px solid #e2e8f0;
|
|
59
|
+
background: white;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.cimplify-storefront__main svg {
|
|
63
|
+
max-width: 64px;
|
|
64
|
+
max-height: 64px;
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"]
|
|
19
|
+
}
|