create-cartbase 0.0.1 → 0.1.0

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -3
  3. package/dist/index.js +94 -0
  4. package/package.json +18 -4
  5. package/template/app/CLAUDE.md +18 -0
  6. package/template/app/docs/BUILD-A-STOREFRONT.md +216 -0
  7. package/template/app/docs/README.md +76 -0
  8. package/template/app/docs/auth.md +105 -0
  9. package/template/app/docs/carts.md +376 -0
  10. package/template/app/docs/categories.md +194 -0
  11. package/template/app/docs/checkout.md +611 -0
  12. package/template/app/docs/collections.md +167 -0
  13. package/template/app/docs/components.md +1089 -0
  14. package/template/app/docs/consent.md +81 -0
  15. package/template/app/docs/content.md +126 -0
  16. package/template/app/docs/customers.md +269 -0
  17. package/template/app/docs/deploy.md +192 -0
  18. package/template/app/docs/gift-cards.md +153 -0
  19. package/template/app/docs/integrations.md +137 -0
  20. package/template/app/docs/menus.md +73 -0
  21. package/template/app/docs/metaobjects.md +126 -0
  22. package/template/app/docs/orders.md +221 -0
  23. package/template/app/docs/products.md +300 -0
  24. package/template/app/docs/redirects.md +50 -0
  25. package/template/app/docs/regions.md +207 -0
  26. package/template/app/docs/reviews.md +223 -0
  27. package/template/app/docs/search.md +218 -0
  28. package/template/app/docs/subscriptions.md +148 -0
  29. package/template/app/next.config.ts +34 -0
  30. package/template/app/package.json +25 -0
  31. package/template/app/postcss.config.cjs +6 -0
  32. package/template/app/smoke.mjs +158 -0
  33. package/template/app/src/app/checkout/checkout-page-client.tsx +66 -0
  34. package/template/app/src/app/checkout/page.tsx +49 -0
  35. package/template/app/src/app/globals.css +42 -0
  36. package/template/app/src/app/layout.tsx +105 -0
  37. package/template/app/src/app/order/[id]/confirmed/page.tsx +77 -0
  38. package/template/app/src/app/page.tsx +25 -0
  39. package/template/app/src/app/products/[handle]/page.tsx +58 -0
  40. package/template/app/src/app/providers.tsx +54 -0
  41. package/template/app/src/app/search/page.tsx +20 -0
  42. package/template/app/src/lib/browser-client.ts +35 -0
  43. package/template/app/src/lib/cart-actions.ts +43 -0
  44. package/template/app/src/lib/config.ts +16 -0
  45. package/template/app/src/lib/server-client.ts +25 -0
  46. package/template/app/tailwind.config.cjs +9 -0
  47. package/template/app/tsconfig.json +41 -0
  48. package/template/app/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,167 @@
1
+ # Collections & membership listings
2
+
3
+ Curated product groupings (manual or smart). The membership listing —
4
+ `/collections/:id/products` — is the collection page's data source: it reads
5
+ the membership JOIN (multi-collection products appear in every collection
6
+ they belong to), honors the collection's `default_sort`, and accepts a
7
+ per-request `order` override. Money is EUR decimal major units.
8
+
9
+ SDK module: `@cartbase/storefront/api/collections`.
10
+
11
+ **Channel scope (Shopify publish-to-channel semantics):** a collection with
12
+ sales-channel links is visible ONLY on those channels; a collection with no
13
+ links is visible everywhere. Pass your channel as `sales_channel_id` — the
14
+ list excludes scoped-away collections, and the membership listing 404s them.
15
+
16
+ ---
17
+
18
+ ## GET /api/store/collections
19
+
20
+ - **Purpose** — list collections (navigation, collection index pages).
21
+ - **Auth** — anon: `x-client-id` required.
22
+ - **Request**
23
+
24
+ ```jsonc
25
+ // query (all optional)
26
+ {
27
+ "q": "essen", // case-insensitive substring on title
28
+ "handle": "essentials", // exact — THE handle lookup (no /:handle route)
29
+ "sales_channel_id": "sc_…", // channel scope (see above)
30
+ "limit": 50, // 1–200, default 50
31
+ "offset": 0
32
+ }
33
+ ```
34
+
35
+ - **Response** — `{ collections, count, offset, limit }`, ordered by title:
36
+
37
+ ```jsonc
38
+ {
39
+ "collections": [
40
+ {
41
+ "id": "pcol_01tst00000000000000000001",
42
+ "title": "Essentials",
43
+ "handle": "essentials",
44
+ "type": "manual", // "manual" | "smart"
45
+ "description": null,
46
+ "image_url": null,
47
+ "default_sort": "manual", // used by /products when no order override
48
+ "conditions": [], // smart-collection rules (admin-authored)
49
+ "match": "all", // smart matching: "all" | "any"
50
+ "seo_title": null, // null = fall back to title
51
+ "seo_description": null, // null = fall back to description
52
+ "metadata": null,
53
+ "created_at": "2026-07-01T00:00:00.000Z",
54
+ "updated_at": "2026-07-01T00:00:00.000Z"
55
+ }
56
+ ],
57
+ "count": 1, "offset": 0, "limit": 50
58
+ }
59
+ ```
60
+
61
+ - **Working curl** — the seeded catalog carries the `essentials` collection:
62
+
63
+ ```bash
64
+ COLLECTIONS=$(curl -sf "$BASE/api/store/collections?handle=essentials" \
65
+ -H "x-client-id: $CLIENT_ID")
66
+ echo "$COLLECTIONS" | grep -q '"collections"'
67
+ echo "$COLLECTIONS" | grep -q '"handle":"essentials"'
68
+ COL_ID=$(echo "$COLLECTIONS" | grep -o '"id":"pcol_[^"]*"' | head -1 | cut -d'"' -f4)
69
+ test -n "$COL_ID"
70
+ ```
71
+
72
+ - **Errors** — 400 `missing_client_id`, 400 `validation_failed`.
73
+ - **SDK** — `listCollections(client, query?)`.
74
+ - **Components** — navigation, collection index grid.
75
+ - **Settings** — collection channel links; smart-collection conditions
76
+ (membership recomputes on rule/product change).
77
+
78
+ ---
79
+
80
+ ## GET /api/store/collections/:id
81
+
82
+ - **Purpose** — retrieve one collection (header/SEO block of a collection
83
+ page). By-handle lookup goes through the list (`?handle=`).
84
+ - **Auth** — anon: `x-client-id` required.
85
+ - **Request** — no query. NOTE (code truth): the single read takes no
86
+ `sales_channel_id` — channel scope applies to the list and the membership
87
+ listing, not here.
88
+ - **Response** — `{ "collection": { ...same shape as list rows... } }`
89
+ - **Working curl**
90
+
91
+ ```bash
92
+ COLLECTION=$(curl -sf "$BASE/api/store/collections/$COL_ID" -H "x-client-id: $CLIENT_ID")
93
+ echo "$COLLECTION" | grep -q '"collection"'
94
+ echo "$COLLECTION" | grep -q '"default_sort"'
95
+ ```
96
+
97
+ - **Errors** — 404 `not_found`.
98
+
99
+ ```bash
100
+ STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
101
+ "$BASE/api/store/collections/pcol_doesnotexist$RUN" -H "x-client-id: $CLIENT_ID")
102
+ test "$STATUS" = 404
103
+ ```
104
+
105
+ - **SDK** — `retrieveCollection(client, collectionId)`.
106
+ - **Components** — collection page header.
107
+ - **Settings** — SEO overrides.
108
+
109
+ ---
110
+
111
+ ## GET /api/store/collections/:id/products
112
+
113
+ - **Purpose** — the collection page's product grid: membership join,
114
+ published products only, ordered by the collection's `default_sort` with
115
+ an optional `order` override.
116
+ - **Auth** — anon: `x-client-id`; optional Bearer JWT (group pricing).
117
+ - **Request**
118
+
119
+ ```jsonc
120
+ // query (all optional)
121
+ {
122
+ "order": "price_asc", // override: manual | title_asc | title_desc |
123
+ // price_asc | price_desc | newest | oldest |
124
+ // best_selling (90-day aggregate).
125
+ // Unknown values are IGNORED (default_sort used).
126
+ "sales_channel_id": "sc_…", // a collection scoped to OTHER channels 404s
127
+ "currency_code": "eur", // pricing context → calculated_price
128
+ "region_id": "reg_…",
129
+ "limit": 50, // 1–100, default 50
130
+ "offset": 0
131
+ }
132
+ ```
133
+
134
+ - **Response** — `{ products, count, offset, limit }` — products carry the
135
+ FULL canonical product shape (see products.md), incl. `calculated_price`
136
+ when a pricing context is given. `count` is the visible membership size.
137
+ - **Working curl** — seeded membership (backfilled from
138
+ `products.collection_id`) contains the three fixture products:
139
+
140
+ ```bash
141
+ MEMBERS=$(curl -sf "$BASE/api/store/collections/$COL_ID/products?currency_code=eur" \
142
+ -H "x-client-id: $CLIENT_ID")
143
+ echo "$MEMBERS" | grep -q '"products"'
144
+ echo "$MEMBERS" | grep -q '"handle":"linen-shirt"'
145
+ echo "$MEMBERS" | grep -q '"calculated_price"'
146
+ ```
147
+
148
+ ```bash
149
+ # Sort override: price_asc puts the Wool Beanie (EUR 23) before the Linen
150
+ # Shirt (EUR 45) — asserted as RELATIVE order so unrelated rows can't break it.
151
+ curl -sf "$BASE/api/store/collections/$COL_ID/products?order=price_asc" \
152
+ -H "x-client-id: $CLIENT_ID" | node -e "
153
+ const c=[];process.stdin.on('data',d=>c.push(d)).on('end',()=>{
154
+ const j=JSON.parse(Buffer.concat(c));
155
+ const h=j.products.map(p=>p.handle);
156
+ const a=h.indexOf('wool-beanie'), b=h.indexOf('linen-shirt');
157
+ if(a<0||b<0||a>b){console.error('price_asc order wrong: '+h.join(','));process.exit(1)}
158
+ })"
159
+ ```
160
+
161
+ - **Errors** — 404 `not_found` (unknown collection, or scoped away from the
162
+ given `sales_channel_id`), 400 `validation_failed`, 400 `invalid_region`.
163
+ - **SDK** — `listCollectionProducts(client, collectionId, query?)`.
164
+ - **Components** — product card grid + sort dropdown (emit the `order`
165
+ values above).
166
+ - **Settings** — collection `default_sort` + manual position order
167
+ (drag-reorder in admin); price lists; channel links.