@doswiftly/cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +357 -0
- package/bin/doswiftly.js +2 -0
- package/dist/commands/auth-github.d.ts +6 -0
- package/dist/commands/auth-github.d.ts.map +1 -0
- package/dist/commands/auth-github.js +89 -0
- package/dist/commands/auth-github.js.map +1 -0
- package/dist/commands/auth-token.d.ts +12 -0
- package/dist/commands/auth-token.d.ts.map +1 -0
- package/dist/commands/auth-token.js +43 -0
- package/dist/commands/auth-token.js.map +1 -0
- package/dist/commands/auth.d.ts +22 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +348 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/check.d.ts +5 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +234 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +104 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/deploy.d.ts +37 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +580 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dev.d.ts +8 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +83 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +363 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/domain.d.ts +13 -0
- package/dist/commands/domain.d.ts.map +1 -0
- package/dist/commands/domain.js +128 -0
- package/dist/commands/domain.js.map +1 -0
- package/dist/commands/env.d.ts +25 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +228 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +1028 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/inspect.d.ts +12 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +162 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/migrate.d.ts +18 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +355 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/preview.d.ts +29 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +199 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/proxy.d.ts +9 -0
- package/dist/commands/proxy.d.ts.map +1 -0
- package/dist/commands/proxy.js +37 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/commands/sdk.d.ts +5 -0
- package/dist/commands/sdk.d.ts.map +1 -0
- package/dist/commands/sdk.js +82 -0
- package/dist/commands/sdk.js.map +1 -0
- package/dist/commands/template.d.ts +107 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +1309 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/types.d.ts +5 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +82 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +103 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/upgrade.d.ts +18 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +55 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/verify.d.ts +5 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +232 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/whoami.d.ts +5 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +60 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config/types.d.ts +173 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +48 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +416 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-url.d.ts +14 -0
- package/dist/lib/api-url.d.ts.map +1 -0
- package/dist/lib/api-url.js +24 -0
- package/dist/lib/api-url.js.map +1 -0
- package/dist/lib/api.d.ts +67 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +36 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +39 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +195 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/env-storage.d.ts +140 -0
- package/dist/lib/env-storage.d.ts.map +1 -0
- package/dist/lib/env-storage.js +464 -0
- package/dist/lib/env-storage.js.map +1 -0
- package/dist/lib/errors.d.ts +61 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +204 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/i18n.d.ts +99 -0
- package/dist/lib/i18n.d.ts.map +1 -0
- package/dist/lib/i18n.js +184 -0
- package/dist/lib/i18n.js.map +1 -0
- package/dist/lib/logger.d.ts +95 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +168 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/package-manager.d.ts +91 -0
- package/dist/lib/package-manager.d.ts.map +1 -0
- package/dist/lib/package-manager.js +205 -0
- package/dist/lib/package-manager.js.map +1 -0
- package/dist/lib/proxy-server.d.ts +24 -0
- package/dist/lib/proxy-server.d.ts.map +1 -0
- package/dist/lib/proxy-server.js +173 -0
- package/dist/lib/proxy-server.js.map +1 -0
- package/dist/lib/select-with-back.d.ts +34 -0
- package/dist/lib/select-with-back.d.ts.map +1 -0
- package/dist/lib/select-with-back.js +94 -0
- package/dist/lib/select-with-back.js.map +1 -0
- package/dist/lib/shared-api-client.d.ts +40 -0
- package/dist/lib/shared-api-client.d.ts.map +1 -0
- package/dist/lib/shared-api-client.js +92 -0
- package/dist/lib/shared-api-client.js.map +1 -0
- package/dist/lib/wizard-engine.d.ts +128 -0
- package/dist/lib/wizard-engine.d.ts.map +1 -0
- package/dist/lib/wizard-engine.js +168 -0
- package/dist/lib/wizard-engine.js.map +1 -0
- package/package.json +85 -0
- package/templates/storefront-minimal/.env.example +10 -0
- package/templates/storefront-minimal/.github/workflows/build-template.yml +109 -0
- package/templates/storefront-minimal/app/globals.css +18 -0
- package/templates/storefront-minimal/app/layout.tsx +26 -0
- package/templates/storefront-minimal/app/page.tsx +93 -0
- package/templates/storefront-minimal/lib/graphql-client.ts +23 -0
- package/templates/storefront-minimal/next.config.ts +15 -0
- package/templates/storefront-minimal/open-next.config.ts +3 -0
- package/templates/storefront-minimal/package.json +30 -0
- package/templates/storefront-minimal/postcss.config.mjs +5 -0
- package/templates/storefront-minimal/tailwind.config.ts +14 -0
- package/templates/storefront-minimal/tsconfig.json +27 -0
- package/templates/storefront-minimal/wrangler.toml +9 -0
- package/templates/storefront-nextjs/.env.example +68 -0
- package/templates/storefront-nextjs/.github/workflows/build-template.yml +109 -0
- package/templates/storefront-nextjs/.github/workflows/deploy.yml +25 -0
- package/templates/storefront-nextjs/.github/workflows/preview.yml +22 -0
- package/templates/storefront-nextjs/README.md +520 -0
- package/templates/storefront-nextjs/app/account/orders/page.tsx +216 -0
- package/templates/storefront-nextjs/app/account/page.tsx +167 -0
- package/templates/storefront-nextjs/app/auth/login/page.tsx +135 -0
- package/templates/storefront-nextjs/app/auth/register/page.tsx +228 -0
- package/templates/storefront-nextjs/app/cart/page.tsx +263 -0
- package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +200 -0
- package/templates/storefront-nextjs/app/categories/page.tsx +58 -0
- package/templates/storefront-nextjs/app/checkout/page.tsx +351 -0
- package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +158 -0
- package/templates/storefront-nextjs/app/collections/page.tsx +61 -0
- package/templates/storefront-nextjs/app/globals.css +98 -0
- package/templates/storefront-nextjs/app/layout.tsx +39 -0
- package/templates/storefront-nextjs/app/page.tsx +136 -0
- package/templates/storefront-nextjs/app/products/[slug]/page.tsx +119 -0
- package/templates/storefront-nextjs/app/products/page.tsx +107 -0
- package/templates/storefront-nextjs/app/search/page.tsx +127 -0
- package/templates/storefront-nextjs/components/auth/auth-guard.tsx +94 -0
- package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +77 -0
- package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +29 -0
- package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +217 -0
- package/templates/storefront-nextjs/components/commerce/pagination.tsx +62 -0
- package/templates/storefront-nextjs/components/commerce/product-actions.tsx +135 -0
- package/templates/storefront-nextjs/components/commerce/product-filters.tsx +109 -0
- package/templates/storefront-nextjs/components/commerce/product-price.tsx +375 -0
- package/templates/storefront-nextjs/components/commerce/search-input.tsx +178 -0
- package/templates/storefront-nextjs/components/commerce/sort-select.tsx +64 -0
- package/templates/storefront-nextjs/components/commerce/variant-selector.tsx +210 -0
- package/templates/storefront-nextjs/components/layout/footer.tsx +107 -0
- package/templates/storefront-nextjs/components/layout/header.tsx +104 -0
- package/templates/storefront-nextjs/components/providers.tsx +62 -0
- package/templates/storefront-nextjs/lib/auth/routes.ts +52 -0
- package/templates/storefront-nextjs/lib/currency.tsx +140 -0
- package/templates/storefront-nextjs/lib/format.ts +159 -0
- package/templates/storefront-nextjs/lib/graphql-queries.ts +629 -0
- package/templates/storefront-nextjs/lib/hooks.ts +30 -0
- package/templates/storefront-nextjs/middleware.ts +80 -0
- package/templates/storefront-nextjs/next.config.ts +37 -0
- package/templates/storefront-nextjs/open-next.config.ts +3 -0
- package/templates/storefront-nextjs/package.dev.json +30 -0
- package/templates/storefront-nextjs/package.json +32 -0
- package/templates/storefront-nextjs/package.json.template +32 -0
- package/templates/storefront-nextjs/postcss.config.mjs +8 -0
- package/templates/storefront-nextjs/tailwind.config.ts +111 -0
- package/templates/storefront-nextjs/tsconfig.json +27 -0
- package/templates/storefront-nextjs/wrangler.toml +9 -0
- package/templates/storefront-nextjs-shadcn/.env.example +68 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +109 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +25 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +22 -0
- package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +282 -0
- package/templates/storefront-nextjs-shadcn/CLAUDE.md +96 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +190 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +263 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +135 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +142 -0
- package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +448 -0
- package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +307 -0
- package/templates/storefront-nextjs-shadcn/README.md +195 -0
- package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +245 -0
- package/templates/storefront-nextjs-shadcn/app/about/page.tsx +34 -0
- package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +215 -0
- package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +484 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +128 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/tracking/page.tsx +206 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +80 -0
- package/templates/storefront-nextjs-shadcn/app/account/page.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +195 -0
- package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +87 -0
- package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +125 -0
- package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +131 -0
- package/templates/storefront-nextjs-shadcn/app/auth/login/page.tsx +24 -0
- package/templates/storefront-nextjs-shadcn/app/auth/register/page.tsx +20 -0
- package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +323 -0
- package/templates/storefront-nextjs-shadcn/app/blog/page.tsx +159 -0
- package/templates/storefront-nextjs-shadcn/app/brands/[slug]/page.tsx +170 -0
- package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +73 -0
- package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +165 -0
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +78 -0
- package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +75 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +1752 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/success/[orderId]/page.tsx +256 -0
- package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +74 -0
- package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +75 -0
- package/templates/storefront-nextjs-shadcn/app/contact/page.tsx +114 -0
- package/templates/storefront-nextjs-shadcn/app/error.tsx +90 -0
- package/templates/storefront-nextjs-shadcn/app/globals.css +125 -0
- package/templates/storefront-nextjs-shadcn/app/layout.tsx +57 -0
- package/templates/storefront-nextjs-shadcn/app/not-found.tsx +68 -0
- package/templates/storefront-nextjs-shadcn/app/page.tsx +21 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +246 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +343 -0
- package/templates/storefront-nextjs-shadcn/app/products/page.tsx +25 -0
- package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +192 -0
- package/templates/storefront-nextjs-shadcn/app/returns/page.tsx +77 -0
- package/templates/storefront-nextjs-shadcn/app/robots.ts +53 -0
- package/templates/storefront-nextjs-shadcn/app/search/page.tsx +16 -0
- package/templates/storefront-nextjs-shadcn/app/search/search-client.tsx +47 -0
- package/templates/storefront-nextjs-shadcn/app/shipping/page.tsx +62 -0
- package/templates/storefront-nextjs-shadcn/app/sitemap.ts +144 -0
- package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +179 -0
- package/templates/storefront-nextjs-shadcn/codegen.ts +51 -0
- package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +348 -0
- package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +144 -0
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +258 -0
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +132 -0
- package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +188 -0
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +305 -0
- package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +240 -0
- package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +177 -0
- package/templates/storefront-nextjs-shadcn/components/blog/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +119 -0
- package/templates/storefront-nextjs-shadcn/components/brand/brand-grid.tsx +64 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +140 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +48 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +112 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +84 -0
- package/templates/storefront-nextjs-shadcn/components/cart/index.ts +17 -0
- package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +121 -0
- package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +162 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/index.ts +25 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +187 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +160 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +154 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +225 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +62 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +158 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +174 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/variant-selector.tsx +210 -0
- package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +97 -0
- package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +187 -0
- package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +151 -0
- package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +166 -0
- package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +245 -0
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +246 -0
- package/templates/storefront-nextjs-shadcn/components/discount/index.ts +19 -0
- package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +113 -0
- package/templates/storefront-nextjs-shadcn/components/error/index.ts +7 -0
- package/templates/storefront-nextjs-shadcn/components/filters/attribute-filter.tsx +153 -0
- package/templates/storefront-nextjs-shadcn/components/filters/checkbox-group-filter.tsx +167 -0
- package/templates/storefront-nextjs-shadcn/components/filters/color-swatch-filter.tsx +176 -0
- package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +220 -0
- package/templates/storefront-nextjs-shadcn/components/filters/index.ts +36 -0
- package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +193 -0
- package/templates/storefront-nextjs-shadcn/components/filters/toggle-filter.tsx +132 -0
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +321 -0
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +309 -0
- package/templates/storefront-nextjs-shadcn/components/gift-card/index.ts +24 -0
- package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +72 -0
- package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +85 -0
- package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +34 -0
- package/templates/storefront-nextjs-shadcn/components/home/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +108 -0
- package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +133 -0
- package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +341 -0
- package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +128 -0
- package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +147 -0
- package/templates/storefront-nextjs-shadcn/components/layout/index.ts +9 -0
- package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +211 -0
- package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +95 -0
- package/templates/storefront-nextjs-shadcn/components/layout/theme-switcher.tsx +192 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/index.ts +11 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +93 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +177 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +250 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +217 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/tier-badge.tsx +106 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +131 -0
- package/templates/storefront-nextjs-shadcn/components/order/delivery-estimate.tsx +196 -0
- package/templates/storefront-nextjs-shadcn/components/order/index.ts +11 -0
- package/templates/storefront-nextjs-shadcn/components/order/order-tracking.tsx +200 -0
- package/templates/storefront-nextjs-shadcn/components/order/shipment-card.tsx +407 -0
- package/templates/storefront-nextjs-shadcn/components/order/tracking-status.tsx +222 -0
- package/templates/storefront-nextjs-shadcn/components/order/tracking-timeline.tsx +205 -0
- package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +161 -0
- package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +250 -0
- package/templates/storefront-nextjs-shadcn/components/product/discount-badge.tsx +196 -0
- package/templates/storefront-nextjs-shadcn/components/product/index.ts +41 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +147 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +217 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-gallery.tsx +143 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +83 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +155 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +158 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-quantity-selector.tsx +111 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +238 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +58 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +169 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +220 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +338 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-summary.tsx +143 -0
- package/templates/storefront-nextjs-shadcn/components/product/sale-countdown.tsx +166 -0
- package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +213 -0
- package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +57 -0
- package/templates/storefront-nextjs-shadcn/components/product/stock-indicator.tsx +91 -0
- package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +103 -0
- package/templates/storefront-nextjs-shadcn/components/providers/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/providers/query-provider.tsx +260 -0
- package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +13 -0
- package/templates/storefront-nextjs-shadcn/components/returns/index.ts +26 -0
- package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +608 -0
- package/templates/storefront-nextjs-shadcn/components/returns/return-status-card.tsx +554 -0
- package/templates/storefront-nextjs-shadcn/components/search/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +140 -0
- package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +58 -0
- package/templates/storefront-nextjs-shadcn/components/search/search-suggestions.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/components/seo/index.ts +12 -0
- package/templates/storefront-nextjs-shadcn/components/seo/json-ld.tsx +56 -0
- package/templates/storefront-nextjs-shadcn/components/seo/product-json-ld.ts +167 -0
- package/templates/storefront-nextjs-shadcn/components/shipping/index.ts +16 -0
- package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +337 -0
- package/templates/storefront-nextjs-shadcn/components/ui/accordion.tsx +153 -0
- package/templates/storefront-nextjs-shadcn/components/ui/alert.tsx +59 -0
- package/templates/storefront-nextjs-shadcn/components/ui/badge.tsx +34 -0
- package/templates/storefront-nextjs-shadcn/components/ui/button.tsx +51 -0
- package/templates/storefront-nextjs-shadcn/components/ui/card.tsx +77 -0
- package/templates/storefront-nextjs-shadcn/components/ui/checkbox.tsx +30 -0
- package/templates/storefront-nextjs-shadcn/components/ui/dialog.tsx +137 -0
- package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +207 -0
- package/templates/storefront-nextjs-shadcn/components/ui/index.ts +67 -0
- package/templates/storefront-nextjs-shadcn/components/ui/input.tsx +65 -0
- package/templates/storefront-nextjs-shadcn/components/ui/label.tsx +26 -0
- package/templates/storefront-nextjs-shadcn/components/ui/pagination.tsx +205 -0
- package/templates/storefront-nextjs-shadcn/components/ui/radio-group.tsx +44 -0
- package/templates/storefront-nextjs-shadcn/components/ui/select.tsx +160 -0
- package/templates/storefront-nextjs-shadcn/components/ui/separator.tsx +28 -0
- package/templates/storefront-nextjs-shadcn/components/ui/skeleton.tsx +20 -0
- package/templates/storefront-nextjs-shadcn/components/ui/spinner.tsx +82 -0
- package/templates/storefront-nextjs-shadcn/components/ui/tabs.tsx +119 -0
- package/templates/storefront-nextjs-shadcn/components/ui/toast.tsx +96 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/index.ts +9 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +148 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +47 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +165 -0
- package/templates/storefront-nextjs-shadcn/components.json +19 -0
- package/templates/storefront-nextjs-shadcn/generated/.gitkeep +2 -0
- package/templates/storefront-nextjs-shadcn/graphql/.gitkeep +31 -0
- package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +168 -0
- package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +160 -0
- package/templates/storefront-nextjs-shadcn/hooks/index.ts +9 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +310 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +286 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +110 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-filter-params.test.ts +173 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-filter-params.ts +298 -0
- package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +220 -0
- package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +57 -0
- package/templates/storefront-nextjs-shadcn/lib/config.ts +46 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +254 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/README.md +464 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +328 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +295 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +27 -0
- package/templates/storefront-nextjs-shadcn/lib/format.test.ts +397 -0
- package/templates/storefront-nextjs-shadcn/lib/format.ts +226 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +109 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +1183 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +267 -0
- package/templates/storefront-nextjs-shadcn/lib/hooks.ts +30 -0
- package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +89 -0
- package/templates/storefront-nextjs-shadcn/lib/utils.ts +6 -0
- package/templates/storefront-nextjs-shadcn/next.config.ts +47 -0
- package/templates/storefront-nextjs-shadcn/open-next.config.ts +3 -0
- package/templates/storefront-nextjs-shadcn/package.dev.json +30 -0
- package/templates/storefront-nextjs-shadcn/package.json +60 -0
- package/templates/storefront-nextjs-shadcn/package.json.template +46 -0
- package/templates/storefront-nextjs-shadcn/postcss.config.mjs +8 -0
- package/templates/storefront-nextjs-shadcn/proxy.ts +80 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/apple-pay.svg +8 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/bank-transfer.svg +10 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/blik.svg +6 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/cash-on-delivery.svg +11 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/google-pay.svg +11 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/mastercard.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/paypal.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/payu.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/przelewy24.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/stripe.svg +4 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/visa.svg +5 -0
- package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +66 -0
- package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +56 -0
- package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +184 -0
- package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +103 -0
- package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +291 -0
- package/templates/storefront-nextjs-shadcn/tailwind.config.ts +111 -0
- package/templates/storefront-nextjs-shadcn/tsconfig.json +27 -0
- package/templates/storefront-nextjs-shadcn/wrangler.toml +9 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides login, logout, and authentication state management.
|
|
5
|
+
*
|
|
6
|
+
* Security Model:
|
|
7
|
+
* 1. Login: Calls GraphQL mutation → Stores token in httpOnly cookie via API route
|
|
8
|
+
* 2. Logout: Calls GraphQL mutation → Clears httpOnly cookie via API route
|
|
9
|
+
* 3. Token validation: Happens on GraphQL backend (not client-side)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* - Login/Register pages: Call login() after successful mutation
|
|
13
|
+
* - Protected pages: Use AuthGuard component (checks cookie existence)
|
|
14
|
+
* - Logout button: Call logout() to clear session
|
|
15
|
+
*
|
|
16
|
+
* @see lib/auth/cookies.ts - Cookie helper functions
|
|
17
|
+
* @see lib/auth/routes.ts - Route configuration
|
|
18
|
+
* @see proxy.ts - Server-side route protection
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* function LoginForm() {
|
|
23
|
+
* const { login, isLoading, error } = useAuth();
|
|
24
|
+
*
|
|
25
|
+
* const handleSubmit = async (e) => {
|
|
26
|
+
* e.preventDefault();
|
|
27
|
+
* const result = await login(email, password);
|
|
28
|
+
* if (result.userErrors.length === 0) {
|
|
29
|
+
* // Success - redirected to /account
|
|
30
|
+
* }
|
|
31
|
+
* };
|
|
32
|
+
*
|
|
33
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
"use client";
|
|
39
|
+
|
|
40
|
+
import { useState } from "react";
|
|
41
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
42
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
43
|
+
import { getGraphQLClient } from "@/lib/graphql/client";
|
|
44
|
+
import {
|
|
45
|
+
CustomerLoginDocument,
|
|
46
|
+
CustomerLogoutDocument,
|
|
47
|
+
CustomerTokenRenewDocument,
|
|
48
|
+
type CustomerLoginMutation,
|
|
49
|
+
type CustomerLogoutMutation,
|
|
50
|
+
type CustomerTokenRenewMutation,
|
|
51
|
+
} from "@/generated/graphql";
|
|
52
|
+
import { setAuthToken, clearAuthToken, getAuthToken } from "@/lib/auth/cookies";
|
|
53
|
+
import { redirects } from "@/lib/auth/routes";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Login input
|
|
57
|
+
*/
|
|
58
|
+
export interface LoginInput {
|
|
59
|
+
email: string;
|
|
60
|
+
password: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Login result
|
|
65
|
+
*/
|
|
66
|
+
export interface LoginResult {
|
|
67
|
+
success: boolean;
|
|
68
|
+
userErrors: Array<{ message: string; field?: string[] }>;
|
|
69
|
+
accessToken?: string;
|
|
70
|
+
expiresAt?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Logout result
|
|
75
|
+
*/
|
|
76
|
+
export interface LogoutResult {
|
|
77
|
+
success: boolean;
|
|
78
|
+
userErrors: Array<{ message: string; field?: string[] }>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Token renew result
|
|
83
|
+
*/
|
|
84
|
+
export interface TokenRenewResult {
|
|
85
|
+
success: boolean;
|
|
86
|
+
userErrors: Array<{ message: string; field?: string[] }>;
|
|
87
|
+
accessToken?: string;
|
|
88
|
+
expiresAt?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Authentication hook
|
|
93
|
+
*/
|
|
94
|
+
export function useAuth() {
|
|
95
|
+
const router = useRouter();
|
|
96
|
+
const searchParams = useSearchParams();
|
|
97
|
+
const queryClient = useQueryClient();
|
|
98
|
+
const client = getGraphQLClient();
|
|
99
|
+
|
|
100
|
+
const [error, setError] = useState<string | null>(null);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Login mutation
|
|
104
|
+
*/
|
|
105
|
+
const loginMutation = useMutation({
|
|
106
|
+
mutationFn: async (input: LoginInput): Promise<LoginResult> => {
|
|
107
|
+
const data = await client.request<CustomerLoginMutation>(
|
|
108
|
+
CustomerLoginDocument,
|
|
109
|
+
{ input }
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const { customerAccessToken, userErrors } =
|
|
113
|
+
data.customerAccessTokenCreate;
|
|
114
|
+
|
|
115
|
+
if (userErrors && userErrors.length > 0) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
userErrors: userErrors.map((e: any) => ({
|
|
119
|
+
message: e.message,
|
|
120
|
+
field: e.field || undefined,
|
|
121
|
+
})),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!customerAccessToken) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
userErrors: [{ message: "Failed to create access token" }],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Store token in httpOnly cookie via API route
|
|
133
|
+
await setAuthToken(customerAccessToken.accessToken);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
success: true,
|
|
137
|
+
userErrors: [],
|
|
138
|
+
accessToken: customerAccessToken.accessToken,
|
|
139
|
+
expiresAt: customerAccessToken.expiresAt,
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
onSuccess: (result: LoginResult) => {
|
|
143
|
+
if (result.success) {
|
|
144
|
+
// Clear any cached queries that might need authentication
|
|
145
|
+
queryClient.invalidateQueries();
|
|
146
|
+
|
|
147
|
+
// Redirect to original destination or account page
|
|
148
|
+
const redirect = searchParams.get("redirect");
|
|
149
|
+
router.push(redirect || redirects.authenticated);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
onError: (err: unknown) => {
|
|
153
|
+
setError(err instanceof Error ? err.message : "Login failed");
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Logout mutation
|
|
159
|
+
*/
|
|
160
|
+
const logoutMutation = useMutation({
|
|
161
|
+
mutationFn: async (): Promise<LogoutResult> => {
|
|
162
|
+
const token = getAuthToken();
|
|
163
|
+
|
|
164
|
+
if (!token) {
|
|
165
|
+
// No token to logout
|
|
166
|
+
return {
|
|
167
|
+
success: true,
|
|
168
|
+
userErrors: [],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const data = await client.request<CustomerLogoutMutation>(
|
|
173
|
+
CustomerLogoutDocument,
|
|
174
|
+
{ customerAccessToken: token }
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const { userErrors } = data.customerAccessTokenDelete;
|
|
178
|
+
|
|
179
|
+
if (userErrors && userErrors.length > 0) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
userErrors: userErrors.map((e: any) => ({
|
|
183
|
+
message: e.message,
|
|
184
|
+
field: e.field || undefined,
|
|
185
|
+
})),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Clear httpOnly cookie via API route
|
|
190
|
+
await clearAuthToken();
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
userErrors: [],
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
onSuccess: (result: LogoutResult) => {
|
|
198
|
+
if (result.success) {
|
|
199
|
+
// Clear all cached queries
|
|
200
|
+
queryClient.clear();
|
|
201
|
+
|
|
202
|
+
// Redirect to home page
|
|
203
|
+
router.push("/");
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
onError: (err: unknown) => {
|
|
207
|
+
setError(err instanceof Error ? err.message : "Logout failed");
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Token renew mutation
|
|
213
|
+
*/
|
|
214
|
+
const renewTokenMutation = useMutation({
|
|
215
|
+
mutationFn: async (): Promise<TokenRenewResult> => {
|
|
216
|
+
const token = getAuthToken();
|
|
217
|
+
|
|
218
|
+
if (!token) {
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
userErrors: [{ message: "No token to renew" }],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const data = await client.request<CustomerTokenRenewMutation>(
|
|
226
|
+
CustomerTokenRenewDocument,
|
|
227
|
+
{ customerAccessToken: token }
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const { customerAccessToken, userErrors } =
|
|
231
|
+
data.customerAccessTokenRenew;
|
|
232
|
+
|
|
233
|
+
if (userErrors && userErrors.length > 0) {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
userErrors: userErrors.map((e: any) => ({
|
|
237
|
+
message: e.message,
|
|
238
|
+
field: e.field || undefined,
|
|
239
|
+
})),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!customerAccessToken) {
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
userErrors: [{ message: "Failed to renew access token" }],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Update token in httpOnly cookie via API route
|
|
251
|
+
await setAuthToken(customerAccessToken.accessToken);
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
success: true,
|
|
255
|
+
userErrors: [],
|
|
256
|
+
accessToken: customerAccessToken.accessToken,
|
|
257
|
+
expiresAt: customerAccessToken.expiresAt,
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
onError: (err: unknown) => {
|
|
261
|
+
setError(err instanceof Error ? err.message : "Token renewal failed");
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Login function
|
|
267
|
+
*/
|
|
268
|
+
const login = async (email: string, password: string): Promise<LoginResult> => {
|
|
269
|
+
setError(null);
|
|
270
|
+
return loginMutation.mutateAsync({ email, password });
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Logout function
|
|
275
|
+
*/
|
|
276
|
+
const logout = async (): Promise<LogoutResult> => {
|
|
277
|
+
setError(null);
|
|
278
|
+
return logoutMutation.mutateAsync();
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Renew token function
|
|
283
|
+
*/
|
|
284
|
+
const renewToken = async (): Promise<TokenRenewResult> => {
|
|
285
|
+
setError(null);
|
|
286
|
+
return renewTokenMutation.mutateAsync();
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
// Functions
|
|
291
|
+
login,
|
|
292
|
+
logout,
|
|
293
|
+
renewToken,
|
|
294
|
+
|
|
295
|
+
// State
|
|
296
|
+
isLoggingIn: loginMutation.isPending,
|
|
297
|
+
isLoggingOut: logoutMutation.isPending,
|
|
298
|
+
isRenewingToken: renewTokenMutation.isPending,
|
|
299
|
+
isLoading:
|
|
300
|
+
loginMutation.isPending ||
|
|
301
|
+
logoutMutation.isPending ||
|
|
302
|
+
renewTokenMutation.isPending,
|
|
303
|
+
error,
|
|
304
|
+
|
|
305
|
+
// Results
|
|
306
|
+
loginResult: loginMutation.data,
|
|
307
|
+
logoutResult: logoutMutation.data,
|
|
308
|
+
renewResult: renewTokenMutation.data,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useRef } from 'react';
|
|
4
|
+
import { useCartStore } from '@/stores/cart-store';
|
|
5
|
+
import { useCartCreate, useCartLinesAdd, useCartLinesUpdate, useCartLinesRemove } from '@/lib/graphql/hooks';
|
|
6
|
+
import { toast } from 'sonner';
|
|
7
|
+
|
|
8
|
+
// Debounce delay for quantity updates (prevents rate limiting)
|
|
9
|
+
const QUANTITY_UPDATE_DEBOUNCE_MS = 500;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook for cart mutations — server-only, no local state manipulation.
|
|
13
|
+
*
|
|
14
|
+
* All mutations go through GraphQL. React Query cache invalidation
|
|
15
|
+
* (configured in hooks.ts) automatically updates all consumers via useCartSync.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const { addToCart, updateQuantity, removeFromCart } = useCartActions();
|
|
20
|
+
*
|
|
21
|
+
* await addToCart({
|
|
22
|
+
* variantId: 'variant-123',
|
|
23
|
+
* productId: 'product-456',
|
|
24
|
+
* productTitle: 'T-Shirt',
|
|
25
|
+
* variantTitle: 'Large / Blue',
|
|
26
|
+
* price: { amount: '29.99', currencyCode: 'USD' },
|
|
27
|
+
* quantity: 1
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // updateQuantity and removeFromCart take lineId (not variantId)
|
|
31
|
+
* updateQuantity('line-abc', 3);
|
|
32
|
+
* removeFromCart('line-abc');
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function useCartActions() {
|
|
36
|
+
const {
|
|
37
|
+
setCartId,
|
|
38
|
+
clearCart,
|
|
39
|
+
openCart,
|
|
40
|
+
} = useCartStore();
|
|
41
|
+
|
|
42
|
+
// GraphQL mutations
|
|
43
|
+
const createCartMutation = useCartCreate();
|
|
44
|
+
const addLinesMutation = useCartLinesAdd();
|
|
45
|
+
const updateLinesMutation = useCartLinesUpdate();
|
|
46
|
+
const removeLinesMutation = useCartLinesRemove();
|
|
47
|
+
|
|
48
|
+
// Debounce refs for quantity updates (prevents ThrottlerException on rapid clicks)
|
|
49
|
+
const updateTimeoutRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
|
|
50
|
+
const pendingUpdatesRef = useRef<Map<string, { lineId: string; quantity: number }>>(new Map());
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get or create cart ID.
|
|
54
|
+
* Reads cartId from store (fresh, not stale closure) or creates a new cart.
|
|
55
|
+
*/
|
|
56
|
+
const getOrCreateCartId = useCallback(async (forceNew: boolean = false): Promise<string> => {
|
|
57
|
+
const currentCartId = useCartStore.getState().cartId;
|
|
58
|
+
if (currentCartId && !forceNew) {
|
|
59
|
+
return currentCartId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const result = await createCartMutation.mutateAsync({ input: {} });
|
|
64
|
+
|
|
65
|
+
if (result.cartCreate.cart) {
|
|
66
|
+
const newCartId = result.cartCreate.cart.id;
|
|
67
|
+
setCartId(newCartId);
|
|
68
|
+
return newCartId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (result.cartCreate.userErrors?.length > 0) {
|
|
72
|
+
throw new Error(result.cartCreate.userErrors[0].message);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error('Failed to create cart');
|
|
76
|
+
} catch (error: any) {
|
|
77
|
+
console.error('Cart creation failed:', error);
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}, [setCartId, createCartMutation]);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if error is a "Cart not found" error (stale/expired cart)
|
|
84
|
+
*/
|
|
85
|
+
const isCartNotFoundError = (error: any): boolean => {
|
|
86
|
+
const message = error?.message || '';
|
|
87
|
+
return message.toLowerCase().includes('cart not found') ||
|
|
88
|
+
message.toLowerCase().includes('cart does not exist');
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Add item to cart (server-only).
|
|
93
|
+
*
|
|
94
|
+
* Creates cart if needed. On "cart not found", clears cartId, creates new cart, retries once.
|
|
95
|
+
* React Query cache invalidation in hooks.ts updates all useCartSync consumers.
|
|
96
|
+
*/
|
|
97
|
+
const addToCart = useCallback(async (item: {
|
|
98
|
+
variantId: string;
|
|
99
|
+
productId: string;
|
|
100
|
+
productHandle?: string;
|
|
101
|
+
productTitle: string;
|
|
102
|
+
variantTitle: string;
|
|
103
|
+
price: { amount: string; currencyCode: string };
|
|
104
|
+
image?: { url: string; altText?: string | null } | null;
|
|
105
|
+
available?: boolean;
|
|
106
|
+
quantity?: number;
|
|
107
|
+
}, _options?: { _forceNewCart?: boolean }) => {
|
|
108
|
+
const forceNewCart = _options?._forceNewCart ?? false;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const cartId = await getOrCreateCartId(forceNewCart);
|
|
112
|
+
|
|
113
|
+
const result = await addLinesMutation.mutateAsync({
|
|
114
|
+
cartId,
|
|
115
|
+
lines: [{
|
|
116
|
+
merchandiseId: item.variantId,
|
|
117
|
+
quantity: item.quantity ?? 1,
|
|
118
|
+
}],
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (result.cartLinesAdd.userErrors?.length > 0) {
|
|
122
|
+
const errorMessage = result.cartLinesAdd.userErrors[0].message;
|
|
123
|
+
|
|
124
|
+
if (isCartNotFoundError({ message: errorMessage }) && !forceNewCart) {
|
|
125
|
+
console.warn('Cart expired, creating new cart and retrying...');
|
|
126
|
+
setCartId(null);
|
|
127
|
+
return addToCart(item, { _forceNewCart: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
throw new Error(errorMessage);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Open cart drawer to show the added item
|
|
134
|
+
openCart();
|
|
135
|
+
toast.success('Added to cart');
|
|
136
|
+
} catch (error: any) {
|
|
137
|
+
if (isCartNotFoundError(error) && !forceNewCart) {
|
|
138
|
+
console.warn('Cart expired (caught), creating new cart and retrying...');
|
|
139
|
+
setCartId(null);
|
|
140
|
+
return addToCart(item, { _forceNewCart: true });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.error('Add to cart failed:', error);
|
|
144
|
+
toast.error(error.message || 'Failed to add to cart');
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}, [setCartId, openCart, getOrCreateCartId, addLinesMutation]);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Execute the actual GraphQL update for a pending quantity change
|
|
151
|
+
*/
|
|
152
|
+
const executeQuantityUpdate = useCallback(async (lineId: string, quantity: number) => {
|
|
153
|
+
try {
|
|
154
|
+
const currentCartId = useCartStore.getState().cartId;
|
|
155
|
+
if (!currentCartId) {
|
|
156
|
+
throw new Error('No cart found');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result = await updateLinesMutation.mutateAsync({
|
|
160
|
+
cartId: currentCartId,
|
|
161
|
+
lines: [{
|
|
162
|
+
id: lineId,
|
|
163
|
+
quantity,
|
|
164
|
+
}],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (result.cartLinesUpdate.userErrors?.length > 0) {
|
|
168
|
+
const errorMessage = result.cartLinesUpdate.userErrors[0].message;
|
|
169
|
+
|
|
170
|
+
if (isCartNotFoundError({ message: errorMessage })) {
|
|
171
|
+
console.warn('Cart expired during update, clearing cart');
|
|
172
|
+
clearCart();
|
|
173
|
+
toast.error('Your cart has expired. Please add items again.');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
throw new Error(errorMessage);
|
|
178
|
+
}
|
|
179
|
+
} catch (error: any) {
|
|
180
|
+
if (isCartNotFoundError(error)) {
|
|
181
|
+
console.warn('Cart expired during update (caught), clearing cart');
|
|
182
|
+
clearCart();
|
|
183
|
+
toast.error('Your cart has expired. Please add items again.');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.error('Update quantity failed:', error);
|
|
188
|
+
toast.error(error.message || 'Failed to update quantity');
|
|
189
|
+
}
|
|
190
|
+
}, [updateLinesMutation, clearCart]);
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Remove item from cart by line ID.
|
|
194
|
+
*
|
|
195
|
+
* Silently handles expired cart (just clears stale cartId).
|
|
196
|
+
*/
|
|
197
|
+
const removeFromCart = useCallback(async (lineId: string) => {
|
|
198
|
+
try {
|
|
199
|
+
const currentCartId = useCartStore.getState().cartId;
|
|
200
|
+
if (!currentCartId) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const result = await removeLinesMutation.mutateAsync({
|
|
205
|
+
cartId: currentCartId,
|
|
206
|
+
lineIds: [lineId],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (result.cartLinesRemove.userErrors?.length > 0) {
|
|
210
|
+
const errorMessage = result.cartLinesRemove.userErrors[0].message;
|
|
211
|
+
|
|
212
|
+
if (isCartNotFoundError({ message: errorMessage })) {
|
|
213
|
+
console.warn('Cart expired during remove, clearing stale cartId');
|
|
214
|
+
setCartId(null);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
throw new Error(errorMessage);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
toast.success('Removed from cart');
|
|
222
|
+
} catch (error: any) {
|
|
223
|
+
if (isCartNotFoundError(error)) {
|
|
224
|
+
console.warn('Cart expired during remove (caught), clearing stale cartId');
|
|
225
|
+
setCartId(null);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.error('Remove from cart failed:', error);
|
|
230
|
+
toast.error(error.message || 'Failed to remove from cart');
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}, [setCartId, removeLinesMutation]);
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Update item quantity (debounced, takes lineId).
|
|
237
|
+
*
|
|
238
|
+
* Debounces GraphQL API calls to prevent ThrottlerException on rapid clicks.
|
|
239
|
+
* If quantity <= 0, removes the item instead.
|
|
240
|
+
*/
|
|
241
|
+
const updateQuantity = useCallback((lineId: string, quantity: number) => {
|
|
242
|
+
if (quantity <= 0) {
|
|
243
|
+
removeFromCart(lineId);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Cancel any pending update for this line
|
|
248
|
+
const existingTimeout = updateTimeoutRef.current.get(lineId);
|
|
249
|
+
if (existingTimeout) {
|
|
250
|
+
clearTimeout(existingTimeout);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Store the pending update
|
|
254
|
+
pendingUpdatesRef.current.set(lineId, { lineId, quantity });
|
|
255
|
+
|
|
256
|
+
// Schedule debounced API call
|
|
257
|
+
const timeout = setTimeout(() => {
|
|
258
|
+
const pending = pendingUpdatesRef.current.get(lineId);
|
|
259
|
+
if (pending) {
|
|
260
|
+
pendingUpdatesRef.current.delete(lineId);
|
|
261
|
+
updateTimeoutRef.current.delete(lineId);
|
|
262
|
+
executeQuantityUpdate(pending.lineId, pending.quantity);
|
|
263
|
+
}
|
|
264
|
+
}, QUANTITY_UPDATE_DEBOUNCE_MS);
|
|
265
|
+
|
|
266
|
+
updateTimeoutRef.current.set(lineId, timeout);
|
|
267
|
+
}, [removeFromCart, executeQuantityUpdate]);
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Clear entire cart.
|
|
271
|
+
* Clears cartId in zustand persist → useCartSync returns empty.
|
|
272
|
+
*/
|
|
273
|
+
const clearEntireCart = useCallback(() => {
|
|
274
|
+
clearCart();
|
|
275
|
+
toast.success('Cart cleared');
|
|
276
|
+
}, [clearCart]);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
addToCart,
|
|
280
|
+
updateQuantity,
|
|
281
|
+
removeFromCart,
|
|
282
|
+
clearCart: clearEntireCart,
|
|
283
|
+
isLoading: createCartMutation.isPending || addLinesMutation.isPending ||
|
|
284
|
+
updateLinesMutation.isPending || removeLinesMutation.isPending,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { useCart } from "@/lib/graphql/hooks";
|
|
5
|
+
import { useCartStore } from "@/stores/cart-store";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Mapped cart item for display components.
|
|
9
|
+
* Server is the single source of truth — no client-side items[].
|
|
10
|
+
*/
|
|
11
|
+
export interface CartItemData {
|
|
12
|
+
lineId: string;
|
|
13
|
+
variantId: string;
|
|
14
|
+
productId: string;
|
|
15
|
+
productHandle?: string;
|
|
16
|
+
productTitle: string;
|
|
17
|
+
variantTitle: string;
|
|
18
|
+
productType?: string;
|
|
19
|
+
quantity: number;
|
|
20
|
+
price: { amount: string; currencyCode: string };
|
|
21
|
+
image?: { url: string; altText?: string | null } | null;
|
|
22
|
+
available: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Primary data source for cart display.
|
|
27
|
+
*
|
|
28
|
+
* Reads cart from GraphQL (server as source of truth).
|
|
29
|
+
* Maps GraphQL lines to display-friendly CartItemData.
|
|
30
|
+
* Detects and auto-clears stale cart IDs.
|
|
31
|
+
*/
|
|
32
|
+
export function useCartSync() {
|
|
33
|
+
const cartId = useCartStore((state) => state.cartId);
|
|
34
|
+
const isHydrated = useCartStore((state) => state.isHydrated);
|
|
35
|
+
const setCartId = useCartStore((state) => state.setCartId);
|
|
36
|
+
|
|
37
|
+
const { data, isLoading, error, refetch } = useCart(cartId, {
|
|
38
|
+
enabled: isHydrated && Boolean(cartId),
|
|
39
|
+
retry: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const cart = data?.cart;
|
|
43
|
+
|
|
44
|
+
// Detect stale cart: cartId exists but server returns nothing
|
|
45
|
+
const isStaleCart = isHydrated && Boolean(cartId) && !isLoading && !cart;
|
|
46
|
+
|
|
47
|
+
// Auto-clear stale cartId
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (isStaleCart) {
|
|
50
|
+
setCartId(null);
|
|
51
|
+
}
|
|
52
|
+
}, [isStaleCart, setCartId]);
|
|
53
|
+
|
|
54
|
+
// Map GraphQL lines to display-friendly items
|
|
55
|
+
const items: CartItemData[] = (cart?.lines ?? []).map((line: any) => {
|
|
56
|
+
const merchandiseTitle = line.merchandise.title ?? "";
|
|
57
|
+
// Hide generic variant names like "Default" or "Default Title"
|
|
58
|
+
const isDefaultVariant = /^default(\s+title)?$/i.test(merchandiseTitle);
|
|
59
|
+
const productTitle = line.productTitle || merchandiseTitle;
|
|
60
|
+
const variantTitle = isDefaultVariant ? "" : merchandiseTitle;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
lineId: line.id,
|
|
64
|
+
variantId: line.merchandise.id,
|
|
65
|
+
productId: line.productId || line.merchandise.id,
|
|
66
|
+
productHandle: line.productHandle,
|
|
67
|
+
productTitle,
|
|
68
|
+
variantTitle,
|
|
69
|
+
productType: line.productType,
|
|
70
|
+
quantity: line.quantity,
|
|
71
|
+
price: {
|
|
72
|
+
amount: line.merchandise.price.amount,
|
|
73
|
+
currencyCode: line.merchandise.price.currencyCode,
|
|
74
|
+
},
|
|
75
|
+
image: line.merchandise.image || null,
|
|
76
|
+
available: line.merchandise.available,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const totalQuantity = cart?.totalQuantity ?? 0;
|
|
81
|
+
const subtotal = cart?.cost?.subtotalAmount
|
|
82
|
+
? parseFloat(cart.cost.subtotalAmount.amount)
|
|
83
|
+
: 0;
|
|
84
|
+
const total = cart?.cost?.totalAmount
|
|
85
|
+
? parseFloat(cart.cost.totalAmount.amount)
|
|
86
|
+
: subtotal;
|
|
87
|
+
const currency = cart?.cost?.subtotalAmount?.currencyCode ?? "PLN";
|
|
88
|
+
|
|
89
|
+
// Discount data from server
|
|
90
|
+
const discountCodes: string[] = (cart?.discountCodes ?? [])
|
|
91
|
+
.filter((dc: any) => dc.applicable)
|
|
92
|
+
.map((dc: any) => dc.code);
|
|
93
|
+
const totalDiscount = subtotal - total;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
cart,
|
|
97
|
+
cartId,
|
|
98
|
+
items,
|
|
99
|
+
totalQuantity,
|
|
100
|
+
subtotal,
|
|
101
|
+
total,
|
|
102
|
+
currency,
|
|
103
|
+
discountCodes,
|
|
104
|
+
totalDiscount,
|
|
105
|
+
isLoading: !isHydrated || isLoading,
|
|
106
|
+
isStaleCart,
|
|
107
|
+
error,
|
|
108
|
+
refetch,
|
|
109
|
+
};
|
|
110
|
+
}
|