@tanstack/create 0.65.0 → 0.66.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.
- package/CHANGELOG.md +64 -0
- package/dist/frameworks/react/add-ons/shopify/README.md +86 -0
- package/dist/frameworks/react/add-ons/shopify/assets/_dot_env.local.append +19 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/account-nav.tsx.ejs +41 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/add-to-cart-button.tsx +48 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-line-item.tsx +94 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-summary.tsx +111 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/empty-state.tsx +29 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/money.tsx +11 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/product-card.tsx +74 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/product-grid.tsx +24 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/shop-image.tsx +57 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/shop.css +58 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/variant-selector.tsx +79 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/hooks/use-cart.ts +276 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/hooks/use-customer.ts.ejs +22 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/integrations/shopify/header-cart.tsx +37 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/customer-queries.ts.ejs +228 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/format.ts +33 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/queries.ts +684 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.addresses.tsx.ejs +67 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.callback.tsx.ejs +45 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.index.tsx.ejs +70 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.login.tsx.ejs +59 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.logout.tsx.ejs +16 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.$id.tsx.ejs +126 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.tsx.ejs +50 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.tsx.ejs +34 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.cart.tsx +45 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.collections.$handle.tsx +66 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.index.tsx +36 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.pages.$handle.tsx +39 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.policies.$handle.tsx +30 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.products.$handle.tsx +106 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.search.tsx +75 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.tsx +78 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/cart.functions.ts +207 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/catalog.functions.ts +244 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/cookies.ts +29 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-client.ts.ejs +99 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-cookies.ts.ejs +49 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer.functions.ts.ejs +168 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/env.ts +89 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/oauth.ts.ejs +301 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/storefront-client.ts +101 -0
- package/dist/frameworks/react/add-ons/shopify/info.json +104 -0
- package/dist/frameworks/react/add-ons/shopify/package.json +6 -0
- package/dist/frameworks/react/add-ons/shopify/small-logo.svg +1 -0
- package/dist/frameworks/react/examples/shopify-storefront/README.md +39 -0
- package/dist/frameworks/react/examples/shopify-storefront/assets/src/components/FeaturedCollections.tsx +43 -0
- package/dist/frameworks/react/examples/shopify-storefront/assets/src/components/ShopHero.tsx +39 -0
- package/dist/frameworks/react/examples/shopify-storefront/assets/src/routes/index.tsx +65 -0
- package/dist/frameworks/react/examples/shopify-storefront/info.json +18 -0
- package/dist/frameworks/react/examples/shopify-storefront/package.json +3 -0
- package/dist/frameworks/react/project/base/src/components/Header.tsx.ejs +34 -34
- package/package.json +1 -1
- package/src/frameworks/react/add-ons/shopify/README.md +86 -0
- package/src/frameworks/react/add-ons/shopify/assets/_dot_env.local.append +19 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/account-nav.tsx.ejs +41 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/add-to-cart-button.tsx +48 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-line-item.tsx +94 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-summary.tsx +111 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/empty-state.tsx +29 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/money.tsx +11 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/product-card.tsx +74 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/product-grid.tsx +24 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/shop-image.tsx +57 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/shop.css +58 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/variant-selector.tsx +79 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/hooks/use-cart.ts +276 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/hooks/use-customer.ts.ejs +22 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/integrations/shopify/header-cart.tsx +37 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/customer-queries.ts.ejs +228 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/format.ts +33 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/queries.ts +684 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.addresses.tsx.ejs +67 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.callback.tsx.ejs +45 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.index.tsx.ejs +70 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.login.tsx.ejs +59 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.logout.tsx.ejs +16 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.$id.tsx.ejs +126 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.tsx.ejs +50 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.tsx.ejs +34 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.cart.tsx +45 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.collections.$handle.tsx +66 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.index.tsx +36 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.pages.$handle.tsx +39 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.policies.$handle.tsx +30 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.products.$handle.tsx +106 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.search.tsx +75 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.tsx +78 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/cart.functions.ts +207 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/catalog.functions.ts +244 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/cookies.ts +29 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-client.ts.ejs +99 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-cookies.ts.ejs +49 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer.functions.ts.ejs +168 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/env.ts +89 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/oauth.ts.ejs +301 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/storefront-client.ts +101 -0
- package/src/frameworks/react/add-ons/shopify/info.json +104 -0
- package/src/frameworks/react/add-ons/shopify/package.json +6 -0
- package/src/frameworks/react/add-ons/shopify/small-logo.svg +1 -0
- package/src/frameworks/react/examples/shopify-storefront/README.md +39 -0
- package/src/frameworks/react/examples/shopify-storefront/assets/src/components/FeaturedCollections.tsx +43 -0
- package/src/frameworks/react/examples/shopify-storefront/assets/src/components/ShopHero.tsx +39 -0
- package/src/frameworks/react/examples/shopify-storefront/assets/src/routes/index.tsx +65 -0
- package/src/frameworks/react/examples/shopify-storefront/info.json +18 -0
- package/src/frameworks/react/examples/shopify-storefront/package.json +3 -0
- package/src/frameworks/react/project/base/src/components/Header.tsx.ejs +34 -34
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe formatting helpers for Shopify Storefront API responses.
|
|
3
|
+
* Framework-free — no React, no provider context required.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function formatMoney(amount: string | number, currencyCode: string) {
|
|
7
|
+
return new Intl.NumberFormat(undefined, {
|
|
8
|
+
style: 'currency',
|
|
9
|
+
currency: currencyCode,
|
|
10
|
+
minimumFractionDigits: 0,
|
|
11
|
+
maximumFractionDigits: 0,
|
|
12
|
+
}).format(typeof amount === 'string' ? Number(amount) : amount)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type ShopifyImageOptions = {
|
|
16
|
+
width?: number
|
|
17
|
+
height?: number
|
|
18
|
+
format?: 'webp' | 'jpg' | 'png'
|
|
19
|
+
crop?: 'center' | 'top' | 'bottom' | 'left' | 'right'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Append Shopify CDN transform parameters to a product image URL.
|
|
24
|
+
* Shopify's CDN serves resized/reformatted versions automatically.
|
|
25
|
+
*/
|
|
26
|
+
export function shopifyImageUrl(url: string, opts: ShopifyImageOptions = {}) {
|
|
27
|
+
const u = new URL(url)
|
|
28
|
+
if (opts.width) u.searchParams.set('width', String(opts.width))
|
|
29
|
+
if (opts.height) u.searchParams.set('height', String(opts.height))
|
|
30
|
+
if (opts.format) u.searchParams.set('format', opts.format)
|
|
31
|
+
if (opts.crop) u.searchParams.set('crop', opts.crop)
|
|
32
|
+
return u.toString()
|
|
33
|
+
}
|
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Cart,
|
|
3
|
+
CartLine,
|
|
4
|
+
Collection,
|
|
5
|
+
Image as StorefrontImage,
|
|
6
|
+
MoneyV2,
|
|
7
|
+
Page,
|
|
8
|
+
Product,
|
|
9
|
+
ProductOption,
|
|
10
|
+
ProductSortKeys,
|
|
11
|
+
ProductVariant,
|
|
12
|
+
} from '@shopify/hydrogen-react/storefront-api-types'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* GraphQL queries for the Shopify Storefront API.
|
|
16
|
+
*
|
|
17
|
+
* Result types are hand-picked slices of `@shopify/hydrogen-react`
|
|
18
|
+
* Storefront API types. The `@shopify/hydrogen-react` import is type-only
|
|
19
|
+
* (zero runtime cost). When the query count grows, swap to
|
|
20
|
+
* `@shopify/hydrogen-codegen` and regenerate; consuming code won't change.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/* ─── Shop info ─────────────────────────────────────────────────────────── */
|
|
24
|
+
|
|
25
|
+
export const SHOP_QUERY = /* GraphQL */ `
|
|
26
|
+
query Shop {
|
|
27
|
+
shop {
|
|
28
|
+
name
|
|
29
|
+
description
|
|
30
|
+
primaryDomain {
|
|
31
|
+
url
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
export type ShopQueryResult = {
|
|
38
|
+
shop: {
|
|
39
|
+
name: string
|
|
40
|
+
description: string | null
|
|
41
|
+
primaryDomain: { url: string }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* ─── Product card fragment + product list ──────────────────────────────── */
|
|
46
|
+
|
|
47
|
+
const PRODUCT_CARD_FRAGMENT = /* GraphQL */ `
|
|
48
|
+
fragment ProductCard on Product {
|
|
49
|
+
id
|
|
50
|
+
handle
|
|
51
|
+
title
|
|
52
|
+
productType
|
|
53
|
+
tags
|
|
54
|
+
publishedAt
|
|
55
|
+
options {
|
|
56
|
+
name
|
|
57
|
+
values
|
|
58
|
+
}
|
|
59
|
+
featuredImage {
|
|
60
|
+
url
|
|
61
|
+
altText
|
|
62
|
+
width
|
|
63
|
+
height
|
|
64
|
+
}
|
|
65
|
+
variants(first: 1) {
|
|
66
|
+
nodes {
|
|
67
|
+
id
|
|
68
|
+
availableForSale
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
priceRange {
|
|
72
|
+
minVariantPrice {
|
|
73
|
+
amount
|
|
74
|
+
currencyCode
|
|
75
|
+
}
|
|
76
|
+
maxVariantPrice {
|
|
77
|
+
amount
|
|
78
|
+
currencyCode
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
compareAtPriceRange {
|
|
82
|
+
minVariantPrice {
|
|
83
|
+
amount
|
|
84
|
+
currencyCode
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
|
|
90
|
+
export const PRODUCTS_QUERY = /* GraphQL */ `
|
|
91
|
+
${PRODUCT_CARD_FRAGMENT}
|
|
92
|
+
query Products(
|
|
93
|
+
$first: Int!
|
|
94
|
+
$after: String
|
|
95
|
+
$sortKey: ProductSortKeys
|
|
96
|
+
$reverse: Boolean
|
|
97
|
+
) {
|
|
98
|
+
products(
|
|
99
|
+
first: $first
|
|
100
|
+
after: $after
|
|
101
|
+
sortKey: $sortKey
|
|
102
|
+
reverse: $reverse
|
|
103
|
+
) {
|
|
104
|
+
pageInfo {
|
|
105
|
+
hasNextPage
|
|
106
|
+
endCursor
|
|
107
|
+
}
|
|
108
|
+
nodes {
|
|
109
|
+
...ProductCard
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
`
|
|
114
|
+
|
|
115
|
+
type CardImage = Pick<
|
|
116
|
+
StorefrontImage,
|
|
117
|
+
'url' | 'altText' | 'width' | 'height'
|
|
118
|
+
> | null
|
|
119
|
+
|
|
120
|
+
export type ProductListItem = Pick<
|
|
121
|
+
Product,
|
|
122
|
+
'id' | 'handle' | 'title' | 'productType' | 'tags' | 'publishedAt'
|
|
123
|
+
> & {
|
|
124
|
+
options: Array<Pick<ProductOption, 'name' | 'values'>>
|
|
125
|
+
featuredImage: CardImage
|
|
126
|
+
variants: {
|
|
127
|
+
nodes: Array<{ id: string; availableForSale: boolean }>
|
|
128
|
+
}
|
|
129
|
+
priceRange: {
|
|
130
|
+
minVariantPrice: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
131
|
+
maxVariantPrice: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
132
|
+
}
|
|
133
|
+
compareAtPriceRange: {
|
|
134
|
+
minVariantPrice: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type ProductListPage = {
|
|
139
|
+
nodes: Array<ProductListItem>
|
|
140
|
+
pageInfo: { hasNextPage: boolean; endCursor: string | null }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type ProductsQueryVariables = {
|
|
144
|
+
first: number
|
|
145
|
+
after?: string | null
|
|
146
|
+
sortKey?: ProductSortKeys | null
|
|
147
|
+
reverse?: boolean | null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type ProductsQueryResult = {
|
|
151
|
+
products: ProductListPage
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ─── Single product (PDP) ──────────────────────────────────────────────── */
|
|
155
|
+
|
|
156
|
+
export const PRODUCT_QUERY = /* GraphQL */ `
|
|
157
|
+
query Product($handle: String!) {
|
|
158
|
+
product(handle: $handle) {
|
|
159
|
+
id
|
|
160
|
+
handle
|
|
161
|
+
title
|
|
162
|
+
descriptionHtml
|
|
163
|
+
options {
|
|
164
|
+
id
|
|
165
|
+
name
|
|
166
|
+
values
|
|
167
|
+
}
|
|
168
|
+
images(first: 10) {
|
|
169
|
+
nodes {
|
|
170
|
+
url
|
|
171
|
+
altText
|
|
172
|
+
width
|
|
173
|
+
height
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
variants(first: 100) {
|
|
177
|
+
nodes {
|
|
178
|
+
id
|
|
179
|
+
title
|
|
180
|
+
availableForSale
|
|
181
|
+
selectedOptions {
|
|
182
|
+
name
|
|
183
|
+
value
|
|
184
|
+
}
|
|
185
|
+
price {
|
|
186
|
+
amount
|
|
187
|
+
currencyCode
|
|
188
|
+
}
|
|
189
|
+
image {
|
|
190
|
+
url
|
|
191
|
+
altText
|
|
192
|
+
width
|
|
193
|
+
height
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
seo {
|
|
198
|
+
title
|
|
199
|
+
description
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
`
|
|
204
|
+
|
|
205
|
+
export type ProductDetailVariant = Pick<
|
|
206
|
+
ProductVariant,
|
|
207
|
+
'id' | 'title' | 'availableForSale'
|
|
208
|
+
> & {
|
|
209
|
+
selectedOptions: Array<{ name: string; value: string }>
|
|
210
|
+
price: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
211
|
+
image: Pick<StorefrontImage, 'url' | 'altText' | 'width' | 'height'> | null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export type ProductDetail = Pick<
|
|
215
|
+
Product,
|
|
216
|
+
'id' | 'handle' | 'title' | 'descriptionHtml'
|
|
217
|
+
> & {
|
|
218
|
+
options: Array<Pick<ProductOption, 'id' | 'name' | 'values'>>
|
|
219
|
+
images: {
|
|
220
|
+
nodes: Array<Pick<StorefrontImage, 'url' | 'altText' | 'width' | 'height'>>
|
|
221
|
+
}
|
|
222
|
+
variants: { nodes: Array<ProductDetailVariant> }
|
|
223
|
+
seo: { title: string | null; description: string | null }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type ProductQueryResult = {
|
|
227
|
+
product: ProductDetail | null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* ─── Collections list ──────────────────────────────────────────────────── */
|
|
231
|
+
|
|
232
|
+
export const COLLECTIONS_QUERY = /* GraphQL */ `
|
|
233
|
+
query Collections($first: Int!) {
|
|
234
|
+
collections(first: $first, sortKey: TITLE) {
|
|
235
|
+
nodes {
|
|
236
|
+
id
|
|
237
|
+
handle
|
|
238
|
+
title
|
|
239
|
+
description
|
|
240
|
+
image {
|
|
241
|
+
url
|
|
242
|
+
altText
|
|
243
|
+
width
|
|
244
|
+
height
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
`
|
|
250
|
+
|
|
251
|
+
export type CollectionListItem = Pick<
|
|
252
|
+
Collection,
|
|
253
|
+
'id' | 'handle' | 'title' | 'description'
|
|
254
|
+
> & {
|
|
255
|
+
image: Pick<StorefrontImage, 'url' | 'altText' | 'width' | 'height'> | null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export type CollectionsQueryResult = {
|
|
259
|
+
collections: { nodes: Array<CollectionListItem> }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* ─── Collection by handle ──────────────────────────────────────────────── */
|
|
263
|
+
|
|
264
|
+
export const COLLECTION_QUERY = /* GraphQL */ `
|
|
265
|
+
${PRODUCT_CARD_FRAGMENT}
|
|
266
|
+
query Collection(
|
|
267
|
+
$handle: String!
|
|
268
|
+
$first: Int!
|
|
269
|
+
$after: String
|
|
270
|
+
$sortKey: ProductCollectionSortKeys
|
|
271
|
+
$reverse: Boolean
|
|
272
|
+
) {
|
|
273
|
+
collection(handle: $handle) {
|
|
274
|
+
id
|
|
275
|
+
handle
|
|
276
|
+
title
|
|
277
|
+
description
|
|
278
|
+
descriptionHtml
|
|
279
|
+
image {
|
|
280
|
+
url
|
|
281
|
+
altText
|
|
282
|
+
width
|
|
283
|
+
height
|
|
284
|
+
}
|
|
285
|
+
seo {
|
|
286
|
+
title
|
|
287
|
+
description
|
|
288
|
+
}
|
|
289
|
+
products(
|
|
290
|
+
first: $first
|
|
291
|
+
after: $after
|
|
292
|
+
sortKey: $sortKey
|
|
293
|
+
reverse: $reverse
|
|
294
|
+
) {
|
|
295
|
+
pageInfo {
|
|
296
|
+
hasNextPage
|
|
297
|
+
endCursor
|
|
298
|
+
}
|
|
299
|
+
nodes {
|
|
300
|
+
...ProductCard
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
`
|
|
306
|
+
|
|
307
|
+
export type CollectionDetail = Pick<
|
|
308
|
+
Collection,
|
|
309
|
+
'id' | 'handle' | 'title' | 'description' | 'descriptionHtml'
|
|
310
|
+
> & {
|
|
311
|
+
image: Pick<StorefrontImage, 'url' | 'altText' | 'width' | 'height'> | null
|
|
312
|
+
seo: { title: string | null; description: string | null }
|
|
313
|
+
products: ProductListPage
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export type CollectionQueryResult = {
|
|
317
|
+
collection: CollectionDetail | null
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* ─── Cart fragment + queries + mutations ───────────────────────────────── */
|
|
321
|
+
|
|
322
|
+
const CART_FRAGMENT = /* GraphQL */ `
|
|
323
|
+
fragment CartFields on Cart {
|
|
324
|
+
id
|
|
325
|
+
checkoutUrl
|
|
326
|
+
totalQuantity
|
|
327
|
+
cost {
|
|
328
|
+
totalAmount {
|
|
329
|
+
amount
|
|
330
|
+
currencyCode
|
|
331
|
+
}
|
|
332
|
+
subtotalAmount {
|
|
333
|
+
amount
|
|
334
|
+
currencyCode
|
|
335
|
+
}
|
|
336
|
+
totalTaxAmount {
|
|
337
|
+
amount
|
|
338
|
+
currencyCode
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
lines(first: 100) {
|
|
342
|
+
nodes {
|
|
343
|
+
id
|
|
344
|
+
quantity
|
|
345
|
+
merchandise {
|
|
346
|
+
... on ProductVariant {
|
|
347
|
+
id
|
|
348
|
+
title
|
|
349
|
+
availableForSale
|
|
350
|
+
selectedOptions {
|
|
351
|
+
name
|
|
352
|
+
value
|
|
353
|
+
}
|
|
354
|
+
price {
|
|
355
|
+
amount
|
|
356
|
+
currencyCode
|
|
357
|
+
}
|
|
358
|
+
image {
|
|
359
|
+
url
|
|
360
|
+
altText
|
|
361
|
+
width
|
|
362
|
+
height
|
|
363
|
+
}
|
|
364
|
+
product {
|
|
365
|
+
handle
|
|
366
|
+
title
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
cost {
|
|
371
|
+
totalAmount {
|
|
372
|
+
amount
|
|
373
|
+
currencyCode
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
discountCodes {
|
|
379
|
+
code
|
|
380
|
+
applicable
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
`
|
|
384
|
+
|
|
385
|
+
export const CART_QUERY = /* GraphQL */ `
|
|
386
|
+
${CART_FRAGMENT}
|
|
387
|
+
query Cart($cartId: ID!) {
|
|
388
|
+
cart(id: $cartId) {
|
|
389
|
+
...CartFields
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
`
|
|
393
|
+
|
|
394
|
+
export const CART_CREATE_MUTATION = /* GraphQL */ `
|
|
395
|
+
${CART_FRAGMENT}
|
|
396
|
+
mutation CartCreate($input: CartInput!) {
|
|
397
|
+
cartCreate(input: $input) {
|
|
398
|
+
cart {
|
|
399
|
+
...CartFields
|
|
400
|
+
}
|
|
401
|
+
userErrors {
|
|
402
|
+
field
|
|
403
|
+
message
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
`
|
|
408
|
+
|
|
409
|
+
export const CART_LINES_ADD_MUTATION = /* GraphQL */ `
|
|
410
|
+
${CART_FRAGMENT}
|
|
411
|
+
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
|
412
|
+
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
|
413
|
+
cart {
|
|
414
|
+
...CartFields
|
|
415
|
+
}
|
|
416
|
+
userErrors {
|
|
417
|
+
field
|
|
418
|
+
message
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
`
|
|
423
|
+
|
|
424
|
+
export const CART_LINES_UPDATE_MUTATION = /* GraphQL */ `
|
|
425
|
+
${CART_FRAGMENT}
|
|
426
|
+
mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
|
|
427
|
+
cartLinesUpdate(cartId: $cartId, lines: $lines) {
|
|
428
|
+
cart {
|
|
429
|
+
...CartFields
|
|
430
|
+
}
|
|
431
|
+
userErrors {
|
|
432
|
+
field
|
|
433
|
+
message
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
`
|
|
438
|
+
|
|
439
|
+
export const CART_LINES_REMOVE_MUTATION = /* GraphQL */ `
|
|
440
|
+
${CART_FRAGMENT}
|
|
441
|
+
mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
|
|
442
|
+
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
|
|
443
|
+
cart {
|
|
444
|
+
...CartFields
|
|
445
|
+
}
|
|
446
|
+
userErrors {
|
|
447
|
+
field
|
|
448
|
+
message
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
`
|
|
453
|
+
|
|
454
|
+
export const CART_DISCOUNT_CODES_UPDATE_MUTATION = /* GraphQL */ `
|
|
455
|
+
${CART_FRAGMENT}
|
|
456
|
+
mutation CartDiscountCodesUpdate($cartId: ID!, $discountCodes: [String!]) {
|
|
457
|
+
cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
|
|
458
|
+
cart {
|
|
459
|
+
...CartFields
|
|
460
|
+
}
|
|
461
|
+
userErrors {
|
|
462
|
+
field
|
|
463
|
+
message
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
`
|
|
468
|
+
|
|
469
|
+
export type CartLineMerchandise = Pick<
|
|
470
|
+
ProductVariant,
|
|
471
|
+
'id' | 'title' | 'availableForSale'
|
|
472
|
+
> & {
|
|
473
|
+
selectedOptions: Array<{ name: string; value: string }>
|
|
474
|
+
price: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
475
|
+
image: Pick<StorefrontImage, 'url' | 'altText' | 'width' | 'height'> | null
|
|
476
|
+
product: Pick<Product, 'handle' | 'title'>
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export type CartLineDetail = Pick<CartLine, 'id' | 'quantity'> & {
|
|
480
|
+
merchandise: CartLineMerchandise
|
|
481
|
+
cost: {
|
|
482
|
+
totalAmount: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export type CartDetail = Pick<Cart, 'id' | 'checkoutUrl' | 'totalQuantity'> & {
|
|
487
|
+
cost: {
|
|
488
|
+
totalAmount: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
489
|
+
subtotalAmount: Pick<MoneyV2, 'amount' | 'currencyCode'>
|
|
490
|
+
totalTaxAmount: Pick<MoneyV2, 'amount' | 'currencyCode'> | null
|
|
491
|
+
}
|
|
492
|
+
lines: { nodes: Array<CartLineDetail> }
|
|
493
|
+
discountCodes: Array<{ code: string; applicable: boolean }>
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export type CartQueryResult = { cart: CartDetail | null }
|
|
497
|
+
export type CartUserError = { field: string[] | null; message: string }
|
|
498
|
+
|
|
499
|
+
type CartMutationResult<TName extends string> = {
|
|
500
|
+
[K in TName]: { cart: CartDetail | null; userErrors: Array<CartUserError> }
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export type CartCreateResult = CartMutationResult<'cartCreate'>
|
|
504
|
+
export type CartLinesAddResult = CartMutationResult<'cartLinesAdd'>
|
|
505
|
+
export type CartLinesUpdateResult = CartMutationResult<'cartLinesUpdate'>
|
|
506
|
+
export type CartLinesRemoveResult = CartMutationResult<'cartLinesRemove'>
|
|
507
|
+
export type CartDiscountCodesUpdateResult =
|
|
508
|
+
CartMutationResult<'cartDiscountCodesUpdate'>
|
|
509
|
+
|
|
510
|
+
/* ─── Sort options ──────────────────────────────────────────────────────── */
|
|
511
|
+
|
|
512
|
+
export const SORT_OPTIONS = [
|
|
513
|
+
{ key: 'BEST_SELLING', reverse: false, label: 'Best selling' },
|
|
514
|
+
{ key: 'CREATED_AT', reverse: true, label: 'Newest' },
|
|
515
|
+
{ key: 'PRICE', reverse: false, label: 'Price: low to high' },
|
|
516
|
+
{ key: 'PRICE', reverse: true, label: 'Price: high to low' },
|
|
517
|
+
{ key: 'TITLE', reverse: false, label: 'Title: A–Z' },
|
|
518
|
+
] as const satisfies ReadonlyArray<{
|
|
519
|
+
key: ProductSortKeys
|
|
520
|
+
reverse: boolean
|
|
521
|
+
label: string
|
|
522
|
+
}>
|
|
523
|
+
|
|
524
|
+
export type SortOption = (typeof SORT_OPTIONS)[number]
|
|
525
|
+
export type SortOptionId = `${SortOption['key']}${'' | ':rev'}`
|
|
526
|
+
|
|
527
|
+
export function sortOptionId(opt: SortOption): SortOptionId {
|
|
528
|
+
return (opt.reverse ? `${opt.key}:rev` : opt.key) as SortOptionId
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function resolveSortOption(id: string | undefined): SortOption {
|
|
532
|
+
if (!id) return SORT_OPTIONS[0]
|
|
533
|
+
for (const opt of SORT_OPTIONS) {
|
|
534
|
+
if (sortOptionId(opt) === id) return opt
|
|
535
|
+
}
|
|
536
|
+
return SORT_OPTIONS[0]
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export const COLLECTION_SORT_OPTIONS = [
|
|
540
|
+
{ key: 'COLLECTION_DEFAULT', reverse: false, label: 'Featured' },
|
|
541
|
+
{ key: 'BEST_SELLING', reverse: false, label: 'Best selling' },
|
|
542
|
+
{ key: 'CREATED', reverse: true, label: 'Newest' },
|
|
543
|
+
{ key: 'PRICE', reverse: false, label: 'Price: low to high' },
|
|
544
|
+
{ key: 'PRICE', reverse: true, label: 'Price: high to low' },
|
|
545
|
+
{ key: 'TITLE', reverse: false, label: 'Title: A–Z' },
|
|
546
|
+
] as const satisfies ReadonlyArray<{
|
|
547
|
+
key: string
|
|
548
|
+
reverse: boolean
|
|
549
|
+
label: string
|
|
550
|
+
}>
|
|
551
|
+
|
|
552
|
+
export type CollectionSortOption = (typeof COLLECTION_SORT_OPTIONS)[number]
|
|
553
|
+
|
|
554
|
+
export function resolveCollectionSortOption(
|
|
555
|
+
id: string | undefined,
|
|
556
|
+
): CollectionSortOption {
|
|
557
|
+
if (!id) return COLLECTION_SORT_OPTIONS[0]
|
|
558
|
+
const expected = (opt: CollectionSortOption) =>
|
|
559
|
+
opt.reverse ? `${opt.key}:rev` : opt.key
|
|
560
|
+
for (const opt of COLLECTION_SORT_OPTIONS) {
|
|
561
|
+
if (expected(opt) === id) return opt
|
|
562
|
+
}
|
|
563
|
+
return COLLECTION_SORT_OPTIONS[0]
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* ─── Pages + policies ──────────────────────────────────────────────────── */
|
|
567
|
+
|
|
568
|
+
export const PAGE_QUERY = /* GraphQL */ `
|
|
569
|
+
query Page($handle: String!) {
|
|
570
|
+
page(handle: $handle) {
|
|
571
|
+
id
|
|
572
|
+
handle
|
|
573
|
+
title
|
|
574
|
+
body
|
|
575
|
+
bodySummary
|
|
576
|
+
seo {
|
|
577
|
+
title
|
|
578
|
+
description
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
`
|
|
583
|
+
|
|
584
|
+
export type PageDetail = Pick<
|
|
585
|
+
Page,
|
|
586
|
+
'id' | 'handle' | 'title' | 'body' | 'bodySummary'
|
|
587
|
+
> & {
|
|
588
|
+
seo: { title: string | null; description: string | null }
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export type PageQueryResult = { page: PageDetail | null }
|
|
592
|
+
|
|
593
|
+
export const SHOP_POLICIES_QUERY = /* GraphQL */ `
|
|
594
|
+
query ShopPolicies {
|
|
595
|
+
shop {
|
|
596
|
+
privacyPolicy {
|
|
597
|
+
handle
|
|
598
|
+
title
|
|
599
|
+
body
|
|
600
|
+
}
|
|
601
|
+
refundPolicy {
|
|
602
|
+
handle
|
|
603
|
+
title
|
|
604
|
+
body
|
|
605
|
+
}
|
|
606
|
+
termsOfService {
|
|
607
|
+
handle
|
|
608
|
+
title
|
|
609
|
+
body
|
|
610
|
+
}
|
|
611
|
+
shippingPolicy {
|
|
612
|
+
handle
|
|
613
|
+
title
|
|
614
|
+
body
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
`
|
|
619
|
+
|
|
620
|
+
export type ShopPolicy = {
|
|
621
|
+
handle: string
|
|
622
|
+
title: string
|
|
623
|
+
body: string
|
|
624
|
+
} | null
|
|
625
|
+
|
|
626
|
+
export type ShopPoliciesQueryResult = {
|
|
627
|
+
shop: {
|
|
628
|
+
privacyPolicy: ShopPolicy
|
|
629
|
+
refundPolicy: ShopPolicy
|
|
630
|
+
termsOfService: ShopPolicy
|
|
631
|
+
shippingPolicy: ShopPolicy
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export type PolicySummary = { handle: string; title: string }
|
|
636
|
+
|
|
637
|
+
export function flattenPolicies(
|
|
638
|
+
shop: ShopPoliciesQueryResult['shop'],
|
|
639
|
+
): Array<PolicySummary> {
|
|
640
|
+
const keys = [
|
|
641
|
+
'shippingPolicy',
|
|
642
|
+
'refundPolicy',
|
|
643
|
+
'privacyPolicy',
|
|
644
|
+
'termsOfService',
|
|
645
|
+
] as const
|
|
646
|
+
return keys.flatMap((k) => {
|
|
647
|
+
const p = shop[k]
|
|
648
|
+
return p ? [{ handle: p.handle, title: p.title }] : []
|
|
649
|
+
})
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/* ─── Search ────────────────────────────────────────────────────────────── */
|
|
653
|
+
|
|
654
|
+
export const SEARCH_QUERY = /* GraphQL */ `
|
|
655
|
+
${PRODUCT_CARD_FRAGMENT}
|
|
656
|
+
query Search($query: String!, $first: Int!, $after: String) {
|
|
657
|
+
search(
|
|
658
|
+
query: $query
|
|
659
|
+
first: $first
|
|
660
|
+
after: $after
|
|
661
|
+
types: [PRODUCT]
|
|
662
|
+
productFilters: [{ available: true }]
|
|
663
|
+
) {
|
|
664
|
+
totalCount
|
|
665
|
+
pageInfo {
|
|
666
|
+
hasNextPage
|
|
667
|
+
endCursor
|
|
668
|
+
}
|
|
669
|
+
nodes {
|
|
670
|
+
... on Product {
|
|
671
|
+
...ProductCard
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
`
|
|
677
|
+
|
|
678
|
+
export type SearchQueryResult = {
|
|
679
|
+
search: {
|
|
680
|
+
totalCount: number
|
|
681
|
+
pageInfo: { hasNextPage: boolean; endCursor: string | null }
|
|
682
|
+
nodes: Array<ProductListItem>
|
|
683
|
+
}
|
|
684
|
+
}
|