@doswiftly/cli 0.1.18 → 0.1.20
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 +20 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +249 -17
- 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 +4 -4
- package/templates/storefront-minimal/.github/workflows/build-template.yml +10 -0
- package/templates/storefront-minimal/wrangler.toml +11 -0
- package/templates/storefront-nextjs/.github/workflows/build-template.yml +10 -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 +11 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +10 -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 +172 -35
- package/templates/storefront-nextjs-shadcn/README.md +29 -162
- package/templates/storefront-nextjs-shadcn/app/{about → [locale]/about}/page.tsx +17 -14
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/addresses/page.tsx +226 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/error.tsx +46 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loyalty/page.tsx +89 -193
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/loading.tsx +60 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/page.tsx +119 -0
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/tracking/page.tsx +27 -25
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/page.tsx +101 -0
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/page.tsx +9 -7
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/settings/page.tsx +208 -0
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/forgot-password/page.tsx +24 -17
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/login/page.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/register/page.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/app/[locale]/blog/[slug]/loading.tsx +17 -0
- package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/page.tsx +44 -3
- package/templates/storefront-nextjs-shadcn/app/[locale]/blog/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/page.tsx +2 -1
- package/templates/storefront-nextjs-shadcn/app/[locale]/cart/loading.tsx +26 -0
- package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/page.tsx +20 -13
- package/templates/storefront-nextjs-shadcn/app/[locale]/categories/[slug]/category-products-client.tsx +58 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/categories/[slug]/loading.tsx +32 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/categories/[slug]/page.tsx +95 -0
- package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/page.tsx +21 -12
- package/templates/storefront-nextjs-shadcn/app/[locale]/checkout/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/checkout/loading.tsx +31 -0
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/page.tsx +334 -253
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/success/[orderId]/page.tsx +36 -34
- package/templates/storefront-nextjs-shadcn/app/[locale]/collections/[handle]/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/page.tsx +6 -4
- package/templates/storefront-nextjs-shadcn/app/[locale]/collections/loading.tsx +18 -0
- package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/page.tsx +20 -12
- package/templates/storefront-nextjs-shadcn/app/{contact → [locale]/contact}/page.tsx +24 -21
- package/templates/storefront-nextjs-shadcn/app/{error.tsx → [locale]/error.tsx} +13 -8
- package/templates/storefront-nextjs-shadcn/app/[locale]/layout.tsx +92 -0
- package/templates/storefront-nextjs-shadcn/app/{not-found.tsx → [locale]/not-found.tsx} +13 -18
- package/templates/storefront-nextjs-shadcn/app/{page.tsx → [locale]/page.tsx} +8 -4
- package/templates/storefront-nextjs-shadcn/app/[locale]/products/[slug]/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/products/[slug]/loading.tsx +29 -0
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx +17 -14
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/product-client.tsx +18 -62
- package/templates/storefront-nextjs-shadcn/app/[locale]/products/loading.tsx +32 -0
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/page.tsx +6 -3
- package/templates/storefront-nextjs-shadcn/app/[locale]/products/products-client.tsx +450 -0
- package/templates/storefront-nextjs-shadcn/app/[locale]/search/loading.tsx +18 -0
- package/templates/storefront-nextjs-shadcn/app/{wishlist → [locale]/wishlist}/page.tsx +27 -28
- 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/global-error.tsx +117 -0
- package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
- package/templates/storefront-nextjs-shadcn/app/layout.tsx +8 -35
- package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
- package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +25 -20
- package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +11 -10
- package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +17 -13
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +42 -30
- package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
- package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +18 -16
- package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +37 -58
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +85 -66
- package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +10 -6
- package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +9 -6
- package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +8 -6
- package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +38 -20
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +15 -25
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +9 -6
- package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +7 -5
- package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +6 -4
- package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +10 -9
- package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
- package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +9 -6
- package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +22 -12
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +18 -15
- package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +53 -28
- package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +7 -5
- package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +19 -15
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +13 -10
- package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +10 -6
- package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
- package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +3 -13
- package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +12 -8
- package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +13 -8
- package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
- package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
- package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +41 -16
- package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
- package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +24 -23
- package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +52 -34
- package/templates/storefront-nextjs-shadcn/components/layout/language-switcher.tsx +54 -0
- package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +33 -30
- package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +27 -24
- 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 +32 -42
- 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/add-to-cart-button.tsx +6 -14
- package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +4 -2
- package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +72 -0
- package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +87 -0
- package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +140 -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 +11 -37
- package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +179 -124
- package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
- package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +3 -7
- 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 +44 -19
- 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 +26 -34
- 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/language-sync-provider.tsx +27 -0
- package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +63 -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 +59 -72
- package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
- package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +12 -9
- package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +23 -12
- 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-button.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +4 -2
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +2 -10
- package/templates/storefront-nextjs-shadcn/generated/graphql.ts +13387 -0
- package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
- package/templates/storefront-nextjs-shadcn/hooks/index.ts +3 -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 +34 -229
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-di.ts +67 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +16 -12
- package/templates/storefront-nextjs-shadcn/i18n/navigation.ts +12 -0
- package/templates/storefront-nextjs-shadcn/i18n/request.ts +17 -0
- package/templates/storefront-nextjs-shadcn/i18n/routing.ts +17 -0
- 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 +33 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +720 -632
- package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +88 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +132 -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/messages/en.json +869 -0
- package/templates/storefront-nextjs-shadcn/messages/pl.json +869 -0
- package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
- package/templates/storefront-nextjs-shadcn/next.config.ts +6 -5
- package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
- package/templates/storefront-nextjs-shadcn/package.json +14 -14
- package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
- package/templates/storefront-nextjs-shadcn/proxy.ts +115 -47
- package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +24 -56
- 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 +11 -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/app/account/addresses/page.tsx +0 -215
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +0 -128
- package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +0 -80
- package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +0 -171
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +0 -78
- package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +0 -192
- 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
- /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/[slug]/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{returns → [locale]/returns}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/search-client.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{shipping → [locale]/shipping}/page.tsx +0 -0
|
@@ -1,42 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { useRouter } from "
|
|
5
|
-
import
|
|
4
|
+
import { useRouter, Link } from "@/i18n/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
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
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.max(50, "First name must be less than 50 characters"),
|
|
20
|
-
lastName: z
|
|
21
|
-
.string()
|
|
22
|
-
.min(1, "Last name is required")
|
|
23
|
-
.min(2, "Last name must be at least 2 characters")
|
|
24
|
-
.max(50, "Last name must be less than 50 characters"),
|
|
25
|
-
email: z
|
|
26
|
-
.string()
|
|
27
|
-
.min(1, "Email is required")
|
|
28
|
-
.email("Please enter a valid email address"),
|
|
29
|
-
password: z
|
|
30
|
-
.string()
|
|
31
|
-
.min(1, "Password is required")
|
|
32
|
-
.min(8, "Password must be at least 8 characters")
|
|
33
|
-
.regex(
|
|
34
|
-
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
|
|
35
|
-
"Password must contain at least one uppercase letter, one lowercase letter, and one number"
|
|
36
|
-
),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
type RegisterFormData = z.infer<typeof registerSchema>;
|
|
17
|
+
type RegisterFormData = {
|
|
18
|
+
firstName: string;
|
|
19
|
+
lastName: string;
|
|
20
|
+
email: string;
|
|
21
|
+
password: string;
|
|
22
|
+
};
|
|
40
23
|
|
|
41
24
|
export interface RegisterFormProps {
|
|
42
25
|
onSuccess?: () => void;
|
|
@@ -45,12 +28,38 @@ export interface RegisterFormProps {
|
|
|
45
28
|
|
|
46
29
|
/**
|
|
47
30
|
* RegisterForm - Reusable registration form component with Zod validation
|
|
48
|
-
*
|
|
31
|
+
*
|
|
49
32
|
* Validates user input before submission and displays field-level errors
|
|
50
33
|
*/
|
|
51
34
|
export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFormProps) {
|
|
35
|
+
const t = useTranslations("auth");
|
|
52
36
|
const router = useRouter();
|
|
53
37
|
|
|
38
|
+
const registerSchema = z.object({
|
|
39
|
+
firstName: z
|
|
40
|
+
.string()
|
|
41
|
+
.min(1, t("validation.firstNameMinLength"))
|
|
42
|
+
.min(2, t("validation.firstNameMinLength"))
|
|
43
|
+
.max(50, t("validation.firstNameMaxLength")),
|
|
44
|
+
lastName: z
|
|
45
|
+
.string()
|
|
46
|
+
.min(1, t("validation.lastNameMinLength"))
|
|
47
|
+
.min(2, t("validation.lastNameMinLength"))
|
|
48
|
+
.max(50, t("validation.lastNameMaxLength")),
|
|
49
|
+
email: z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1, t("validation.emailInvalid"))
|
|
52
|
+
.email(t("validation.emailInvalid")),
|
|
53
|
+
password: z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1, t("validation.passwordMinLength"))
|
|
56
|
+
.min(8, t("validation.passwordMinLength"))
|
|
57
|
+
.regex(
|
|
58
|
+
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
|
|
59
|
+
t("validation.passwordRequirements"),
|
|
60
|
+
),
|
|
61
|
+
});
|
|
62
|
+
|
|
54
63
|
const [email, setEmail] = useState("");
|
|
55
64
|
const [password, setPassword] = useState("");
|
|
56
65
|
const [firstName, setFirstName] = useState("");
|
|
@@ -58,7 +67,9 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
58
67
|
const [error, setError] = useState("");
|
|
59
68
|
const [fieldErrors, setFieldErrors] = useState<Partial<Record<keyof RegisterFormData, string>>>({});
|
|
60
69
|
|
|
61
|
-
const
|
|
70
|
+
const queryClient = useQueryClient();
|
|
71
|
+
const { setAuth } = useAuthStore();
|
|
72
|
+
const execute = useExecute();
|
|
62
73
|
|
|
63
74
|
const registerMutation = useMutation({
|
|
64
75
|
mutationFn: async (input: {
|
|
@@ -67,7 +78,10 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
67
78
|
firstName: string;
|
|
68
79
|
lastName: string;
|
|
69
80
|
}) => {
|
|
70
|
-
return
|
|
81
|
+
return execute<CustomerCreateMutation>(
|
|
82
|
+
CustomerCreateDocument.toString(),
|
|
83
|
+
{ input },
|
|
84
|
+
);
|
|
71
85
|
},
|
|
72
86
|
});
|
|
73
87
|
|
|
@@ -105,45 +119,50 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
105
119
|
});
|
|
106
120
|
|
|
107
121
|
// Check for user errors first
|
|
108
|
-
const userErrors = mutationResult?.customerCreate?.userErrors ||
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
const userErrors = mutationResult?.customerCreate?.userErrors || [];
|
|
123
|
+
|
|
111
124
|
if (userErrors.length > 0) {
|
|
112
|
-
setError(userErrors[0].message || "
|
|
125
|
+
setError(userErrors[0].message || t("registerFailed"));
|
|
113
126
|
return;
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
if (mutationResult?.customerCreate?.customer) {
|
|
117
|
-
// Registration successful - token is already in the response
|
|
118
130
|
const accessToken = mutationResult?.customerCreate?.customerAccessToken?.accessToken;
|
|
119
|
-
|
|
131
|
+
const customer = mutationResult.customerCreate.customer;
|
|
132
|
+
|
|
120
133
|
if (accessToken) {
|
|
121
|
-
// Store token in
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
// Store token in Zustand store (for client-side GraphQL requests)
|
|
135
|
+
setAuth(
|
|
136
|
+
{
|
|
137
|
+
id: customer.id,
|
|
138
|
+
email: customer.email,
|
|
139
|
+
firstName: customer.firstName || undefined,
|
|
140
|
+
lastName: customer.lastName || undefined,
|
|
141
|
+
phone: customer.phone || undefined,
|
|
142
|
+
},
|
|
143
|
+
accessToken,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Store token in httpOnly cookie (for SSR/middleware)
|
|
147
|
+
await setAuthToken(accessToken);
|
|
148
|
+
|
|
149
|
+
// Invalidate queries so authenticated data loads fresh
|
|
150
|
+
queryClient.invalidateQueries();
|
|
127
151
|
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
onSuccess();
|
|
131
|
-
} else {
|
|
132
|
-
router.push(redirectTo);
|
|
133
|
-
}
|
|
152
|
+
if (onSuccess) {
|
|
153
|
+
onSuccess();
|
|
134
154
|
} else {
|
|
135
|
-
|
|
155
|
+
router.push(redirectTo);
|
|
136
156
|
}
|
|
137
157
|
} else {
|
|
138
|
-
|
|
139
|
-
setError("Registration successful! Please log in with your credentials.");
|
|
158
|
+
setError(t("registerSuccess"));
|
|
140
159
|
setTimeout(() => router.push("/auth/login"), 2000);
|
|
141
160
|
}
|
|
142
161
|
} else {
|
|
143
|
-
setError("
|
|
162
|
+
setError(t("registerFailed"));
|
|
144
163
|
}
|
|
145
164
|
} catch (err) {
|
|
146
|
-
setError("
|
|
165
|
+
setError(t("unexpectedError"));
|
|
147
166
|
}
|
|
148
167
|
};
|
|
149
168
|
|
|
@@ -161,7 +180,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
161
180
|
htmlFor="firstName"
|
|
162
181
|
className="text-sm font-medium text-foreground"
|
|
163
182
|
>
|
|
164
|
-
|
|
183
|
+
{t("firstName")}
|
|
165
184
|
</label>
|
|
166
185
|
<Input
|
|
167
186
|
id="firstName"
|
|
@@ -186,7 +205,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
186
205
|
htmlFor="lastName"
|
|
187
206
|
className="text-sm font-medium text-foreground"
|
|
188
207
|
>
|
|
189
|
-
|
|
208
|
+
{t("lastName")}
|
|
190
209
|
</label>
|
|
191
210
|
<Input
|
|
192
211
|
id="lastName"
|
|
@@ -212,7 +231,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
212
231
|
htmlFor="email"
|
|
213
232
|
className="text-sm font-medium text-foreground"
|
|
214
233
|
>
|
|
215
|
-
|
|
234
|
+
{t("email")}
|
|
216
235
|
</label>
|
|
217
236
|
<Input
|
|
218
237
|
id="email"
|
|
@@ -237,7 +256,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
237
256
|
htmlFor="password"
|
|
238
257
|
className="text-sm font-medium text-foreground"
|
|
239
258
|
>
|
|
240
|
-
|
|
259
|
+
{t("password")}
|
|
241
260
|
</label>
|
|
242
261
|
<Input
|
|
243
262
|
id="password"
|
|
@@ -257,7 +276,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
257
276
|
</p>
|
|
258
277
|
) : (
|
|
259
278
|
<p className="text-xs text-muted-foreground">
|
|
260
|
-
|
|
279
|
+
{t("passwordRequirements")}
|
|
261
280
|
</p>
|
|
262
281
|
)}
|
|
263
282
|
</div>
|
|
@@ -267,16 +286,16 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
267
286
|
className="w-full"
|
|
268
287
|
disabled={registerMutation.isPending}
|
|
269
288
|
>
|
|
270
|
-
{registerMutation.isPending ? "
|
|
289
|
+
{registerMutation.isPending ? t("creatingAccount") : t("createAccount")}
|
|
271
290
|
</Button>
|
|
272
291
|
|
|
273
292
|
<div className="text-center text-sm text-muted-foreground">
|
|
274
|
-
|
|
293
|
+
{t("hasAccount")}{" "}
|
|
275
294
|
<Link
|
|
276
295
|
href="/auth/login"
|
|
277
296
|
className="font-medium text-primary hover:underline"
|
|
278
297
|
>
|
|
279
|
-
|
|
298
|
+
{t("signIn")}
|
|
280
299
|
</Link>
|
|
281
300
|
</div>
|
|
282
301
|
</form>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import Image from 'next/image';
|
|
10
|
-
import Link from '
|
|
10
|
+
import { Link } from '@/i18n/navigation';
|
|
11
11
|
import { Calendar, Clock, Eye, User } from 'lucide-react';
|
|
12
12
|
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
|
13
13
|
import { Badge } from '@/components/ui/badge';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Sidebar with categories, tags, recent posts, and search.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import Link from '
|
|
9
|
+
import { Link } from '@/i18n/navigation';
|
|
10
10
|
import { Search, FolderOpen, Tag, TrendingUp } from 'lucide-react';
|
|
11
11
|
import { Input } from '@/components/ui/input';
|
|
12
12
|
import { Badge } from '@/components/ui/badge';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect } from "react";
|
|
4
|
+
import { useRouter } from "@/i18n/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
4
6
|
import { X, ShoppingBag, Loader2 } from "lucide-react";
|
|
5
7
|
import { useCartStore } from "@/stores/cart-store";
|
|
6
8
|
import { useCartSync } from "@/hooks/use-cart-sync";
|
|
@@ -22,6 +24,10 @@ export interface CartDrawerProps {
|
|
|
22
24
|
* Mutations go through useCartActions (server-only).
|
|
23
25
|
*/
|
|
24
26
|
export function CartDrawer({ className }: CartDrawerProps) {
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
const t = useTranslations("cart");
|
|
29
|
+
const tc = useTranslations("common");
|
|
30
|
+
|
|
25
31
|
// UI state from Zustand
|
|
26
32
|
const { isOpen, closeCart } = useCartStore();
|
|
27
33
|
|
|
@@ -45,9 +51,7 @@ export function CartDrawer({ className }: CartDrawerProps) {
|
|
|
45
51
|
|
|
46
52
|
const handleCheckout = () => {
|
|
47
53
|
closeCart();
|
|
48
|
-
|
|
49
|
-
window.location.href = "/checkout";
|
|
50
|
-
}
|
|
54
|
+
router.push("/checkout");
|
|
51
55
|
};
|
|
52
56
|
|
|
53
57
|
if (!isOpen) return null;
|
|
@@ -70,14 +74,14 @@ export function CartDrawer({ className }: CartDrawerProps) {
|
|
|
70
74
|
)}
|
|
71
75
|
role="dialog"
|
|
72
76
|
aria-modal="true"
|
|
73
|
-
aria-label="
|
|
77
|
+
aria-label={t("title")}
|
|
74
78
|
>
|
|
75
79
|
{/* Header */}
|
|
76
80
|
<div className="flex items-center justify-between border-b border-border p-4">
|
|
77
81
|
<div className="flex items-center gap-2">
|
|
78
82
|
<ShoppingBag className="h-5 w-5" />
|
|
79
83
|
<h2 className="text-lg font-semibold">
|
|
80
|
-
|
|
84
|
+
{t("title")}
|
|
81
85
|
{totalQuantity > 0 && (
|
|
82
86
|
<span className="ml-2 text-sm font-normal text-muted-foreground">
|
|
83
87
|
({totalQuantity})
|
|
@@ -90,7 +94,7 @@ export function CartDrawer({ className }: CartDrawerProps) {
|
|
|
90
94
|
size="sm"
|
|
91
95
|
onClick={closeCart}
|
|
92
96
|
className="h-8 w-8 p-0"
|
|
93
|
-
aria-label="
|
|
97
|
+
aria-label={tc("close")}
|
|
94
98
|
>
|
|
95
99
|
<X className="h-5 w-5" />
|
|
96
100
|
</Button>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useRouter } from "
|
|
3
|
+
import { useRouter } from "@/i18n/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>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { X, Gift } from "lucide-react";
|
|
4
|
-
import Link from "
|
|
4
|
+
import { Link } from "@/i18n/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
5
6
|
import { ProductImage } from "@/components/product/product-image";
|
|
6
7
|
import { ProductQuantitySelector } from "@/components/product/product-quantity-selector";
|
|
7
8
|
import { Button } from "@/components/ui/button";
|
|
8
9
|
import { cn } from "@/lib/utils";
|
|
9
|
-
import { formatPrice, formatAmount } from "
|
|
10
|
+
import { formatPrice, formatAmount } from "@doswiftly/storefront-sdk";
|
|
10
11
|
import type { CartItemData } from "@/hooks/use-cart-sync";
|
|
11
12
|
|
|
12
13
|
export interface CartItemProps {
|
|
@@ -25,6 +26,7 @@ export function CartItem({
|
|
|
25
26
|
onRemove,
|
|
26
27
|
className,
|
|
27
28
|
}: CartItemProps) {
|
|
29
|
+
const t = useTranslations("cart");
|
|
28
30
|
const itemTotal = parseFloat(item.price.amount) * item.quantity;
|
|
29
31
|
|
|
30
32
|
return (
|
|
@@ -56,7 +58,7 @@ export function CartItem({
|
|
|
56
58
|
{item.productType === "GIFT_CARD" && (
|
|
57
59
|
<p className="mt-1 flex items-center gap-1 text-xs text-primary">
|
|
58
60
|
<Gift className="h-3 w-3" />
|
|
59
|
-
|
|
61
|
+
{t("giftCardLabel")}
|
|
60
62
|
</p>
|
|
61
63
|
)}
|
|
62
64
|
{item.variantTitle && (
|
|
@@ -72,7 +74,7 @@ export function CartItem({
|
|
|
72
74
|
size="sm"
|
|
73
75
|
onClick={() => onRemove(item.lineId)}
|
|
74
76
|
className="h-8 w-8 p-0"
|
|
75
|
-
aria-label="
|
|
77
|
+
aria-label={t("remove")}
|
|
76
78
|
>
|
|
77
79
|
<X className="h-4 w-4" />
|
|
78
80
|
</Button>
|
|
@@ -94,7 +96,7 @@ export function CartItem({
|
|
|
94
96
|
</p>
|
|
95
97
|
{item.quantity > 1 && (
|
|
96
98
|
<p className="text-xs text-muted-foreground">
|
|
97
|
-
{formatPrice(item.price)} each
|
|
99
|
+
{formatPrice(item.price)} {t("each")}
|
|
98
100
|
</p>
|
|
99
101
|
)}
|
|
100
102
|
</div>
|
|
@@ -103,7 +105,7 @@ export function CartItem({
|
|
|
103
105
|
{/* Out of stock warning */}
|
|
104
106
|
{!item.available && (
|
|
105
107
|
<p className="mt-2 text-xs text-destructive">
|
|
106
|
-
|
|
108
|
+
{t("outOfStockWarning")}
|
|
107
109
|
</p>
|
|
108
110
|
)}
|
|
109
111
|
</div>
|
|
@@ -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
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { Button } from "@/components/ui/button";
|
|
4
5
|
import { cn } from "@/lib/utils";
|
|
5
|
-
import { formatAmount } from "
|
|
6
|
+
import { formatAmount } from "@doswiftly/storefront-sdk";
|
|
6
7
|
|
|
7
8
|
export interface CartSummaryProps {
|
|
8
9
|
subtotal: number;
|
|
@@ -26,6 +27,7 @@ export function CartSummary({
|
|
|
26
27
|
onCheckout,
|
|
27
28
|
className,
|
|
28
29
|
}: CartSummaryProps) {
|
|
30
|
+
const t = useTranslations("cart");
|
|
29
31
|
const displayTotal = total ?? subtotal;
|
|
30
32
|
const hasDiscount = (totalDiscount ?? 0) > 0;
|
|
31
33
|
|
|
@@ -33,7 +35,7 @@ export function CartSummary({
|
|
|
33
35
|
<div className={cn("space-y-4", className)}>
|
|
34
36
|
{/* Subtotal */}
|
|
35
37
|
<div className="flex items-center justify-between border-t border-border pt-4">
|
|
36
|
-
<span className="text-base font-medium text-foreground">
|
|
38
|
+
<span className="text-base font-medium text-foreground">{t("subtotal")}</span>
|
|
37
39
|
<span className={cn("text-lg font-semibold text-foreground", hasDiscount && "text-muted-foreground line-through text-base")}>
|
|
38
40
|
{formatAmount(subtotal, currencyCode)}
|
|
39
41
|
</span>
|
|
@@ -43,11 +45,11 @@ export function CartSummary({
|
|
|
43
45
|
{hasDiscount && (
|
|
44
46
|
<>
|
|
45
47
|
<div className="flex items-center justify-between text-sm text-green-600 dark:text-green-400">
|
|
46
|
-
<span>
|
|
48
|
+
<span>{t("discount")}</span>
|
|
47
49
|
<span>-{formatAmount(totalDiscount!, currencyCode)}</span>
|
|
48
50
|
</div>
|
|
49
51
|
<div className="flex items-center justify-between">
|
|
50
|
-
<span className="text-base font-medium text-foreground">
|
|
52
|
+
<span className="text-base font-medium text-foreground">{t("total")}</span>
|
|
51
53
|
<span className="text-lg font-semibold text-foreground">
|
|
52
54
|
{formatAmount(displayTotal, currencyCode)}
|
|
53
55
|
</span>
|
|
@@ -57,12 +59,12 @@ export function CartSummary({
|
|
|
57
59
|
|
|
58
60
|
{/* Item count */}
|
|
59
61
|
<p className="text-sm text-muted-foreground">
|
|
60
|
-
{itemCount
|
|
62
|
+
{t("itemCount", { count: itemCount })} {t("inCart")}
|
|
61
63
|
</p>
|
|
62
64
|
|
|
63
65
|
{/* Shipping note */}
|
|
64
66
|
<p className="text-xs text-muted-foreground">
|
|
65
|
-
|
|
67
|
+
{t("shippingTaxNote")}
|
|
66
68
|
</p>
|
|
67
69
|
|
|
68
70
|
{/* Checkout button */}
|
|
@@ -72,12 +74,12 @@ export function CartSummary({
|
|
|
72
74
|
className="w-full"
|
|
73
75
|
disabled={itemCount === 0}
|
|
74
76
|
>
|
|
75
|
-
|
|
77
|
+
{t("proceedToCheckout")}
|
|
76
78
|
</Button>
|
|
77
79
|
|
|
78
80
|
{/* Continue shopping */}
|
|
79
81
|
<Button variant="outline" size="lg" className="w-full" asChild>
|
|
80
|
-
<a href="/products">
|
|
82
|
+
<a href="/products">{t("continueShopping")}</a>
|
|
81
83
|
</Button>
|
|
82
84
|
</div>
|
|
83
85
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Tag, Check, X } from "lucide-react";
|
|
5
6
|
import { Input } from "@/components/ui/input";
|
|
6
7
|
import { Button } from "@/components/ui/button";
|
|
@@ -22,6 +23,8 @@ export function PromoCodeInput({
|
|
|
22
23
|
onRemove,
|
|
23
24
|
className,
|
|
24
25
|
}: PromoCodeInputProps) {
|
|
26
|
+
const t = useTranslations("cart");
|
|
27
|
+
const tc = useTranslations("common");
|
|
25
28
|
const [code, setCode] = useState("");
|
|
26
29
|
const [isLoading, setIsLoading] = useState(false);
|
|
27
30
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -38,13 +41,13 @@ export function PromoCodeInput({
|
|
|
38
41
|
const result = await onApply(code.trim());
|
|
39
42
|
|
|
40
43
|
if (result.success) {
|
|
41
|
-
setSuccess(result.message || "
|
|
44
|
+
setSuccess(result.message || t("promoApplied"));
|
|
42
45
|
setCode("");
|
|
43
46
|
} else {
|
|
44
|
-
setError(result.message || "
|
|
47
|
+
setError(result.message || t("promoError"));
|
|
45
48
|
}
|
|
46
49
|
} catch (err) {
|
|
47
|
-
setError("
|
|
50
|
+
setError(t("promoError"));
|
|
48
51
|
} finally {
|
|
49
52
|
setIsLoading(false);
|
|
50
53
|
}
|
|
@@ -88,7 +91,7 @@ export function PromoCodeInput({
|
|
|
88
91
|
<Tag className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
89
92
|
<Input
|
|
90
93
|
type="text"
|
|
91
|
-
placeholder="
|
|
94
|
+
placeholder={t("enterPromoCode")}
|
|
92
95
|
value={code}
|
|
93
96
|
onChange={(e) => setCode(e.target.value.toUpperCase())}
|
|
94
97
|
onKeyDown={(e) => e.key === "Enter" && handleApply()}
|
|
@@ -101,7 +104,7 @@ export function PromoCodeInput({
|
|
|
101
104
|
disabled={!code.trim() || isLoading}
|
|
102
105
|
size="default"
|
|
103
106
|
>
|
|
104
|
-
{isLoading ? "
|
|
107
|
+
{isLoading ? t("applying") : tc("apply")}
|
|
105
108
|
</Button>
|
|
106
109
|
</div>
|
|
107
110
|
|