@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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useFilterParams hook
|
|
3
|
+
*
|
|
4
|
+
* Requirements: R35.20, R35.21, R35.22
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Note: This is a test specification file. In a real test setup, you would use
|
|
8
|
+
// @testing-library/react-hooks and mock next/navigation.
|
|
9
|
+
|
|
10
|
+
import type { AppliedFilters } from "@/components/filters/dynamic-attribute-filters";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* URL Parameter Parsing Tests (R35.20)
|
|
14
|
+
*/
|
|
15
|
+
describe("URL Parameter Parsing", () => {
|
|
16
|
+
describe("parseSearchParams", () => {
|
|
17
|
+
it("should parse discrete attribute filters from URL", () => {
|
|
18
|
+
// URL: ?attr_color=red,blue
|
|
19
|
+
const params = new URLSearchParams("attr_color=red,blue");
|
|
20
|
+
const expected: Partial<AppliedFilters> = {
|
|
21
|
+
attributes: { color: ["red", "blue"] },
|
|
22
|
+
};
|
|
23
|
+
// Implementation would parse this
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should parse multiple attribute filters", () => {
|
|
27
|
+
// URL: ?attr_color=red&attr_size=xl,xxl
|
|
28
|
+
const params = new URLSearchParams("attr_color=red&attr_size=xl,xxl");
|
|
29
|
+
const expected: Partial<AppliedFilters> = {
|
|
30
|
+
attributes: {
|
|
31
|
+
color: ["red"],
|
|
32
|
+
size: ["xl", "xxl"],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should parse range attribute filters", () => {
|
|
38
|
+
// URL: ?attr_weight_min=100&attr_weight_max=500
|
|
39
|
+
const params = new URLSearchParams("attr_weight_min=100&attr_weight_max=500");
|
|
40
|
+
const expected: Partial<AppliedFilters> = {
|
|
41
|
+
ranges: { weight: { min: 100, max: 500 } },
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should parse price filters", () => {
|
|
46
|
+
// URL: ?min_price=50&max_price=200
|
|
47
|
+
const params = new URLSearchParams("min_price=50&max_price=200");
|
|
48
|
+
const expected: Partial<AppliedFilters> = {
|
|
49
|
+
price: { min: 50, max: 200 },
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should parse category filter", () => {
|
|
54
|
+
// URL: ?category=category-id
|
|
55
|
+
const params = new URLSearchParams("category=category-id");
|
|
56
|
+
const expected: Partial<AppliedFilters> = {
|
|
57
|
+
categoryId: "category-id",
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should handle empty values", () => {
|
|
62
|
+
// URL: ?attr_color=
|
|
63
|
+
const params = new URLSearchParams("attr_color=");
|
|
64
|
+
const expected: Partial<AppliedFilters> = {
|
|
65
|
+
attributes: {},
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* URL Serialization Tests (R35.21)
|
|
73
|
+
*/
|
|
74
|
+
describe("URL Serialization", () => {
|
|
75
|
+
describe("serializeToSearchParams", () => {
|
|
76
|
+
it("should serialize discrete attribute filters", () => {
|
|
77
|
+
const filters: AppliedFilters = {
|
|
78
|
+
attributes: { color: ["red", "blue"] },
|
|
79
|
+
ranges: {},
|
|
80
|
+
};
|
|
81
|
+
// Expected URL: ?attr_color=red,blue
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should serialize range attribute filters", () => {
|
|
85
|
+
const filters: AppliedFilters = {
|
|
86
|
+
attributes: {},
|
|
87
|
+
ranges: { weight: { min: 100, max: 500 } },
|
|
88
|
+
};
|
|
89
|
+
// Expected URL: ?attr_weight_min=100&attr_weight_max=500
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should serialize price filters", () => {
|
|
93
|
+
const filters: AppliedFilters = {
|
|
94
|
+
attributes: {},
|
|
95
|
+
ranges: {},
|
|
96
|
+
price: { min: 50, max: 200 },
|
|
97
|
+
};
|
|
98
|
+
// Expected URL: ?min_price=50&max_price=200
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should preserve non-filter params", () => {
|
|
102
|
+
const filters: AppliedFilters = {
|
|
103
|
+
attributes: { color: ["red"] },
|
|
104
|
+
ranges: {},
|
|
105
|
+
};
|
|
106
|
+
const existingParams = new URLSearchParams("page=2&sort=price");
|
|
107
|
+
// Expected URL: ?page=2&sort=price&attr_color=red
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should omit empty filters", () => {
|
|
111
|
+
const filters: AppliedFilters = {
|
|
112
|
+
attributes: { color: [] },
|
|
113
|
+
ranges: { weight: {} },
|
|
114
|
+
};
|
|
115
|
+
// Expected URL: (empty or just non-filter params)
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Clear Filters Tests (R35.22)
|
|
122
|
+
*/
|
|
123
|
+
describe("Clear Filters", () => {
|
|
124
|
+
it("should clear single attribute filter", () => {
|
|
125
|
+
const initial: AppliedFilters = {
|
|
126
|
+
attributes: { color: ["red"], size: ["xl"] },
|
|
127
|
+
ranges: {},
|
|
128
|
+
};
|
|
129
|
+
// After clearing color:
|
|
130
|
+
const expected: AppliedFilters = {
|
|
131
|
+
attributes: { size: ["xl"] },
|
|
132
|
+
ranges: {},
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should clear all filters", () => {
|
|
137
|
+
const initial: AppliedFilters = {
|
|
138
|
+
attributes: { color: ["red"], size: ["xl"] },
|
|
139
|
+
ranges: { weight: { min: 100, max: 500 } },
|
|
140
|
+
price: { min: 50, max: 200 },
|
|
141
|
+
categoryId: "category-1",
|
|
142
|
+
};
|
|
143
|
+
const expected: AppliedFilters = {
|
|
144
|
+
attributes: {},
|
|
145
|
+
ranges: {},
|
|
146
|
+
price: undefined,
|
|
147
|
+
categoryId: undefined,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should maintain non-attribute params when clearing", () => {
|
|
152
|
+
// URL: ?page=2&sort=price&attr_color=red
|
|
153
|
+
// After clearAllFilters:
|
|
154
|
+
// URL: ?page=2&sort=price
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Debounce Tests
|
|
160
|
+
*/
|
|
161
|
+
describe("Debounce", () => {
|
|
162
|
+
it("should debounce URL updates", () => {
|
|
163
|
+
// Multiple rapid filter changes should result in single URL update
|
|
164
|
+
// after debounce delay (300ms default)
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should use custom debounce delay if provided", () => {
|
|
168
|
+
// With debounceMs: 500, updates should wait 500ms
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Export for test runner
|
|
173
|
+
export {};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useRef, useEffect } from "react";
|
|
4
|
+
import { useSearchParams, useRouter, usePathname } from "next/navigation";
|
|
5
|
+
import type { AppliedFilters } from "@/components/filters/dynamic-attribute-filters";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* useFilterParams - Hook for syncing filter state with URL
|
|
9
|
+
*
|
|
10
|
+
* URL parameter format:
|
|
11
|
+
* - Discrete attributes: ?attr_color=red,blue&attr_size=xl
|
|
12
|
+
* - Range attributes: ?attr_price_min=100&attr_price_max=500
|
|
13
|
+
* - Price filter: ?min_price=50&max_price=200
|
|
14
|
+
* - Category: ?category=category-id
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - Debounced URL updates (300ms)
|
|
18
|
+
* - Multi-value support (comma-separated)
|
|
19
|
+
* - Range value support (min/max pairs)
|
|
20
|
+
* - SSR-safe (uses useSearchParams)
|
|
21
|
+
*
|
|
22
|
+
* Requirements: R35.20, R35.21, R35.22
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const ATTR_PREFIX = "attr_";
|
|
26
|
+
const RANGE_MIN_SUFFIX = "_min";
|
|
27
|
+
const RANGE_MAX_SUFFIX = "_max";
|
|
28
|
+
const DEBOUNCE_MS = 300;
|
|
29
|
+
|
|
30
|
+
export interface UseFilterParamsOptions {
|
|
31
|
+
/** Debounce delay in ms (default: 300) */
|
|
32
|
+
debounceMs?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UseFilterParamsReturn {
|
|
36
|
+
/** Current applied filters parsed from URL */
|
|
37
|
+
appliedFilters: AppliedFilters;
|
|
38
|
+
/** Update attribute filter values */
|
|
39
|
+
setAttributeFilter: (attributeId: string, values: string[]) => void;
|
|
40
|
+
/** Update range filter values */
|
|
41
|
+
setRangeFilter: (attributeId: string, range: { min?: number; max?: number }) => void;
|
|
42
|
+
/** Update price filter */
|
|
43
|
+
setPriceFilter: (range: { min?: number; max?: number }) => void;
|
|
44
|
+
/** Update category filter */
|
|
45
|
+
setCategoryFilter: (categoryId?: string) => void;
|
|
46
|
+
/** Clear all filters */
|
|
47
|
+
clearAllFilters: () => void;
|
|
48
|
+
/** Get URL for sharing/linking with current filters */
|
|
49
|
+
getFilteredUrl: () => string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse URL search params into AppliedFilters object
|
|
54
|
+
*/
|
|
55
|
+
function parseSearchParams(searchParams: URLSearchParams): AppliedFilters {
|
|
56
|
+
const attributes: Record<string, string[]> = {};
|
|
57
|
+
const ranges: Record<string, { min?: number; max?: number }> = {};
|
|
58
|
+
let price: { min?: number; max?: number } | undefined;
|
|
59
|
+
let categoryId: string | undefined;
|
|
60
|
+
|
|
61
|
+
// Parse category
|
|
62
|
+
const categoryParam = searchParams.get("category");
|
|
63
|
+
if (categoryParam) {
|
|
64
|
+
categoryId = categoryParam;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Parse price
|
|
68
|
+
const minPrice = searchParams.get("min_price");
|
|
69
|
+
const maxPrice = searchParams.get("max_price");
|
|
70
|
+
if (minPrice || maxPrice) {
|
|
71
|
+
price = {
|
|
72
|
+
min: minPrice ? parseFloat(minPrice) : undefined,
|
|
73
|
+
max: maxPrice ? parseFloat(maxPrice) : undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Parse attribute filters
|
|
78
|
+
for (const [key, value] of searchParams.entries()) {
|
|
79
|
+
if (key.startsWith(ATTR_PREFIX)) {
|
|
80
|
+
const attrKey = key.slice(ATTR_PREFIX.length);
|
|
81
|
+
|
|
82
|
+
// Check if it's a range parameter (ends with _min or _max)
|
|
83
|
+
if (attrKey.endsWith(RANGE_MIN_SUFFIX)) {
|
|
84
|
+
const attrId = attrKey.slice(0, -RANGE_MIN_SUFFIX.length);
|
|
85
|
+
const numValue = parseFloat(value);
|
|
86
|
+
if (!isNaN(numValue)) {
|
|
87
|
+
if (!ranges[attrId]) ranges[attrId] = {};
|
|
88
|
+
ranges[attrId].min = numValue;
|
|
89
|
+
}
|
|
90
|
+
} else if (attrKey.endsWith(RANGE_MAX_SUFFIX)) {
|
|
91
|
+
const attrId = attrKey.slice(0, -RANGE_MAX_SUFFIX.length);
|
|
92
|
+
const numValue = parseFloat(value);
|
|
93
|
+
if (!isNaN(numValue)) {
|
|
94
|
+
if (!ranges[attrId]) ranges[attrId] = {};
|
|
95
|
+
ranges[attrId].max = numValue;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// Discrete attribute values (comma-separated)
|
|
99
|
+
const values = value.split(",").filter(Boolean);
|
|
100
|
+
if (values.length > 0) {
|
|
101
|
+
attributes[attrKey] = values;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { attributes, ranges, price, categoryId };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Serialize AppliedFilters to URLSearchParams
|
|
112
|
+
*/
|
|
113
|
+
function serializeToSearchParams(
|
|
114
|
+
filters: AppliedFilters,
|
|
115
|
+
existingParams?: URLSearchParams
|
|
116
|
+
): URLSearchParams {
|
|
117
|
+
const params = new URLSearchParams();
|
|
118
|
+
|
|
119
|
+
// Preserve non-filter params (like page, sort)
|
|
120
|
+
if (existingParams) {
|
|
121
|
+
for (const [key, value] of existingParams.entries()) {
|
|
122
|
+
if (
|
|
123
|
+
!key.startsWith(ATTR_PREFIX) &&
|
|
124
|
+
key !== "min_price" &&
|
|
125
|
+
key !== "max_price" &&
|
|
126
|
+
key !== "category"
|
|
127
|
+
) {
|
|
128
|
+
params.set(key, value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Category
|
|
134
|
+
if (filters.categoryId) {
|
|
135
|
+
params.set("category", filters.categoryId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Price
|
|
139
|
+
if (filters.price?.min !== undefined) {
|
|
140
|
+
params.set("min_price", filters.price.min.toString());
|
|
141
|
+
}
|
|
142
|
+
if (filters.price?.max !== undefined) {
|
|
143
|
+
params.set("max_price", filters.price.max.toString());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Discrete attributes
|
|
147
|
+
for (const [attrId, values] of Object.entries(filters.attributes)) {
|
|
148
|
+
if (values.length > 0) {
|
|
149
|
+
params.set(`${ATTR_PREFIX}${attrId}`, values.join(","));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Range attributes
|
|
154
|
+
for (const [attrId, range] of Object.entries(filters.ranges)) {
|
|
155
|
+
if (range.min !== undefined) {
|
|
156
|
+
params.set(`${ATTR_PREFIX}${attrId}${RANGE_MIN_SUFFIX}`, range.min.toString());
|
|
157
|
+
}
|
|
158
|
+
if (range.max !== undefined) {
|
|
159
|
+
params.set(`${ATTR_PREFIX}${attrId}${RANGE_MAX_SUFFIX}`, range.max.toString());
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return params;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function useFilterParams(
|
|
167
|
+
options: UseFilterParamsOptions = {}
|
|
168
|
+
): UseFilterParamsReturn {
|
|
169
|
+
const { debounceMs = DEBOUNCE_MS } = options;
|
|
170
|
+
const router = useRouter();
|
|
171
|
+
const pathname = usePathname();
|
|
172
|
+
const searchParams = useSearchParams();
|
|
173
|
+
|
|
174
|
+
// Debounce timer ref
|
|
175
|
+
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
|
176
|
+
|
|
177
|
+
// Parse current filters from URL
|
|
178
|
+
const appliedFilters = useMemo(() => {
|
|
179
|
+
return parseSearchParams(searchParams);
|
|
180
|
+
}, [searchParams]);
|
|
181
|
+
|
|
182
|
+
// Update URL with debouncing
|
|
183
|
+
const updateUrl = useCallback(
|
|
184
|
+
(newFilters: AppliedFilters) => {
|
|
185
|
+
if (debounceRef.current) {
|
|
186
|
+
clearTimeout(debounceRef.current);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
debounceRef.current = setTimeout(() => {
|
|
190
|
+
const params = serializeToSearchParams(newFilters, searchParams);
|
|
191
|
+
const queryString = params.toString();
|
|
192
|
+
const newUrl = queryString ? `${pathname}?${queryString}` : pathname;
|
|
193
|
+
router.push(newUrl, { scroll: false });
|
|
194
|
+
}, debounceMs);
|
|
195
|
+
},
|
|
196
|
+
[pathname, searchParams, router, debounceMs]
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Cleanup on unmount
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
return () => {
|
|
202
|
+
if (debounceRef.current) {
|
|
203
|
+
clearTimeout(debounceRef.current);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}, []);
|
|
207
|
+
|
|
208
|
+
// Set attribute filter values
|
|
209
|
+
const setAttributeFilter = useCallback(
|
|
210
|
+
(attributeId: string, values: string[]) => {
|
|
211
|
+
const newFilters = { ...appliedFilters };
|
|
212
|
+
newFilters.attributes = { ...newFilters.attributes };
|
|
213
|
+
|
|
214
|
+
if (values.length > 0) {
|
|
215
|
+
newFilters.attributes[attributeId] = values;
|
|
216
|
+
} else {
|
|
217
|
+
delete newFilters.attributes[attributeId];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
updateUrl(newFilters);
|
|
221
|
+
},
|
|
222
|
+
[appliedFilters, updateUrl]
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Set range filter values
|
|
226
|
+
const setRangeFilter = useCallback(
|
|
227
|
+
(attributeId: string, range: { min?: number; max?: number }) => {
|
|
228
|
+
const newFilters = { ...appliedFilters };
|
|
229
|
+
newFilters.ranges = { ...newFilters.ranges };
|
|
230
|
+
|
|
231
|
+
if (range.min !== undefined || range.max !== undefined) {
|
|
232
|
+
newFilters.ranges[attributeId] = range;
|
|
233
|
+
} else {
|
|
234
|
+
delete newFilters.ranges[attributeId];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
updateUrl(newFilters);
|
|
238
|
+
},
|
|
239
|
+
[appliedFilters, updateUrl]
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Set price filter
|
|
243
|
+
const setPriceFilter = useCallback(
|
|
244
|
+
(range: { min?: number; max?: number }) => {
|
|
245
|
+
const newFilters = { ...appliedFilters };
|
|
246
|
+
|
|
247
|
+
if (range.min !== undefined || range.max !== undefined) {
|
|
248
|
+
newFilters.price = range;
|
|
249
|
+
} else {
|
|
250
|
+
newFilters.price = undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
updateUrl(newFilters);
|
|
254
|
+
},
|
|
255
|
+
[appliedFilters, updateUrl]
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Set category filter
|
|
259
|
+
const setCategoryFilter = useCallback(
|
|
260
|
+
(categoryId?: string) => {
|
|
261
|
+
const newFilters = { ...appliedFilters };
|
|
262
|
+
newFilters.categoryId = categoryId;
|
|
263
|
+
updateUrl(newFilters);
|
|
264
|
+
},
|
|
265
|
+
[appliedFilters, updateUrl]
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Clear all filters
|
|
269
|
+
const clearAllFilters = useCallback(() => {
|
|
270
|
+
const newFilters: AppliedFilters = {
|
|
271
|
+
attributes: {},
|
|
272
|
+
ranges: {},
|
|
273
|
+
price: undefined,
|
|
274
|
+
categoryId: undefined,
|
|
275
|
+
};
|
|
276
|
+
updateUrl(newFilters);
|
|
277
|
+
}, [updateUrl]);
|
|
278
|
+
|
|
279
|
+
// Get URL for sharing
|
|
280
|
+
const getFilteredUrl = useCallback(() => {
|
|
281
|
+
const params = serializeToSearchParams(appliedFilters, searchParams);
|
|
282
|
+
const queryString = params.toString();
|
|
283
|
+
const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
|
|
284
|
+
return queryString ? `${baseUrl}${pathname}?${queryString}` : `${baseUrl}${pathname}`;
|
|
285
|
+
}, [appliedFilters, pathname, searchParams]);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
appliedFilters,
|
|
289
|
+
setAttributeFilter,
|
|
290
|
+
setRangeFilter,
|
|
291
|
+
setPriceFilter,
|
|
292
|
+
setCategoryFilter,
|
|
293
|
+
clearAllFilters,
|
|
294
|
+
getFilteredUrl,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export default useFilterParams;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie Helpers for Authentication
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for managing authentication cookies in both
|
|
5
|
+
* client-side and server-side contexts.
|
|
6
|
+
*
|
|
7
|
+
* Security Notes:
|
|
8
|
+
* - Cookies are httpOnly (set via API routes, not client-side)
|
|
9
|
+
* - Cookies are secure in production (HTTPS only)
|
|
10
|
+
* - Cookies are SameSite=Lax to prevent CSRF
|
|
11
|
+
* - Token validation happens on GraphQL backend
|
|
12
|
+
*
|
|
13
|
+
* @see lib/auth/routes.ts - Cookie name constant
|
|
14
|
+
* @see app/api/auth/set-token/route.ts - Server-side cookie setter
|
|
15
|
+
* @see app/api/auth/clear-token/route.ts - Server-side cookie clearer
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { AUTH_COOKIE_NAME } from "./routes";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cookie configuration for authentication token
|
|
22
|
+
*/
|
|
23
|
+
export const AUTH_COOKIE_CONFIG = {
|
|
24
|
+
name: AUTH_COOKIE_NAME,
|
|
25
|
+
maxAge: 60 * 60 * 24 * 30, // 30 days (in seconds)
|
|
26
|
+
path: "/",
|
|
27
|
+
sameSite: "lax" as const,
|
|
28
|
+
secure: process.env.NODE_ENV === "production",
|
|
29
|
+
httpOnly: true, // Cannot be accessed via JavaScript (security)
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get authentication token from cookies (client-side)
|
|
34
|
+
*
|
|
35
|
+
* Note: This only works if the cookie is NOT httpOnly.
|
|
36
|
+
* For httpOnly cookies, use server-side methods or API routes.
|
|
37
|
+
*
|
|
38
|
+
* @returns Token string or null if not found
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* const token = getAuthToken();
|
|
43
|
+
* if (token) {
|
|
44
|
+
* // User is authenticated
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function getAuthToken(): string | null {
|
|
49
|
+
if (typeof window === "undefined") {
|
|
50
|
+
return null; // Server-side
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const cookies = document.cookie.split("; ");
|
|
54
|
+
const authCookie = cookies.find((c) => c.startsWith(`${AUTH_COOKIE_NAME}=`));
|
|
55
|
+
|
|
56
|
+
if (!authCookie) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return authCookie.split("=")[1] || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if user is authenticated (client-side)
|
|
65
|
+
*
|
|
66
|
+
* Note: This checks cookie EXISTENCE, not VALIDITY.
|
|
67
|
+
* Token validation happens on the GraphQL backend.
|
|
68
|
+
*
|
|
69
|
+
* @returns True if auth cookie exists
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* const isAuthenticated = isAuthTokenPresent();
|
|
74
|
+
* if (isAuthenticated) {
|
|
75
|
+
* // Show authenticated UI
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function isAuthTokenPresent(): boolean {
|
|
80
|
+
return getAuthToken() !== null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Set authentication token (via API route)
|
|
85
|
+
*
|
|
86
|
+
* This function calls the API route to set an httpOnly cookie.
|
|
87
|
+
* Direct client-side cookie setting is NOT secure for auth tokens.
|
|
88
|
+
*
|
|
89
|
+
* @param token - Customer access token from GraphQL
|
|
90
|
+
* @returns Promise that resolves when cookie is set
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* const { customerAccessToken } = await loginMutation.mutateAsync({
|
|
95
|
+
* input: { email, password }
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* if (customerAccessToken) {
|
|
99
|
+
* await setAuthToken(customerAccessToken.accessToken);
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export async function setAuthToken(token: string): Promise<void> {
|
|
104
|
+
const response = await fetch("/api/auth/set-token", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({ token }),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error("Failed to set authentication token");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Clear authentication token (via API route)
|
|
119
|
+
*
|
|
120
|
+
* This function calls the API route to clear the httpOnly cookie.
|
|
121
|
+
*
|
|
122
|
+
* @returns Promise that resolves when cookie is cleared
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```tsx
|
|
126
|
+
* await clearAuthToken();
|
|
127
|
+
* router.push('/auth/login');
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export async function clearAuthToken(): Promise<void> {
|
|
131
|
+
const response = await fetch("/api/auth/clear-token", {
|
|
132
|
+
method: "POST",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
throw new Error("Failed to clear authentication token");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse cookie string into key-value object
|
|
142
|
+
*
|
|
143
|
+
* Utility function for parsing cookie strings in server-side contexts.
|
|
144
|
+
*
|
|
145
|
+
* @param cookieString - Raw cookie string from request headers
|
|
146
|
+
* @returns Object with cookie key-value pairs
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```tsx
|
|
150
|
+
* const cookies = parseCookies(request.headers.get('cookie') || '');
|
|
151
|
+
* const token = cookies[AUTH_COOKIE_NAME];
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export function parseCookies(cookieString: string): Record<string, string> {
|
|
155
|
+
return cookieString
|
|
156
|
+
.split("; ")
|
|
157
|
+
.reduce((acc, cookie) => {
|
|
158
|
+
const [key, value] = cookie.split("=");
|
|
159
|
+
if (key && value) {
|
|
160
|
+
acc[key] = value;
|
|
161
|
+
}
|
|
162
|
+
return acc;
|
|
163
|
+
}, {} as Record<string, string>);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Serialize cookie for Set-Cookie header
|
|
168
|
+
*
|
|
169
|
+
* Utility function for creating Set-Cookie header values in API routes.
|
|
170
|
+
*
|
|
171
|
+
* @param name - Cookie name
|
|
172
|
+
* @param value - Cookie value
|
|
173
|
+
* @param options - Cookie options (maxAge, path, etc.)
|
|
174
|
+
* @returns Serialized cookie string for Set-Cookie header
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```tsx
|
|
178
|
+
* const cookieHeader = serializeCookie(
|
|
179
|
+
* AUTH_COOKIE_NAME,
|
|
180
|
+
* token,
|
|
181
|
+
* AUTH_COOKIE_CONFIG
|
|
182
|
+
* );
|
|
183
|
+
* response.headers.set('Set-Cookie', cookieHeader);
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export function serializeCookie(
|
|
187
|
+
name: string,
|
|
188
|
+
value: string,
|
|
189
|
+
options: {
|
|
190
|
+
maxAge?: number;
|
|
191
|
+
path?: string;
|
|
192
|
+
sameSite?: "strict" | "lax" | "none";
|
|
193
|
+
secure?: boolean;
|
|
194
|
+
httpOnly?: boolean;
|
|
195
|
+
} = {}
|
|
196
|
+
): string {
|
|
197
|
+
const parts = [`${name}=${value}`];
|
|
198
|
+
|
|
199
|
+
if (options.maxAge) {
|
|
200
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (options.path) {
|
|
204
|
+
parts.push(`Path=${options.path}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (options.sameSite) {
|
|
208
|
+
parts.push(`SameSite=${options.sameSite}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (options.secure) {
|
|
212
|
+
parts.push("Secure");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (options.httpOnly) {
|
|
216
|
+
parts.push("HttpOnly");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return parts.join("; ");
|
|
220
|
+
}
|