@cartbase/storefront 0.17.0 → 0.18.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.
Files changed (108) hide show
  1. package/LICENSE +21 -21
  2. package/package.json +3 -1
  3. package/src/api/auth.ts +108 -108
  4. package/src/api/carts.ts +523 -523
  5. package/src/api/categories.ts +184 -184
  6. package/src/api/checkout.ts +526 -526
  7. package/src/api/collections.ts +130 -130
  8. package/src/api/consent.ts +75 -75
  9. package/src/api/content.ts +125 -125
  10. package/src/api/customers.ts +303 -303
  11. package/src/api/gift-cards.ts +112 -112
  12. package/src/api/http.ts +180 -180
  13. package/src/api/index.ts +30 -30
  14. package/src/api/menus.ts +77 -77
  15. package/src/api/metaobjects.ts +136 -136
  16. package/src/api/orders.ts +290 -290
  17. package/src/api/products.ts +43 -1
  18. package/src/api/redirects.ts +37 -37
  19. package/src/api/regions.ts +200 -200
  20. package/src/api/reviews.ts +259 -259
  21. package/src/api/search.ts +163 -163
  22. package/src/api/store.ts +35 -35
  23. package/src/api/types.ts +91 -91
  24. package/src/cart-drawer/context.tsx +367 -168
  25. package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
  26. package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
  27. package/src/cart-drawer/gift-wrap.tsx +82 -82
  28. package/src/cart-drawer/index.ts +73 -68
  29. package/src/cart-drawer/item/index.tsx +162 -162
  30. package/src/cart-drawer/item/quantity.tsx +8 -14
  31. package/src/cart-drawer/item/upsell.tsx +110 -110
  32. package/src/cart-drawer/labels.ts +123 -123
  33. package/src/cart-drawer/mutation-queue.ts +78 -0
  34. package/src/cart-drawer/notes.tsx +131 -131
  35. package/src/cart-drawer/sticky-footer.tsx +73 -73
  36. package/src/cart-drawer/summary-breakdown.tsx +197 -197
  37. package/src/checkout/address-error-copy.ts +117 -117
  38. package/src/checkout/boxnow-locker-selector.tsx +410 -410
  39. package/src/checkout/compare-addresses.ts +40 -40
  40. package/src/checkout/discount-section.tsx +218 -218
  41. package/src/checkout/error-copy-codes.ts +63 -63
  42. package/src/checkout/geocode.ts +154 -154
  43. package/src/checkout/gift-card-section.tsx +224 -224
  44. package/src/checkout/index.ts +72 -72
  45. package/src/checkout/labels.ts +495 -495
  46. package/src/checkout/payment-button.tsx +372 -372
  47. package/src/checkout/payment-error-copy.ts +154 -154
  48. package/src/checkout/promotion-error-copy.ts +91 -91
  49. package/src/checkout/use-checkout-orchestration.ts +1604 -1592
  50. package/src/common/country-flag.tsx +52 -52
  51. package/src/common/country-select.tsx +11 -11
  52. package/src/common/index.ts +20 -20
  53. package/src/common/market-select.tsx +57 -57
  54. package/src/index.ts +12 -12
  55. package/src/lib/cookie-names.ts +14 -0
  56. package/src/lib/country-name.ts +59 -59
  57. package/src/lib/get-product-price.ts +133 -133
  58. package/src/lib/media-image.tsx +39 -39
  59. package/src/lib/payment-constants.ts +53 -53
  60. package/src/lib/platform.ts +13 -13
  61. package/src/lib/price.tsx +39 -39
  62. package/src/lib/store-api-error.ts +36 -36
  63. package/src/lib/variant-caption.ts +32 -32
  64. package/src/locales/bg.ts +467 -467
  65. package/src/locales/context.ts +37 -37
  66. package/src/locales/en.ts +26 -26
  67. package/src/locales/es.ts +466 -466
  68. package/src/locales/index.ts +19 -19
  69. package/src/locales/provider.tsx +59 -59
  70. package/src/locales/types.ts +77 -77
  71. package/src/order/index.ts +62 -62
  72. package/src/order/labels.ts +79 -79
  73. package/src/order/order-totals.tsx +250 -250
  74. package/src/primitives/select-field.tsx +93 -93
  75. package/src/products/image-gallery.tsx +43 -43
  76. package/src/products/index.ts +68 -59
  77. package/src/products/option-select.tsx +67 -67
  78. package/src/products/product-actions-wrapper.tsx +63 -62
  79. package/src/products/product-actions.tsx +151 -150
  80. package/src/products/product-price.tsx +69 -69
  81. package/src/products/product-promises.tsx +61 -61
  82. package/src/products/product-specs.tsx +65 -65
  83. package/src/products/product-tabs.tsx +123 -123
  84. package/src/products/product-template.tsx +163 -162
  85. package/src/products/purchase-options.tsx +130 -130
  86. package/src/products/sets.ts +110 -0
  87. package/src/products/use-product-actions.ts +199 -164
  88. package/src/products/variant-matching.ts +71 -71
  89. package/src/products/variant-url.ts +74 -74
  90. package/src/reviews-ui/index.ts +73 -73
  91. package/src/store/category-template.tsx +136 -136
  92. package/src/store/index.ts +40 -40
  93. package/src/tracking/chatgpt-pixel.tsx +99 -99
  94. package/src/tracking/consent-init.tsx +62 -62
  95. package/src/tracking/events.ts +348 -348
  96. package/src/tracking/ga4.tsx +93 -93
  97. package/src/tracking/google-ads.ts +84 -84
  98. package/src/tracking/gtm.tsx +60 -60
  99. package/src/tracking/inline-script.ts +49 -49
  100. package/src/tracking/oaiq.ts +206 -206
  101. package/src/tracking/tiktok-pixel.tsx +91 -91
  102. package/src/tracking/track-init.tsx +56 -56
  103. package/src/tracking/track-order-purchase.tsx +122 -122
  104. package/src/tracking/ttq.ts +180 -180
  105. package/src/tracking/use-tracking-config.ts +54 -54
  106. package/theme/index.css +25 -25
  107. package/theme/theme.css +150 -150
  108. package/theme/tokens.css +106 -106
@@ -1,303 +1,303 @@
1
- /**
2
- * @cartbase/storefront/api/customers — customer profile, addresses, documents.
3
- *
4
- * Ground truth: src/app/api/store/customers/** + src/app/api/_lib/customer.ts.
5
- *
6
- * Auth model: every endpoint here except none is CUSTOMER-authenticated —
7
- * `authorization: Bearer <supabase jwt>` (minted by the passwordless code
8
- * flow in ./auth or a client-side supabase password sign-in) PLUS the anon
9
- * `x-client-id` tenant header the client always sends. Missing/invalid JWT →
10
- * `401 unauthenticated`.
11
- *
12
- * Lazy registration: `/customers/me` resolves the customer row by
13
- * (client_id, JWT email) and CREATES it on first authenticated call
14
- * (has_account=true, account_status per the store's approval policy) — a
15
- * fresh login never 404s on "me".
16
- */
17
-
18
- import type { StorefrontClient } from "./http"
19
- import type { IsoDateString, PaginationQuery } from "./types"
20
-
21
- /** One row of `customer.addresses` (barter_commerce.customer_addresses), as the store serves it. */
22
- export interface CustomerAddress {
23
- id: string
24
- customer_id: string
25
- address_name: string | null
26
- first_name: string | null
27
- last_name: string | null
28
- company: string | null
29
- address_1: string | null
30
- address_2: string | null
31
- city: string | null
32
- country_code: string | null
33
- province: string | null
34
- postal_code: string | null
35
- phone: string | null
36
- is_default_billing: boolean
37
- is_default_shipping: boolean
38
- metadata: Record<string, unknown> | null
39
- created_at: IsoDateString
40
- updated_at: IsoDateString
41
- }
42
-
43
- /**
44
- * The customer payload: the customer's own row as the store serves it, plus
45
- * addresses. Named columns since 2026-09-14: no tenant id, no soft-delete
46
- * stamp, no staff identity, no provider account id, and no merchant tags
47
- * (the labels a merchant puts on a customer are staff knowledge and never
48
- * reach the shopper).
49
- * `account_status` (`pending` | `approved`) gates B2B storefront content
50
- * when the store's approval policy is on (b2b-v1).
51
- */
52
- export interface StoreCustomer {
53
- id: string
54
- email: string | null
55
- first_name: string | null
56
- last_name: string | null
57
- phone: string | null
58
- company_name: string | null
59
- /** Bulgarian company id (ЕИК) — feeds invoice-required checkout. */
60
- company_eik: string | null
61
- vat_number: string | null
62
- has_account: boolean
63
- /** `pending` | `approved` — store approval policy (customer-accounts card). */
64
- account_status: string
65
- metadata: Record<string, unknown> | null
66
- created_at: IsoDateString
67
- updated_at: IsoDateString
68
- addresses: CustomerAddress[]
69
- }
70
-
71
- export interface CustomerResponse {
72
- customer: StoreCustomer
73
- }
74
-
75
- export interface CreateCustomerInput {
76
- /** Must equal the session JWT's email — mismatch → 403 email_mismatch. */
77
- email: string
78
- first_name?: string
79
- last_name?: string
80
- phone?: string
81
- company_name?: string
82
- }
83
-
84
- /**
85
- * POST /api/store/customers — guest-to-registered conversion (password
86
- * sign-up flow): call `supabase.auth.signUp({email, password})` client-side
87
- * first, then this with the same email. Upserts the (client_id, email)
88
- * customer row with has_account=true. NOT needed for the passwordless code
89
- * flow — verify already lazy-creates the customer.
90
- *
91
- * Auth: Bearer JWT (+ x-client-id).
92
- * Errors: 401 unauthenticated · 403 email_mismatch (body email ≠ JWT email)
93
- * · 400 validation_failed.
94
- * Settings: store approval policy decides account_status of a NEW row.
95
- */
96
- export async function createCustomer(
97
- client: StorefrontClient,
98
- input: CreateCustomerInput
99
- ): Promise<CustomerResponse> {
100
- return client.post("/api/store/customers", input)
101
- }
102
-
103
- /**
104
- * GET /api/store/customers/me — the signed-in customer, addresses embedded.
105
- * Lazy-creates the customer row on first call for this (tenant, email).
106
- *
107
- * Auth: Bearer JWT (+ x-client-id).
108
- * Errors: 401 unauthenticated · 400 missing_client_id.
109
- */
110
- export async function getMe(client: StorefrontClient): Promise<CustomerResponse> {
111
- return client.get("/api/store/customers/me")
112
- }
113
-
114
- export interface UpdateCustomerInput {
115
- first_name?: string | null
116
- last_name?: string | null
117
- phone?: string | null
118
- company_name?: string | null
119
- /** ЕИК / VAT (customer-master-data) — loose format, never shape-blocked. */
120
- company_eik?: string | null
121
- vat_number?: string | null
122
- metadata?: Record<string, unknown>
123
- }
124
-
125
- /**
126
- * POST /api/store/customers/me — update own profile. Unknown keys are
127
- * stripped server-side; returns the refreshed customer.
128
- *
129
- * Auth: Bearer JWT (+ x-client-id).
130
- * Errors: 401 unauthenticated · 400 validation_failed.
131
- */
132
- export async function updateMe(
133
- client: StorefrontClient,
134
- input: UpdateCustomerInput
135
- ): Promise<CustomerResponse> {
136
- return client.post("/api/store/customers/me", input)
137
- }
138
-
139
- export interface AddressListResponse {
140
- addresses: CustomerAddress[]
141
- /** Not truly paginated: offset is always 0 and limit === count. */
142
- count: number
143
- offset: number
144
- limit: number
145
- }
146
-
147
- /**
148
- * GET /api/store/customers/me/addresses — all of the customer's addresses
149
- * (created_at asc). The list envelope is nominal — the route returns EVERY
150
- * address (offset 0, limit = count); no query params are read.
151
- *
152
- * Auth: Bearer JWT (+ x-client-id). Errors: 401 unauthenticated.
153
- */
154
- export async function listAddresses(
155
- client: StorefrontClient
156
- ): Promise<AddressListResponse> {
157
- return client.get("/api/store/customers/me/addresses")
158
- }
159
-
160
- export interface CreateAddressInput {
161
- address_name?: string
162
- first_name?: string
163
- last_name?: string
164
- company?: string
165
- address_1?: string
166
- address_2?: string
167
- city?: string
168
- /** Lower-cased server-side. */
169
- country_code?: string
170
- province?: string
171
- postal_code?: string
172
- phone?: string
173
- /** true clears the flag on every sibling first (one default per kind). */
174
- is_default_billing?: boolean
175
- is_default_shipping?: boolean
176
- metadata?: Record<string, unknown>
177
- }
178
-
179
- /**
180
- * POST /api/store/customers/me/addresses — create an address. Returns the
181
- * FULL refreshed customer (`{customer}`, addresses embedded) — not the
182
- * created address alone; find it in `customer.addresses`.
183
- *
184
- * Auth: Bearer JWT (+ x-client-id).
185
- * Errors: 401 unauthenticated · 400 validation_failed.
186
- */
187
- export async function createAddress(
188
- client: StorefrontClient,
189
- input: CreateAddressInput
190
- ): Promise<CustomerResponse> {
191
- return client.post("/api/store/customers/me/addresses", input)
192
- }
193
-
194
- export interface AddressResponse {
195
- address: CustomerAddress
196
- }
197
-
198
- /**
199
- * GET /api/store/customers/me/addresses/:id — one owned address.
200
- *
201
- * Auth: Bearer JWT (+ x-client-id).
202
- * Errors: 401 unauthenticated · 404 not_found (unknown OR another
203
- * customer's address — ownership 404s, it never 403s).
204
- */
205
- export async function getAddress(
206
- client: StorefrontClient,
207
- addressId: string
208
- ): Promise<AddressResponse> {
209
- return client.get(`/api/store/customers/me/addresses/${encodeURIComponent(addressId)}`)
210
- }
211
-
212
- export interface UpdateAddressInput {
213
- address_name?: string | null
214
- first_name?: string | null
215
- last_name?: string | null
216
- company?: string | null
217
- address_1?: string | null
218
- address_2?: string | null
219
- city?: string | null
220
- country_code?: string | null
221
- province?: string | null
222
- postal_code?: string | null
223
- phone?: string | null
224
- is_default_billing?: boolean
225
- is_default_shipping?: boolean
226
- metadata?: Record<string, unknown>
227
- }
228
-
229
- /**
230
- * POST /api/store/customers/me/addresses/:id — partial update; default
231
- * flags clear siblings. Returns the FULL refreshed customer.
232
- *
233
- * Auth: Bearer JWT (+ x-client-id).
234
- * Errors: 401 unauthenticated · 404 not_found · 400 validation_failed.
235
- */
236
- export async function updateAddress(
237
- client: StorefrontClient,
238
- addressId: string,
239
- input: UpdateAddressInput
240
- ): Promise<CustomerResponse> {
241
- return client.post(
242
- `/api/store/customers/me/addresses/${encodeURIComponent(addressId)}`,
243
- input
244
- )
245
- }
246
-
247
- /**
248
- * DELETE /api/store/customers/me/addresses/:id — soft-delete an owned
249
- * address. Returns the FULL refreshed customer (the address gone from
250
- * `customer.addresses`).
251
- *
252
- * Auth: Bearer JWT (+ x-client-id).
253
- * Errors: 401 unauthenticated · 404 not_found.
254
- */
255
- export async function deleteAddress(
256
- client: StorefrontClient,
257
- addressId: string
258
- ): Promise<CustomerResponse> {
259
- return client.delete(
260
- `/api/store/customers/me/addresses/${encodeURIComponent(addressId)}`
261
- )
262
- }
263
-
264
- /** One issued (non-void) order document — the account "Invoices" row. */
265
- export interface CustomerDocument {
266
- id: string
267
- order_id: string
268
- order_display_id: number | null
269
- doc_type: string
270
- number: string
271
- issued_at: IsoDateString
272
- pdf_url: string | null
273
- due_date: IsoDateString | null
274
- paid_at: IsoDateString | null
275
- }
276
-
277
- export interface ListDocumentsQuery extends PaginationQuery {
278
- /** Filter by document type (e.g. `invoice`). */
279
- doc_type?: string
280
- }
281
-
282
- export interface DocumentListResponse {
283
- documents: CustomerDocument[]
284
- count: number
285
- offset: number
286
- limit: number
287
- }
288
-
289
- /**
290
- * GET /api/store/customers/me/documents — the customer's issued order
291
- * documents, newest first (issued_at desc). Voided documents excluded.
292
- * Ownership is INNER-join enforced (session customer id AND client_id) —
293
- * cross-customer reads are impossible. `limit` ≤ 200, default 20.
294
- *
295
- * Auth: Bearer JWT (+ x-client-id).
296
- * Errors: 401 unauthenticated · 400 validation_failed.
297
- */
298
- export async function listMyDocuments(
299
- client: StorefrontClient,
300
- query?: ListDocumentsQuery
301
- ): Promise<DocumentListResponse> {
302
- return client.get("/api/store/customers/me/documents", { query: { ...query } })
303
- }
1
+ /**
2
+ * @cartbase/storefront/api/customers — customer profile, addresses, documents.
3
+ *
4
+ * Ground truth: src/app/api/store/customers/** + src/app/api/_lib/customer.ts.
5
+ *
6
+ * Auth model: every endpoint here except none is CUSTOMER-authenticated —
7
+ * `authorization: Bearer <supabase jwt>` (minted by the passwordless code
8
+ * flow in ./auth or a client-side supabase password sign-in) PLUS the anon
9
+ * `x-client-id` tenant header the client always sends. Missing/invalid JWT →
10
+ * `401 unauthenticated`.
11
+ *
12
+ * Lazy registration: `/customers/me` resolves the customer row by
13
+ * (client_id, JWT email) and CREATES it on first authenticated call
14
+ * (has_account=true, account_status per the store's approval policy) — a
15
+ * fresh login never 404s on "me".
16
+ */
17
+
18
+ import type { StorefrontClient } from "./http"
19
+ import type { IsoDateString, PaginationQuery } from "./types"
20
+
21
+ /** One row of `customer.addresses` (barter_commerce.customer_addresses), as the store serves it. */
22
+ export interface CustomerAddress {
23
+ id: string
24
+ customer_id: string
25
+ address_name: string | null
26
+ first_name: string | null
27
+ last_name: string | null
28
+ company: string | null
29
+ address_1: string | null
30
+ address_2: string | null
31
+ city: string | null
32
+ country_code: string | null
33
+ province: string | null
34
+ postal_code: string | null
35
+ phone: string | null
36
+ is_default_billing: boolean
37
+ is_default_shipping: boolean
38
+ metadata: Record<string, unknown> | null
39
+ created_at: IsoDateString
40
+ updated_at: IsoDateString
41
+ }
42
+
43
+ /**
44
+ * The customer payload: the customer's own row as the store serves it, plus
45
+ * addresses. Named columns since 2026-09-14: no tenant id, no soft-delete
46
+ * stamp, no staff identity, no provider account id, and no merchant tags
47
+ * (the labels a merchant puts on a customer are staff knowledge and never
48
+ * reach the shopper).
49
+ * `account_status` (`pending` | `approved`) gates B2B storefront content
50
+ * when the store's approval policy is on (b2b-v1).
51
+ */
52
+ export interface StoreCustomer {
53
+ id: string
54
+ email: string | null
55
+ first_name: string | null
56
+ last_name: string | null
57
+ phone: string | null
58
+ company_name: string | null
59
+ /** Bulgarian company id (ЕИК) — feeds invoice-required checkout. */
60
+ company_eik: string | null
61
+ vat_number: string | null
62
+ has_account: boolean
63
+ /** `pending` | `approved` — store approval policy (customer-accounts card). */
64
+ account_status: string
65
+ metadata: Record<string, unknown> | null
66
+ created_at: IsoDateString
67
+ updated_at: IsoDateString
68
+ addresses: CustomerAddress[]
69
+ }
70
+
71
+ export interface CustomerResponse {
72
+ customer: StoreCustomer
73
+ }
74
+
75
+ export interface CreateCustomerInput {
76
+ /** Must equal the session JWT's email — mismatch → 403 email_mismatch. */
77
+ email: string
78
+ first_name?: string
79
+ last_name?: string
80
+ phone?: string
81
+ company_name?: string
82
+ }
83
+
84
+ /**
85
+ * POST /api/store/customers — guest-to-registered conversion (password
86
+ * sign-up flow): call `supabase.auth.signUp({email, password})` client-side
87
+ * first, then this with the same email. Upserts the (client_id, email)
88
+ * customer row with has_account=true. NOT needed for the passwordless code
89
+ * flow — verify already lazy-creates the customer.
90
+ *
91
+ * Auth: Bearer JWT (+ x-client-id).
92
+ * Errors: 401 unauthenticated · 403 email_mismatch (body email ≠ JWT email)
93
+ * · 400 validation_failed.
94
+ * Settings: store approval policy decides account_status of a NEW row.
95
+ */
96
+ export async function createCustomer(
97
+ client: StorefrontClient,
98
+ input: CreateCustomerInput
99
+ ): Promise<CustomerResponse> {
100
+ return client.post("/api/store/customers", input)
101
+ }
102
+
103
+ /**
104
+ * GET /api/store/customers/me — the signed-in customer, addresses embedded.
105
+ * Lazy-creates the customer row on first call for this (tenant, email).
106
+ *
107
+ * Auth: Bearer JWT (+ x-client-id).
108
+ * Errors: 401 unauthenticated · 400 missing_client_id.
109
+ */
110
+ export async function getMe(client: StorefrontClient): Promise<CustomerResponse> {
111
+ return client.get("/api/store/customers/me")
112
+ }
113
+
114
+ export interface UpdateCustomerInput {
115
+ first_name?: string | null
116
+ last_name?: string | null
117
+ phone?: string | null
118
+ company_name?: string | null
119
+ /** ЕИК / VAT (customer-master-data) — loose format, never shape-blocked. */
120
+ company_eik?: string | null
121
+ vat_number?: string | null
122
+ metadata?: Record<string, unknown>
123
+ }
124
+
125
+ /**
126
+ * POST /api/store/customers/me — update own profile. Unknown keys are
127
+ * stripped server-side; returns the refreshed customer.
128
+ *
129
+ * Auth: Bearer JWT (+ x-client-id).
130
+ * Errors: 401 unauthenticated · 400 validation_failed.
131
+ */
132
+ export async function updateMe(
133
+ client: StorefrontClient,
134
+ input: UpdateCustomerInput
135
+ ): Promise<CustomerResponse> {
136
+ return client.post("/api/store/customers/me", input)
137
+ }
138
+
139
+ export interface AddressListResponse {
140
+ addresses: CustomerAddress[]
141
+ /** Not truly paginated: offset is always 0 and limit === count. */
142
+ count: number
143
+ offset: number
144
+ limit: number
145
+ }
146
+
147
+ /**
148
+ * GET /api/store/customers/me/addresses — all of the customer's addresses
149
+ * (created_at asc). The list envelope is nominal — the route returns EVERY
150
+ * address (offset 0, limit = count); no query params are read.
151
+ *
152
+ * Auth: Bearer JWT (+ x-client-id). Errors: 401 unauthenticated.
153
+ */
154
+ export async function listAddresses(
155
+ client: StorefrontClient
156
+ ): Promise<AddressListResponse> {
157
+ return client.get("/api/store/customers/me/addresses")
158
+ }
159
+
160
+ export interface CreateAddressInput {
161
+ address_name?: string
162
+ first_name?: string
163
+ last_name?: string
164
+ company?: string
165
+ address_1?: string
166
+ address_2?: string
167
+ city?: string
168
+ /** Lower-cased server-side. */
169
+ country_code?: string
170
+ province?: string
171
+ postal_code?: string
172
+ phone?: string
173
+ /** true clears the flag on every sibling first (one default per kind). */
174
+ is_default_billing?: boolean
175
+ is_default_shipping?: boolean
176
+ metadata?: Record<string, unknown>
177
+ }
178
+
179
+ /**
180
+ * POST /api/store/customers/me/addresses — create an address. Returns the
181
+ * FULL refreshed customer (`{customer}`, addresses embedded) — not the
182
+ * created address alone; find it in `customer.addresses`.
183
+ *
184
+ * Auth: Bearer JWT (+ x-client-id).
185
+ * Errors: 401 unauthenticated · 400 validation_failed.
186
+ */
187
+ export async function createAddress(
188
+ client: StorefrontClient,
189
+ input: CreateAddressInput
190
+ ): Promise<CustomerResponse> {
191
+ return client.post("/api/store/customers/me/addresses", input)
192
+ }
193
+
194
+ export interface AddressResponse {
195
+ address: CustomerAddress
196
+ }
197
+
198
+ /**
199
+ * GET /api/store/customers/me/addresses/:id — one owned address.
200
+ *
201
+ * Auth: Bearer JWT (+ x-client-id).
202
+ * Errors: 401 unauthenticated · 404 not_found (unknown OR another
203
+ * customer's address — ownership 404s, it never 403s).
204
+ */
205
+ export async function getAddress(
206
+ client: StorefrontClient,
207
+ addressId: string
208
+ ): Promise<AddressResponse> {
209
+ return client.get(`/api/store/customers/me/addresses/${encodeURIComponent(addressId)}`)
210
+ }
211
+
212
+ export interface UpdateAddressInput {
213
+ address_name?: string | null
214
+ first_name?: string | null
215
+ last_name?: string | null
216
+ company?: string | null
217
+ address_1?: string | null
218
+ address_2?: string | null
219
+ city?: string | null
220
+ country_code?: string | null
221
+ province?: string | null
222
+ postal_code?: string | null
223
+ phone?: string | null
224
+ is_default_billing?: boolean
225
+ is_default_shipping?: boolean
226
+ metadata?: Record<string, unknown>
227
+ }
228
+
229
+ /**
230
+ * POST /api/store/customers/me/addresses/:id — partial update; default
231
+ * flags clear siblings. Returns the FULL refreshed customer.
232
+ *
233
+ * Auth: Bearer JWT (+ x-client-id).
234
+ * Errors: 401 unauthenticated · 404 not_found · 400 validation_failed.
235
+ */
236
+ export async function updateAddress(
237
+ client: StorefrontClient,
238
+ addressId: string,
239
+ input: UpdateAddressInput
240
+ ): Promise<CustomerResponse> {
241
+ return client.post(
242
+ `/api/store/customers/me/addresses/${encodeURIComponent(addressId)}`,
243
+ input
244
+ )
245
+ }
246
+
247
+ /**
248
+ * DELETE /api/store/customers/me/addresses/:id — soft-delete an owned
249
+ * address. Returns the FULL refreshed customer (the address gone from
250
+ * `customer.addresses`).
251
+ *
252
+ * Auth: Bearer JWT (+ x-client-id).
253
+ * Errors: 401 unauthenticated · 404 not_found.
254
+ */
255
+ export async function deleteAddress(
256
+ client: StorefrontClient,
257
+ addressId: string
258
+ ): Promise<CustomerResponse> {
259
+ return client.delete(
260
+ `/api/store/customers/me/addresses/${encodeURIComponent(addressId)}`
261
+ )
262
+ }
263
+
264
+ /** One issued (non-void) order document — the account "Invoices" row. */
265
+ export interface CustomerDocument {
266
+ id: string
267
+ order_id: string
268
+ order_display_id: number | null
269
+ doc_type: string
270
+ number: string
271
+ issued_at: IsoDateString
272
+ pdf_url: string | null
273
+ due_date: IsoDateString | null
274
+ paid_at: IsoDateString | null
275
+ }
276
+
277
+ export interface ListDocumentsQuery extends PaginationQuery {
278
+ /** Filter by document type (e.g. `invoice`). */
279
+ doc_type?: string
280
+ }
281
+
282
+ export interface DocumentListResponse {
283
+ documents: CustomerDocument[]
284
+ count: number
285
+ offset: number
286
+ limit: number
287
+ }
288
+
289
+ /**
290
+ * GET /api/store/customers/me/documents — the customer's issued order
291
+ * documents, newest first (issued_at desc). Voided documents excluded.
292
+ * Ownership is INNER-join enforced (session customer id AND client_id) —
293
+ * cross-customer reads are impossible. `limit` ≤ 200, default 20.
294
+ *
295
+ * Auth: Bearer JWT (+ x-client-id).
296
+ * Errors: 401 unauthenticated · 400 validation_failed.
297
+ */
298
+ export async function listMyDocuments(
299
+ client: StorefrontClient,
300
+ query?: ListDocumentsQuery
301
+ ): Promise<DocumentListResponse> {
302
+ return client.get("/api/store/customers/me/documents", { query: { ...query } })
303
+ }