@doswiftly/cli 0.1.17 → 0.1.19
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/README.md +23 -323
- package/dist/commands/check.js +1 -1
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +43 -20
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +4 -4
- package/dist/commands/sdk.js +5 -5
- package/dist/commands/sdk.js.map +1 -1
- package/dist/commands/template.js +4 -4
- package/dist/commands/template.js.map +1 -1
- package/dist/commands/types.js +5 -5
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/verify.js +2 -2
- package/dist/commands/verify.js.map +1 -1
- package/dist/lib/package-manager.d.ts +1 -1
- package/dist/lib/package-manager.js +1 -1
- package/package.json +1 -1
- package/templates/storefront-minimal/wrangler.toml +4 -0
- package/templates/storefront-nextjs/README.md +16 -12
- package/templates/storefront-nextjs/app/account/orders/page.tsx +2 -2
- package/templates/storefront-nextjs/app/account/page.tsx +2 -2
- package/templates/storefront-nextjs/app/auth/login/page.tsx +1 -1
- package/templates/storefront-nextjs/app/auth/register/page.tsx +1 -1
- package/templates/storefront-nextjs/app/cart/page.tsx +1 -1
- package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +2 -2
- package/templates/storefront-nextjs/app/categories/page.tsx +1 -1
- package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +1 -1
- package/templates/storefront-nextjs/app/collections/page.tsx +1 -1
- package/templates/storefront-nextjs/app/page.tsx +1 -1
- package/templates/storefront-nextjs/app/products/[slug]/page.tsx +1 -1
- package/templates/storefront-nextjs/app/products/page.tsx +2 -2
- package/templates/storefront-nextjs/app/search/page.tsx +1 -1
- package/templates/storefront-nextjs/components/auth/auth-guard.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +2 -2
- package/templates/storefront-nextjs/components/commerce/product-filters.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/product-price.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/search-input.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/sort-select.tsx +1 -1
- package/templates/storefront-nextjs/components/providers.tsx +1 -1
- package/templates/storefront-nextjs/lib/currency.tsx +3 -3
- package/templates/storefront-nextjs/lib/format.ts +1 -1
- package/templates/storefront-nextjs/lib/graphql-queries.ts +3 -3
- package/templates/storefront-nextjs/package.dev.json +1 -1
- package/templates/storefront-nextjs/package.json +1 -1
- package/templates/storefront-nextjs/package.json.template +1 -1
- package/templates/storefront-nextjs/wrangler.toml +4 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +47 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +47 -0
- package/templates/storefront-nextjs-shadcn/CLAUDE.md +148 -35
- package/templates/storefront-nextjs-shadcn/README.md +29 -162
- package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +98 -91
- package/templates/storefront-nextjs-shadcn/app/account/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/account/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +53 -162
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/loading.tsx +60 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +36 -47
- package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +46 -29
- package/templates/storefront-nextjs-shadcn/app/account/page.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +108 -71
- package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +2 -86
- package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +2 -124
- package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +10 -5
- package/templates/storefront-nextjs-shadcn/app/blog/[slug]/loading.tsx +17 -0
- package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +43 -2
- package/templates/storefront-nextjs-shadcn/app/blog/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +2 -1
- package/templates/storefront-nextjs-shadcn/app/cart/loading.tsx +26 -0
- package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +6 -3
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/category-products-client.tsx +56 -0
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/loading.tsx +32 -0
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +76 -59
- package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +8 -4
- package/templates/storefront-nextjs-shadcn/app/checkout/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/loading.tsx +31 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +116 -79
- package/templates/storefront-nextjs-shadcn/app/collections/[handle]/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/collections/loading.tsx +18 -0
- package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/app/global-error.tsx +117 -0
- package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
- package/templates/storefront-nextjs-shadcn/app/layout.tsx +46 -11
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/loading.tsx +29 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +6 -6
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +15 -61
- package/templates/storefront-nextjs-shadcn/app/products/loading.tsx +32 -0
- package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +405 -151
- package/templates/storefront-nextjs-shadcn/app/search/loading.tsx +18 -0
- package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
- package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +26 -24
- package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
- package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +9 -9
- package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +11 -37
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +37 -23
- package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +4 -3
- package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +22 -7
- package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
- package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +3 -3
- package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +2 -1
- package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
- package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +2 -12
- package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
- package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
- package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +4 -4
- package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
- package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +33 -23
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +2 -11
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +8 -25
- package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +10 -19
- package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +17 -41
- package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +2 -29
- package/templates/storefront-nextjs-shadcn/components/order/index.ts +6 -1
- package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +69 -0
- package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +84 -0
- package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +138 -0
- package/templates/storefront-nextjs-shadcn/components/product/index.ts +9 -2
- package/templates/storefront-nextjs-shadcn/components/product/product-card.fragment.graphql +49 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +3 -31
- package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +176 -123
- package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
- package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +5 -4
- package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +19 -7
- package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +8 -23
- package/templates/storefront-nextjs-shadcn/components/product/product-variant.fragment.graphql +51 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +1 -7
- package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +17 -2
- package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +3 -2
- package/templates/storefront-nextjs-shadcn/components/providers/index.ts +1 -1
- package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +30 -0
- package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/returns/index.ts +2 -2
- package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +3 -2
- package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
- package/templates/storefront-nextjs-shadcn/components/ui/form.tsx +174 -0
- package/templates/storefront-nextjs-shadcn/components/ui/index.ts +30 -2
- package/templates/storefront-nextjs-shadcn/components/ui/progress.tsx +40 -0
- package/templates/storefront-nextjs-shadcn/components/ui/sheet.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/components/ui/slider.tsx +33 -0
- package/templates/storefront-nextjs-shadcn/components/ui/textarea.tsx +24 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/generated/graphql.ts +12779 -0
- package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
- package/templates/storefront-nextjs-shadcn/hooks/index.ts +2 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +42 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +17 -295
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +51 -19
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +13 -9
- package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +4 -17
- package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +22 -99
- package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +32 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +687 -632
- package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +86 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +131 -182
- package/templates/storefront-nextjs-shadcn/lib/graphql/types.ts +62 -0
- package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +0 -17
- package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
- package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
- package/templates/storefront-nextjs-shadcn/package.json +12 -13
- package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
- package/templates/storefront-nextjs-shadcn/proxy.ts +3 -4
- package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +41 -39
- package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +64 -75
- package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +178 -177
- package/templates/storefront-nextjs-shadcn/tsconfig.json +23 -5
- package/templates/storefront-nextjs-shadcn/wrangler.toml +4 -0
- package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +0 -282
- package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +0 -190
- package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +0 -263
- package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +0 -135
- package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +0 -142
- package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +0 -448
- package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +0 -307
- package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +0 -245
- package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +0 -103
- package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +0 -168
- package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +0 -160
- package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +0 -220
- package/templates/storefront-nextjs-shadcn/lib/config.ts +0 -46
- package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +0 -254
- package/templates/storefront-nextjs-shadcn/lib/currency/README.md +0 -464
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +0 -328
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +0 -295
- package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +0 -27
- package/templates/storefront-nextjs-shadcn/lib/format.ts +0 -226
- package/templates/storefront-nextjs-shadcn/lib/hooks.ts +0 -30
- package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +0 -66
- package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +0 -103
|
@@ -1,45 +1,62 @@
|
|
|
1
|
-
import { CodegenConfig } from '@graphql-codegen/cli';
|
|
2
|
-
import { loadEnvConfig } from '@next/env';
|
|
3
|
-
|
|
4
|
-
// Load environment variables from .env.local
|
|
5
|
-
loadEnvConfig(process.cwd());
|
|
6
|
-
|
|
7
|
-
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
|
8
|
-
const shopSlug = process.env.NEXT_PUBLIC_SHOP_SLUG || 'test-shop';
|
|
1
|
+
import type { CodegenConfig } from '@graphql-codegen/cli';
|
|
9
2
|
|
|
3
|
+
/**
|
|
4
|
+
* GraphQL Code Generator Configuration
|
|
5
|
+
*
|
|
6
|
+
* Uses `client-preset` with `documentMode: 'string'` (TypedDocumentString).
|
|
7
|
+
* This generates lightweight string-based documents instead of parsed AST objects,
|
|
8
|
+
* eliminating the need for the `graphql` package at runtime.
|
|
9
|
+
*
|
|
10
|
+
* Schema and operations come from @doswiftly/storefront-operations (SSOT).
|
|
11
|
+
* No running backend needed — schema is synced as a file.
|
|
12
|
+
*
|
|
13
|
+
* Architecture decision: https://github.com/dotansimha/graphql-typed-document-node
|
|
14
|
+
* Inspired by: Saleor Storefront (client-preset + TypedDocumentString)
|
|
15
|
+
*
|
|
16
|
+
* To update schema after backend changes:
|
|
17
|
+
* cd packages/@doswiftly/storefront-operations && pnpm run sync
|
|
18
|
+
*/
|
|
10
19
|
const config: CodegenConfig = {
|
|
11
|
-
// Schema from
|
|
12
|
-
schema:
|
|
13
|
-
{
|
|
14
|
-
[`${apiUrl}/storefront/graphql`]: {
|
|
15
|
-
headers: {
|
|
16
|
-
'X-Shop-Slug': shopSlug,
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
],
|
|
20
|
+
// Schema from storefront-operations package (no introspection needed!)
|
|
21
|
+
schema: 'node_modules/@doswiftly/storefront-operations/schema.graphql',
|
|
21
22
|
|
|
22
|
-
// Operations: Backend SSOT + Developer Custom Queries
|
|
23
|
+
// Operations: Backend SSOT + Developer Custom Queries + Colocated Component Fragments
|
|
23
24
|
documents: [
|
|
24
|
-
'node_modules/@doswiftly/storefront-operations/**/*.graphql',
|
|
25
|
-
'graphql/**/*.
|
|
25
|
+
'node_modules/@doswiftly/storefront-operations/**/*.graphql',
|
|
26
|
+
'graphql/**/*.graphql',
|
|
27
|
+
'components/**/*.fragment.graphql',
|
|
26
28
|
],
|
|
27
29
|
|
|
28
|
-
// Output configuration
|
|
29
30
|
generates: {
|
|
30
|
-
'generated/
|
|
31
|
-
|
|
32
|
-
'typescript', // Generate TS types
|
|
33
|
-
'typescript-operations', // Generate operation types
|
|
34
|
-
'typed-document-node', // Generate TypedDocumentNode
|
|
35
|
-
],
|
|
31
|
+
'generated/': {
|
|
32
|
+
preset: 'client',
|
|
36
33
|
config: {
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
// TypedDocumentString: lightweight string wrapper instead of parsed AST
|
|
35
|
+
// Eliminates `graphql` package from runtime bundle (~40kb savings)
|
|
36
|
+
documentMode: 'string',
|
|
37
|
+
|
|
38
|
+
// Scalar mappings
|
|
39
39
|
scalars: {
|
|
40
40
|
DateTime: 'string',
|
|
41
|
-
|
|
41
|
+
Decimal: 'string',
|
|
42
|
+
JSON: 'Record<string, unknown>',
|
|
42
43
|
},
|
|
44
|
+
|
|
45
|
+
// Keep __typename for connection normalization and cache keys
|
|
46
|
+
skipTypename: false,
|
|
47
|
+
|
|
48
|
+
// Readable enum types (union of string literals)
|
|
49
|
+
enumsAsTypes: true,
|
|
50
|
+
|
|
51
|
+
// Deduplicate fragments shared across queries
|
|
52
|
+
dedupeFragments: true,
|
|
53
|
+
|
|
54
|
+
// Use `import type` for cleaner output
|
|
55
|
+
useTypeImports: true,
|
|
56
|
+
},
|
|
57
|
+
presetConfig: {
|
|
58
|
+
// Disable fragment masking — simpler DX, developer sees all fields
|
|
59
|
+
fragmentMasking: false,
|
|
43
60
|
},
|
|
44
61
|
},
|
|
45
62
|
},
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Fragment for CustomerInfo / AccountDashboard components.
|
|
2
|
+
#
|
|
3
|
+
# Customer profile data for account pages.
|
|
4
|
+
# Does NOT include orders or addresses (fetch separately).
|
|
5
|
+
#
|
|
6
|
+
# Usage in components:
|
|
7
|
+
# import type { CustomerInfoFieldsFragment } from '@/generated/graphql';
|
|
8
|
+
# interface Props { customer: CustomerInfoFieldsFragment }
|
|
9
|
+
|
|
10
|
+
fragment CustomerInfoFields on Customer {
|
|
11
|
+
id
|
|
12
|
+
email
|
|
13
|
+
firstName
|
|
14
|
+
lastName
|
|
15
|
+
displayName
|
|
16
|
+
phone
|
|
17
|
+
emailVerified
|
|
18
|
+
ordersCount
|
|
19
|
+
totalSpent {
|
|
20
|
+
amount
|
|
21
|
+
currencyCode
|
|
22
|
+
}
|
|
23
|
+
defaultAddress {
|
|
24
|
+
id
|
|
25
|
+
firstName
|
|
26
|
+
lastName
|
|
27
|
+
address1
|
|
28
|
+
address2
|
|
29
|
+
city
|
|
30
|
+
province
|
|
31
|
+
zip
|
|
32
|
+
country
|
|
33
|
+
phone
|
|
34
|
+
}
|
|
35
|
+
createdAt
|
|
36
|
+
}
|
|
@@ -39,7 +39,7 @@ export interface OrderDetailsData {
|
|
|
39
39
|
tax: string;
|
|
40
40
|
total: string;
|
|
41
41
|
currency: string;
|
|
42
|
-
shippingAddress
|
|
42
|
+
shippingAddress?: OrderAddress;
|
|
43
43
|
billingAddress?: OrderAddress;
|
|
44
44
|
trackingNumber?: string;
|
|
45
45
|
estimatedDelivery?: string;
|
|
@@ -237,12 +237,14 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
237
237
|
|
|
238
238
|
{/* Addresses */}
|
|
239
239
|
<div className="grid gap-6 md:grid-cols-2">
|
|
240
|
+
{order.shippingAddress && (
|
|
240
241
|
<Card className="p-6">
|
|
241
242
|
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
242
243
|
Shipping Address
|
|
243
244
|
</h3>
|
|
244
245
|
{formatAddress(order.shippingAddress)}
|
|
245
246
|
</Card>
|
|
247
|
+
)}
|
|
246
248
|
|
|
247
249
|
{order.billingAddress && (
|
|
248
250
|
<Card className="p-6">
|
|
@@ -4,20 +4,11 @@ import Link from "next/link";
|
|
|
4
4
|
import { Package, ChevronRight } from "lucide-react";
|
|
5
5
|
import { Badge } from "@/components/ui/badge";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
|
-
import { formatAmount } from "
|
|
8
|
-
|
|
9
|
-
export interface Order {
|
|
10
|
-
id: string;
|
|
11
|
-
orderNumber: string;
|
|
12
|
-
date: string;
|
|
13
|
-
status: "delivered" | "shipped" | "processing" | "cancelled";
|
|
14
|
-
total: string;
|
|
15
|
-
currency: string;
|
|
16
|
-
itemCount: number;
|
|
17
|
-
}
|
|
7
|
+
import { formatAmount } from "@doswiftly/storefront-sdk";
|
|
8
|
+
import type { OrderSummaryFields } from "@/lib/graphql/fragments";
|
|
18
9
|
|
|
19
10
|
export interface OrderHistoryProps {
|
|
20
|
-
orders:
|
|
11
|
+
orders: OrderSummaryFields[];
|
|
21
12
|
className?: string;
|
|
22
13
|
}
|
|
23
14
|
|
|
@@ -25,17 +16,28 @@ export interface OrderHistoryProps {
|
|
|
25
16
|
* OrderHistory - Display list of customer orders
|
|
26
17
|
*/
|
|
27
18
|
export function OrderHistory({ orders, className }: OrderHistoryProps) {
|
|
28
|
-
const getStatusBadge = (status:
|
|
29
|
-
const variants: Record<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
const getStatusBadge = (status: string) => {
|
|
20
|
+
const variants: Record<string, "default" | "secondary" | "success" | "warning"> = {
|
|
21
|
+
DELIVERED: "success",
|
|
22
|
+
FULFILLED: "success",
|
|
23
|
+
IN_TRANSIT: "default",
|
|
24
|
+
PARTIALLY_FULFILLED: "warning",
|
|
25
|
+
UNFULFILLED: "warning",
|
|
26
|
+
RETURNED: "secondary",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const labels: Record<string, string> = {
|
|
30
|
+
DELIVERED: "Delivered",
|
|
31
|
+
FULFILLED: "Fulfilled",
|
|
32
|
+
IN_TRANSIT: "Shipped",
|
|
33
|
+
PARTIALLY_FULFILLED: "Partially Fulfilled",
|
|
34
|
+
UNFULFILLED: "Processing",
|
|
35
|
+
RETURNED: "Returned",
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
return (
|
|
37
|
-
<Badge variant={variants[status]}>
|
|
38
|
-
{status
|
|
39
|
+
<Badge variant={variants[status] ?? "secondary"}>
|
|
40
|
+
{labels[status] ?? status}
|
|
39
41
|
</Badge>
|
|
40
42
|
);
|
|
41
43
|
};
|
|
@@ -80,12 +82,12 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
|
|
|
80
82
|
<h3 className="text-lg font-semibold text-foreground">
|
|
81
83
|
Order {order.orderNumber}
|
|
82
84
|
</h3>
|
|
83
|
-
{getStatusBadge(order.
|
|
85
|
+
{getStatusBadge(order.fulfillmentStatus)}
|
|
84
86
|
</div>
|
|
85
87
|
<div className="space-y-1 text-sm text-muted-foreground">
|
|
86
|
-
<p>Placed on {formatDate(order.
|
|
88
|
+
<p>Placed on {formatDate(order.processedAt)}</p>
|
|
87
89
|
<p>
|
|
88
|
-
{order.
|
|
90
|
+
{order.lineItemsCount} {order.lineItemsCount === 1 ? "item" : "items"}
|
|
89
91
|
</p>
|
|
90
92
|
</div>
|
|
91
93
|
</div>
|
|
@@ -93,7 +95,7 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
|
|
|
93
95
|
<div className="text-right">
|
|
94
96
|
<p className="text-sm text-muted-foreground">Total</p>
|
|
95
97
|
<p className="text-lg font-semibold text-foreground">
|
|
96
|
-
{formatAmount(order.
|
|
98
|
+
{formatAmount(order.totalPrice.amount, order.totalPrice.currencyCode)}
|
|
97
99
|
</p>
|
|
98
100
|
</div>
|
|
99
101
|
<ChevronRight className="h-5 w-5 text-muted-foreground" />
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Fragment for OrderCard component in account pages.
|
|
2
|
+
#
|
|
3
|
+
# Minimal order data for listing view. Full order details
|
|
4
|
+
# require the Order fragment from storefront-operations.
|
|
5
|
+
#
|
|
6
|
+
# Usage in components:
|
|
7
|
+
# import type { OrderSummaryFieldsFragment } from '@/generated/graphql';
|
|
8
|
+
# interface Props { order: OrderSummaryFieldsFragment }
|
|
9
|
+
|
|
10
|
+
fragment OrderSummaryFields on Order {
|
|
11
|
+
id
|
|
12
|
+
orderNumber
|
|
13
|
+
status
|
|
14
|
+
financialStatus
|
|
15
|
+
fulfillmentStatus
|
|
16
|
+
processedAt
|
|
17
|
+
lineItemsCount
|
|
18
|
+
totalPrice {
|
|
19
|
+
amount
|
|
20
|
+
currencyCode
|
|
21
|
+
}
|
|
22
|
+
subtotalPrice {
|
|
23
|
+
amount
|
|
24
|
+
currencyCode
|
|
25
|
+
}
|
|
26
|
+
totalShipping {
|
|
27
|
+
amount
|
|
28
|
+
currencyCode
|
|
29
|
+
}
|
|
30
|
+
shippingAddress {
|
|
31
|
+
firstName
|
|
32
|
+
lastName
|
|
33
|
+
city
|
|
34
|
+
country
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -6,25 +6,24 @@ import { useRouter } from "next/navigation";
|
|
|
6
6
|
import { User, LogOut, Package, MapPin, Settings } from "lucide-react";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
8
|
import { cn } from "@/lib/utils";
|
|
9
|
+
import { useAuth } from "@/hooks/use-auth";
|
|
9
10
|
|
|
10
11
|
export interface AccountMenuProps {
|
|
11
12
|
customerName?: string;
|
|
12
13
|
className?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
/**
|
|
16
|
-
* AccountMenu - User account dropdown menu
|
|
17
|
-
*
|
|
18
|
-
* Shows user profile and quick links to account pages
|
|
19
|
-
*/
|
|
20
16
|
export function AccountMenu({ customerName, className }: AccountMenuProps) {
|
|
21
17
|
const router = useRouter();
|
|
22
18
|
const [isOpen, setIsOpen] = useState(false);
|
|
23
19
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
const { logout, isLoggingOut } = useAuth();
|
|
24
21
|
|
|
25
22
|
const handleLogout = async () => {
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const result = await logout();
|
|
24
|
+
if (result.success) {
|
|
25
|
+
router.push("/");
|
|
26
|
+
}
|
|
28
27
|
setIsOpen(false);
|
|
29
28
|
};
|
|
30
29
|
|
|
@@ -119,10 +118,11 @@ export function AccountMenu({ customerName, className }: AccountMenuProps) {
|
|
|
119
118
|
<div className="border-t border-border py-2">
|
|
120
119
|
<button
|
|
121
120
|
onClick={handleLogout}
|
|
122
|
-
|
|
121
|
+
disabled={isLoggingOut}
|
|
122
|
+
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-destructive hover:bg-muted transition-colors disabled:opacity-50"
|
|
123
123
|
>
|
|
124
124
|
<LogOut className="h-4 w-4" />
|
|
125
|
-
Sign Out
|
|
125
|
+
{isLoggingOut ? "Signing out..." : "Sign Out"}
|
|
126
126
|
</button>
|
|
127
127
|
</div>
|
|
128
128
|
</div>
|
|
@@ -6,9 +6,8 @@ import Link from "next/link";
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
8
|
import { Input } from "@/components/ui/input";
|
|
9
|
-
import {
|
|
9
|
+
import { useAuth } from "@/hooks/use-auth";
|
|
10
10
|
|
|
11
|
-
// Zod validation schema for login form
|
|
12
11
|
const loginSchema = z.object({
|
|
13
12
|
email: z
|
|
14
13
|
.string()
|
|
@@ -27,11 +26,6 @@ export interface LoginFormProps {
|
|
|
27
26
|
redirectTo?: string;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
/**
|
|
31
|
-
* LoginForm - Reusable login form component with Zod validation
|
|
32
|
-
*
|
|
33
|
-
* Validates user input before submission and displays field-level errors
|
|
34
|
-
*/
|
|
35
29
|
export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
36
30
|
const router = useRouter();
|
|
37
31
|
const searchParams = useSearchParams();
|
|
@@ -42,18 +36,16 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
42
36
|
const [error, setError] = useState("");
|
|
43
37
|
const [fieldErrors, setFieldErrors] = useState<Partial<Record<keyof LoginFormData, string>>>({});
|
|
44
38
|
|
|
45
|
-
const
|
|
39
|
+
const { login, isLoggingIn } = useAuth();
|
|
46
40
|
|
|
47
41
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
48
42
|
e.preventDefault();
|
|
49
43
|
setError("");
|
|
50
44
|
setFieldErrors({});
|
|
51
45
|
|
|
52
|
-
// Validate form data with Zod
|
|
53
46
|
const result = loginSchema.safeParse({ email, password });
|
|
54
47
|
|
|
55
48
|
if (!result.success) {
|
|
56
|
-
// Extract field-level errors from Zod validation
|
|
57
49
|
const errors: Partial<Record<keyof LoginFormData, string>> = {};
|
|
58
50
|
result.error.errors.forEach((err) => {
|
|
59
51
|
if (err.path[0]) {
|
|
@@ -65,37 +57,19 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
try {
|
|
68
|
-
const
|
|
69
|
-
input: { email, password },
|
|
70
|
-
});
|
|
60
|
+
const loginResult = await login(email, password);
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (userErrors.length > 0) {
|
|
76
|
-
setError(userErrors[0].message || "Login failed");
|
|
62
|
+
if (!loginResult.success) {
|
|
63
|
+
setError(loginResult.userErrors[0]?.message || "Login failed");
|
|
77
64
|
return;
|
|
78
65
|
}
|
|
79
66
|
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
await fetch("/api/auth/set-token", {
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers: { "Content-Type": "application/json" },
|
|
85
|
-
body: JSON.stringify({
|
|
86
|
-
token: customerAccessTokenCreate.customerAccessToken.accessToken,
|
|
87
|
-
}),
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
if (onSuccess) {
|
|
91
|
-
onSuccess();
|
|
92
|
-
} else {
|
|
93
|
-
router.push(defaultRedirect);
|
|
94
|
-
}
|
|
67
|
+
if (onSuccess) {
|
|
68
|
+
onSuccess();
|
|
95
69
|
} else {
|
|
96
|
-
|
|
70
|
+
router.push(defaultRedirect);
|
|
97
71
|
}
|
|
98
|
-
} catch
|
|
72
|
+
} catch {
|
|
99
73
|
setError("An error occurred. Please try again.");
|
|
100
74
|
}
|
|
101
75
|
};
|
|
@@ -169,9 +143,9 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
169
143
|
<Button
|
|
170
144
|
type="submit"
|
|
171
145
|
className="w-full"
|
|
172
|
-
disabled={
|
|
146
|
+
disabled={isLoggingIn}
|
|
173
147
|
>
|
|
174
|
-
{
|
|
148
|
+
{isLoggingIn ? "Signing in..." : "Sign In"}
|
|
175
149
|
</Button>
|
|
176
150
|
|
|
177
151
|
<div className="text-center text-sm text-muted-foreground">
|
|
@@ -4,9 +4,13 @@ import { useState } from "react";
|
|
|
4
4
|
import { useRouter } from "next/navigation";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { useMutation } from "@tanstack/react-query";
|
|
8
|
-
import {
|
|
9
|
-
import { CustomerCreateDocument } from "@/generated/graphql";
|
|
7
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
8
|
+
import { useExecute } from "@/lib/graphql/client";
|
|
9
|
+
import { CustomerCreateDocument, type CustomerCreateMutation } from "@/generated/graphql";
|
|
10
|
+
import { useAuthStore } from "@doswiftly/storefront-sdk/react";
|
|
11
|
+
import { createAuthTokenClient } from "@doswiftly/storefront-sdk";
|
|
12
|
+
|
|
13
|
+
const { setToken: setAuthToken } = createAuthTokenClient();
|
|
10
14
|
import { Button } from "@/components/ui/button";
|
|
11
15
|
import { Input } from "@/components/ui/input";
|
|
12
16
|
|
|
@@ -58,7 +62,9 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
58
62
|
const [error, setError] = useState("");
|
|
59
63
|
const [fieldErrors, setFieldErrors] = useState<Partial<Record<keyof RegisterFormData, string>>>({});
|
|
60
64
|
|
|
61
|
-
const
|
|
65
|
+
const queryClient = useQueryClient();
|
|
66
|
+
const { setAuth } = useAuthStore();
|
|
67
|
+
const execute = useExecute();
|
|
62
68
|
|
|
63
69
|
const registerMutation = useMutation({
|
|
64
70
|
mutationFn: async (input: {
|
|
@@ -67,7 +73,10 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
67
73
|
firstName: string;
|
|
68
74
|
lastName: string;
|
|
69
75
|
}) => {
|
|
70
|
-
return
|
|
76
|
+
return execute<CustomerCreateMutation>(
|
|
77
|
+
CustomerCreateDocument.toString(),
|
|
78
|
+
{ input },
|
|
79
|
+
);
|
|
71
80
|
},
|
|
72
81
|
});
|
|
73
82
|
|
|
@@ -105,8 +114,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
105
114
|
});
|
|
106
115
|
|
|
107
116
|
// Check for user errors first
|
|
108
|
-
const userErrors = mutationResult?.customerCreate?.userErrors ||
|
|
109
|
-
mutationResult?.customerCreate?.customerUserErrors || [];
|
|
117
|
+
const userErrors = mutationResult?.customerCreate?.userErrors || [];
|
|
110
118
|
|
|
111
119
|
if (userErrors.length > 0) {
|
|
112
120
|
setError(userErrors[0].message || "Registration failed");
|
|
@@ -114,28 +122,34 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
if (mutationResult?.customerCreate?.customer) {
|
|
117
|
-
// Registration successful - token is already in the response
|
|
118
125
|
const accessToken = mutationResult?.customerCreate?.customerAccessToken?.accessToken;
|
|
119
|
-
|
|
126
|
+
const customer = mutationResult.customerCreate.customer;
|
|
127
|
+
|
|
120
128
|
if (accessToken) {
|
|
121
|
-
// Store token in
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
// Store token in Zustand store (for client-side GraphQL requests)
|
|
130
|
+
setAuth(
|
|
131
|
+
{
|
|
132
|
+
id: customer.id,
|
|
133
|
+
email: customer.email,
|
|
134
|
+
firstName: customer.firstName || undefined,
|
|
135
|
+
lastName: customer.lastName || undefined,
|
|
136
|
+
phone: customer.phone || undefined,
|
|
137
|
+
},
|
|
138
|
+
accessToken,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Store token in httpOnly cookie (for SSR/middleware)
|
|
142
|
+
await setAuthToken(accessToken);
|
|
143
|
+
|
|
144
|
+
// Invalidate queries so authenticated data loads fresh
|
|
145
|
+
queryClient.invalidateQueries();
|
|
127
146
|
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
onSuccess();
|
|
131
|
-
} else {
|
|
132
|
-
router.push(redirectTo);
|
|
133
|
-
}
|
|
147
|
+
if (onSuccess) {
|
|
148
|
+
onSuccess();
|
|
134
149
|
} else {
|
|
135
|
-
|
|
150
|
+
router.push(redirectTo);
|
|
136
151
|
}
|
|
137
152
|
} else {
|
|
138
|
-
// No token in response - redirect to login
|
|
139
153
|
setError("Registration successful! Please log in with your credentials.");
|
|
140
154
|
setTimeout(() => router.push("/auth/login"), 2000);
|
|
141
155
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
4
5
|
import { X, ShoppingBag, Loader2 } from "lucide-react";
|
|
5
6
|
import { useCartStore } from "@/stores/cart-store";
|
|
6
7
|
import { useCartSync } from "@/hooks/use-cart-sync";
|
|
@@ -22,6 +23,8 @@ export interface CartDrawerProps {
|
|
|
22
23
|
* Mutations go through useCartActions (server-only).
|
|
23
24
|
*/
|
|
24
25
|
export function CartDrawer({ className }: CartDrawerProps) {
|
|
26
|
+
const router = useRouter();
|
|
27
|
+
|
|
25
28
|
// UI state from Zustand
|
|
26
29
|
const { isOpen, closeCart } = useCartStore();
|
|
27
30
|
|
|
@@ -45,9 +48,7 @@ export function CartDrawer({ className }: CartDrawerProps) {
|
|
|
45
48
|
|
|
46
49
|
const handleCheckout = () => {
|
|
47
50
|
closeCart();
|
|
48
|
-
|
|
49
|
-
window.location.href = "/checkout";
|
|
50
|
-
}
|
|
51
|
+
router.push("/checkout");
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
if (!isOpen) return null;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useRouter } from "next/navigation";
|
|
4
4
|
import { ShoppingCart } from "lucide-react";
|
|
5
5
|
import { useCartSync } from "@/hooks/use-cart-sync";
|
|
6
|
+
import { useHydrated } from "@doswiftly/storefront-sdk/react";
|
|
6
7
|
import { Button } from "@/components/ui/button";
|
|
7
8
|
import { cn } from "@/lib/utils";
|
|
8
9
|
|
|
@@ -15,11 +16,13 @@ export interface CartIconProps {
|
|
|
15
16
|
/**
|
|
16
17
|
* CartIcon - Cart button with item count badge
|
|
17
18
|
*
|
|
18
|
-
* Client Component that reads cart count from server (source of truth)
|
|
19
|
+
* Client Component that reads cart count from server (source of truth).
|
|
20
|
+
* Badge is guarded by isHydrated to prevent hydration mismatch.
|
|
19
21
|
*/
|
|
20
22
|
export function CartIcon({ className, navigateToCart = true }: CartIconProps) {
|
|
21
23
|
const router = useRouter();
|
|
22
|
-
const
|
|
24
|
+
const isHydrated = useHydrated();
|
|
25
|
+
const { totalQuantity } = useCartSync();
|
|
23
26
|
|
|
24
27
|
const handleClick = () => {
|
|
25
28
|
if (navigateToCart) {
|
|
@@ -33,12 +36,12 @@ export function CartIcon({ className, navigateToCart = true }: CartIconProps) {
|
|
|
33
36
|
size="sm"
|
|
34
37
|
onClick={handleClick}
|
|
35
38
|
className={cn("relative p-2", className)}
|
|
36
|
-
aria-label={`Shopping cart with ${totalQuantity} items`}
|
|
39
|
+
aria-label={`Shopping cart${isHydrated && totalQuantity > 0 ? ` with ${totalQuantity} items` : ""}`}
|
|
37
40
|
>
|
|
38
41
|
<ShoppingCart className="h-5 w-5" />
|
|
39
42
|
|
|
40
|
-
{/* Badge */}
|
|
41
|
-
{totalQuantity > 0 && (
|
|
43
|
+
{/* Badge — only rendered after hydration to prevent mismatch */}
|
|
44
|
+
{isHydrated && totalQuantity > 0 && (
|
|
42
45
|
<span className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground">
|
|
43
46
|
{totalQuantity > 99 ? "99+" : totalQuantity}
|
|
44
47
|
</span>
|
|
@@ -6,7 +6,7 @@ import { ProductImage } from "@/components/product/product-image";
|
|
|
6
6
|
import { ProductQuantitySelector } from "@/components/product/product-quantity-selector";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
8
|
import { cn } from "@/lib/utils";
|
|
9
|
-
import { formatPrice, formatAmount } from "
|
|
9
|
+
import { formatPrice, formatAmount } from "@doswiftly/storefront-sdk";
|
|
10
10
|
import type { CartItemData } from "@/hooks/use-cart-sync";
|
|
11
11
|
|
|
12
12
|
export interface CartItemProps {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Fragment for CartItem / CartLine component.
|
|
2
|
+
#
|
|
3
|
+
# Maps to the CartItemData interface in use-cart-sync.ts.
|
|
4
|
+
# Used by: CartDrawer, CartPage, MiniCart.
|
|
5
|
+
#
|
|
6
|
+
# Usage in components:
|
|
7
|
+
# import type { CartLineFieldsFragment } from '@/generated/graphql';
|
|
8
|
+
# interface Props { line: CartLineFieldsFragment }
|
|
9
|
+
#
|
|
10
|
+
# Note: This is a UI-focused subset. The full Cart fragment
|
|
11
|
+
# from storefront-operations includes cost aggregates and
|
|
12
|
+
# discount allocations for the cart summary.
|
|
13
|
+
|
|
14
|
+
fragment CartLineFields on CartLine {
|
|
15
|
+
id
|
|
16
|
+
quantity
|
|
17
|
+
productId
|
|
18
|
+
productTitle
|
|
19
|
+
productHandle
|
|
20
|
+
productType
|
|
21
|
+
merchandise {
|
|
22
|
+
id
|
|
23
|
+
title
|
|
24
|
+
available
|
|
25
|
+
quantityAvailable
|
|
26
|
+
price {
|
|
27
|
+
amount
|
|
28
|
+
currencyCode
|
|
29
|
+
}
|
|
30
|
+
image {
|
|
31
|
+
url
|
|
32
|
+
altText
|
|
33
|
+
}
|
|
34
|
+
selectedOptions {
|
|
35
|
+
name
|
|
36
|
+
value
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
cost {
|
|
40
|
+
amountPerQuantity {
|
|
41
|
+
amount
|
|
42
|
+
currencyCode
|
|
43
|
+
}
|
|
44
|
+
totalAmount {
|
|
45
|
+
amount
|
|
46
|
+
currencyCode
|
|
47
|
+
}
|
|
48
|
+
compareAtAmountPerQuantity {
|
|
49
|
+
amount
|
|
50
|
+
currencyCode
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|