@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,343 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { useProduct } from "@/lib/graphql/hooks";
|
|
5
|
+
import { useCurrencyStore } from "@/stores/currency-store";
|
|
6
|
+
import { ProductGallery } from "@/components/product/product-gallery";
|
|
7
|
+
import { ProductPrice } from "@/components/product/product-price";
|
|
8
|
+
import { ProductVariantSelector } from "@/components/product/product-variant-selector";
|
|
9
|
+
import { ProductQuantitySelector } from "@/components/product/product-quantity-selector";
|
|
10
|
+
import { AddToCartButton } from "@/components/product/add-to-cart-button";
|
|
11
|
+
import { StockIndicator } from "@/components/product/stock-indicator";
|
|
12
|
+
import { SimilarProducts } from "@/components/product/similar-products";
|
|
13
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
14
|
+
import { Badge } from "@/components/ui/badge";
|
|
15
|
+
import { Gift } from "lucide-react";
|
|
16
|
+
import { shopConfig } from "@/lib/config";
|
|
17
|
+
|
|
18
|
+
export interface Product {
|
|
19
|
+
id: string;
|
|
20
|
+
handle: string;
|
|
21
|
+
title: string;
|
|
22
|
+
description?: string | null;
|
|
23
|
+
vendor?: string | null;
|
|
24
|
+
productType?: string | null;
|
|
25
|
+
type?: string | null;
|
|
26
|
+
collectRecipientInfo?: boolean;
|
|
27
|
+
tags?: string[];
|
|
28
|
+
images: Array<{
|
|
29
|
+
url: string;
|
|
30
|
+
altText?: string | null;
|
|
31
|
+
}>;
|
|
32
|
+
variants: Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
available: boolean;
|
|
36
|
+
selectedOptions: Array<{
|
|
37
|
+
name: string;
|
|
38
|
+
value: string;
|
|
39
|
+
}>;
|
|
40
|
+
price: {
|
|
41
|
+
amount: string;
|
|
42
|
+
currencyCode: string;
|
|
43
|
+
};
|
|
44
|
+
compareAtPrice?: {
|
|
45
|
+
amount: string;
|
|
46
|
+
currencyCode: string;
|
|
47
|
+
} | null;
|
|
48
|
+
image?: {
|
|
49
|
+
url: string;
|
|
50
|
+
altText?: string | null;
|
|
51
|
+
} | null;
|
|
52
|
+
}>;
|
|
53
|
+
priceRange: {
|
|
54
|
+
minVariantPrice: {
|
|
55
|
+
amount: string;
|
|
56
|
+
currencyCode: string;
|
|
57
|
+
};
|
|
58
|
+
maxVariantPrice: {
|
|
59
|
+
amount: string;
|
|
60
|
+
currencyCode: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ProductClientProps {
|
|
66
|
+
product: Product;
|
|
67
|
+
similarProducts?: any[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Product Client Component
|
|
72
|
+
*
|
|
73
|
+
* Handles interactive features and currency-aware price display.
|
|
74
|
+
*
|
|
75
|
+
* Flow:
|
|
76
|
+
* 1. Receives initial product data from Server Component (base currency)
|
|
77
|
+
* 2. Checks if user's preferred currency differs from SSR currency
|
|
78
|
+
* 3. If different, refetches product with preferred currency
|
|
79
|
+
* 4. Uses suppressHydrationWarning on price elements to prevent mismatch
|
|
80
|
+
*
|
|
81
|
+
* Requirements: 2.2, 3.2, 6.4
|
|
82
|
+
*/
|
|
83
|
+
export function ProductClient({ product: initialProduct, similarProducts = [] }: ProductClientProps) {
|
|
84
|
+
const [selectedVariant, setSelectedVariant] = useState(initialProduct.variants[0]);
|
|
85
|
+
const [quantity, setQuantity] = useState(1);
|
|
86
|
+
|
|
87
|
+
// Get user's preferred currency from store
|
|
88
|
+
const currency = useCurrencyStore((s: any) => s.currency);
|
|
89
|
+
const isHydrated = useCurrencyStore((s: any) => s.isHydrated);
|
|
90
|
+
|
|
91
|
+
// Check if SSR currency matches user's preferred currency
|
|
92
|
+
const ssrCurrency = initialProduct.priceRange?.minVariantPrice?.currencyCode;
|
|
93
|
+
const currencyMatches = !isHydrated || !currency || currency === ssrCurrency;
|
|
94
|
+
|
|
95
|
+
// Refetch product with current currency from store
|
|
96
|
+
// Query key includes currency, so it auto-refetches when currency changes
|
|
97
|
+
const { data, isFetching } = useProduct(initialProduct.handle, {
|
|
98
|
+
// Only enable after hydration to avoid SSR/client mismatch
|
|
99
|
+
enabled: isHydrated,
|
|
100
|
+
// Only use SSR data as placeholder if currency matches
|
|
101
|
+
// Otherwise, don't show stale data from wrong currency
|
|
102
|
+
placeholderData: currencyMatches ? { product: initialProduct } : undefined,
|
|
103
|
+
// Don't use stale data - always refetch when currency changes
|
|
104
|
+
staleTime: 0,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Use fetched product if available, otherwise use initial (SSR) data only if currency matches
|
|
108
|
+
const product = data?.product || (currencyMatches ? initialProduct : null);
|
|
109
|
+
|
|
110
|
+
// Update selected variant when product changes (currency switch)
|
|
111
|
+
// IMPORTANT: This useEffect must be called BEFORE any conditional returns
|
|
112
|
+
// to comply with React Hooks rules (hooks must be called in the same order every render)
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (product?.variants.length > 0) {
|
|
115
|
+
// Try to maintain the same variant by matching ID
|
|
116
|
+
const matchingVariant = product.variants.find(
|
|
117
|
+
(v: any) => v.id === selectedVariant.id
|
|
118
|
+
);
|
|
119
|
+
if (matchingVariant) {
|
|
120
|
+
setSelectedVariant(matchingVariant);
|
|
121
|
+
} else {
|
|
122
|
+
// Fallback to first variant if ID doesn't match
|
|
123
|
+
setSelectedVariant(product.variants[0]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, [product, selectedVariant.id]);
|
|
127
|
+
|
|
128
|
+
// Show loading state if we don't have data in correct currency yet
|
|
129
|
+
// This check comes AFTER all hooks to prevent "Rendered fewer hooks than expected" error
|
|
130
|
+
if (!product) {
|
|
131
|
+
return (
|
|
132
|
+
<div className="space-y-16">
|
|
133
|
+
<div className="grid gap-8 lg:grid-cols-2 lg:gap-12">
|
|
134
|
+
<div className="aspect-square animate-pulse bg-muted rounded-lg" />
|
|
135
|
+
<div className="space-y-6">
|
|
136
|
+
<div className="h-10 w-3/4 animate-pulse bg-muted rounded" />
|
|
137
|
+
<div className="h-8 w-1/4 animate-pulse bg-muted rounded" />
|
|
138
|
+
<div className="h-24 animate-pulse bg-muted rounded" />
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const isOnSale = selectedVariant.compareAtPrice &&
|
|
146
|
+
parseFloat(selectedVariant.compareAtPrice.amount) >
|
|
147
|
+
parseFloat(selectedVariant.price.amount);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className="space-y-16">
|
|
151
|
+
{/* Product Details */}
|
|
152
|
+
<div className="grid gap-8 lg:grid-cols-2 lg:gap-12">
|
|
153
|
+
{/* Gallery */}
|
|
154
|
+
<div>
|
|
155
|
+
<ProductGallery images={product.images} productTitle={product.title} />
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{/* Info */}
|
|
159
|
+
<div className="space-y-6">
|
|
160
|
+
{/* Title & Badges */}
|
|
161
|
+
<div>
|
|
162
|
+
<div className="mb-2 flex flex-wrap gap-2">
|
|
163
|
+
{product.type === "GIFT_CARD" && (
|
|
164
|
+
<Badge variant="default">
|
|
165
|
+
<Gift className="mr-1 h-3 w-3" />
|
|
166
|
+
Karta podarunkowa
|
|
167
|
+
</Badge>
|
|
168
|
+
)}
|
|
169
|
+
{product.tags?.includes("new") && product.type !== "GIFT_CARD" && (
|
|
170
|
+
<Badge variant="default">NEW</Badge>
|
|
171
|
+
)}
|
|
172
|
+
{isOnSale && <Badge variant="destructive">SALE</Badge>}
|
|
173
|
+
</div>
|
|
174
|
+
<h1 className="text-3xl font-bold text-foreground lg:text-4xl">
|
|
175
|
+
{product.title}
|
|
176
|
+
</h1>
|
|
177
|
+
{product.vendor && (
|
|
178
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
179
|
+
by {product.vendor}
|
|
180
|
+
</p>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
{/* Price - suppressHydrationWarning prevents mismatch during currency switch */}
|
|
185
|
+
<div suppressHydrationWarning>
|
|
186
|
+
<ProductPrice
|
|
187
|
+
price={selectedVariant.price}
|
|
188
|
+
compareAtPrice={selectedVariant.compareAtPrice || undefined}
|
|
189
|
+
showCompareAt
|
|
190
|
+
size="xl"
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Stock */}
|
|
195
|
+
<StockIndicator
|
|
196
|
+
available={selectedVariant.available}
|
|
197
|
+
variant="text"
|
|
198
|
+
/>
|
|
199
|
+
|
|
200
|
+
{/* Description */}
|
|
201
|
+
{product.description && (
|
|
202
|
+
<div className="prose prose-sm max-w-none text-muted-foreground">
|
|
203
|
+
<p>{product.description}</p>
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{/* Variant Selector */}
|
|
208
|
+
{product.variants.length > 1 && (
|
|
209
|
+
<ProductVariantSelector
|
|
210
|
+
variants={product.variants}
|
|
211
|
+
selectedVariantId={selectedVariant.id}
|
|
212
|
+
onVariantChange={setSelectedVariant}
|
|
213
|
+
/>
|
|
214
|
+
)}
|
|
215
|
+
|
|
216
|
+
{/* Quantity & Add to Cart */}
|
|
217
|
+
<div className="space-y-4">
|
|
218
|
+
<div className="flex items-center gap-4">
|
|
219
|
+
<ProductQuantitySelector
|
|
220
|
+
value={quantity}
|
|
221
|
+
onChange={setQuantity}
|
|
222
|
+
min={1}
|
|
223
|
+
max={99}
|
|
224
|
+
disabled={!selectedVariant.available}
|
|
225
|
+
/>
|
|
226
|
+
<AddToCartButton
|
|
227
|
+
variantId={selectedVariant.id}
|
|
228
|
+
productId={product.id}
|
|
229
|
+
productHandle={product.handle}
|
|
230
|
+
productTitle={product.title}
|
|
231
|
+
variantTitle={selectedVariant.title}
|
|
232
|
+
price={selectedVariant.price}
|
|
233
|
+
image={selectedVariant.image || product.images[0]}
|
|
234
|
+
available={selectedVariant.available}
|
|
235
|
+
quantity={quantity}
|
|
236
|
+
fullWidth
|
|
237
|
+
size="lg"
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{/* Product Details */}
|
|
243
|
+
{(product.vendor || product.productType) && (
|
|
244
|
+
<div className="border-t border-border pt-6">
|
|
245
|
+
<h3 className="mb-4 text-sm font-semibold text-foreground">
|
|
246
|
+
Product Details
|
|
247
|
+
</h3>
|
|
248
|
+
<dl className="space-y-2 text-sm">
|
|
249
|
+
{product.vendor && (
|
|
250
|
+
<div className="flex justify-between">
|
|
251
|
+
<dt className="text-muted-foreground">Vendor</dt>
|
|
252
|
+
<dd className="font-medium text-foreground">
|
|
253
|
+
{product.vendor}
|
|
254
|
+
</dd>
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
{product.productType && (
|
|
258
|
+
<div className="flex justify-between">
|
|
259
|
+
<dt className="text-muted-foreground">Type</dt>
|
|
260
|
+
<dd className="font-medium text-foreground">
|
|
261
|
+
{product.productType}
|
|
262
|
+
</dd>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
</dl>
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{/* Additional Info Tabs */}
|
|
272
|
+
<Tabs defaultValue="description" className="w-full">
|
|
273
|
+
<TabsList className="w-full justify-start">
|
|
274
|
+
<TabsTrigger value="description">Description</TabsTrigger>
|
|
275
|
+
<TabsTrigger value="details">Details</TabsTrigger>
|
|
276
|
+
<TabsTrigger value="shipping">Shipping</TabsTrigger>
|
|
277
|
+
</TabsList>
|
|
278
|
+
|
|
279
|
+
<TabsContent value="description" className="mt-6">
|
|
280
|
+
<div className="prose prose-sm max-w-none">
|
|
281
|
+
<p>{product.description || "No description available."}</p>
|
|
282
|
+
</div>
|
|
283
|
+
</TabsContent>
|
|
284
|
+
|
|
285
|
+
<TabsContent value="details" className="mt-6">
|
|
286
|
+
<dl className="grid gap-4 sm:grid-cols-2">
|
|
287
|
+
<div>
|
|
288
|
+
<dt className="font-medium text-foreground">Product ID</dt>
|
|
289
|
+
<dd className="mt-1 text-sm text-muted-foreground">
|
|
290
|
+
{product.id}
|
|
291
|
+
</dd>
|
|
292
|
+
</div>
|
|
293
|
+
<div>
|
|
294
|
+
<dt className="font-medium text-foreground">Handle</dt>
|
|
295
|
+
<dd className="mt-1 text-sm text-muted-foreground">
|
|
296
|
+
{product.handle}
|
|
297
|
+
</dd>
|
|
298
|
+
</div>
|
|
299
|
+
{product.vendor && (
|
|
300
|
+
<div>
|
|
301
|
+
<dt className="font-medium text-foreground">Vendor</dt>
|
|
302
|
+
<dd className="mt-1 text-sm text-muted-foreground">
|
|
303
|
+
{product.vendor}
|
|
304
|
+
</dd>
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
307
|
+
{product.productType && (
|
|
308
|
+
<div>
|
|
309
|
+
<dt className="font-medium text-foreground">Type</dt>
|
|
310
|
+
<dd className="mt-1 text-sm text-muted-foreground">
|
|
311
|
+
{product.productType}
|
|
312
|
+
</dd>
|
|
313
|
+
</div>
|
|
314
|
+
)}
|
|
315
|
+
</dl>
|
|
316
|
+
</TabsContent>
|
|
317
|
+
|
|
318
|
+
<TabsContent value="shipping" className="mt-6">
|
|
319
|
+
<div className="space-y-4 text-sm">
|
|
320
|
+
<p className="text-muted-foreground">
|
|
321
|
+
Free shipping on orders over ${shopConfig.shipping.freeShippingThreshold}.
|
|
322
|
+
Standard delivery takes {shopConfig.shipping.standardDeliveryDays.min}-{shopConfig.shipping.standardDeliveryDays.max} business days.
|
|
323
|
+
</p>
|
|
324
|
+
<ul className="list-inside list-disc space-y-2 text-muted-foreground">
|
|
325
|
+
{shopConfig.shipping.features.map((feature, index) => (
|
|
326
|
+
<li key={index}>{feature}</li>
|
|
327
|
+
))}
|
|
328
|
+
</ul>
|
|
329
|
+
</div>
|
|
330
|
+
</TabsContent>
|
|
331
|
+
</Tabs>
|
|
332
|
+
|
|
333
|
+
{/* Similar Products */}
|
|
334
|
+
{similarProducts.length > 0 && (
|
|
335
|
+
<SimilarProducts
|
|
336
|
+
products={similarProducts}
|
|
337
|
+
title="You might also like"
|
|
338
|
+
columns={4}
|
|
339
|
+
/>
|
|
340
|
+
)}
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Suspense } from "react";
|
|
2
|
+
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
|
3
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
4
|
+
import { ProductsClient } from "./products-client";
|
|
5
|
+
|
|
6
|
+
export default function ProductsPage() {
|
|
7
|
+
return (
|
|
8
|
+
<div className="container mx-auto px-4 py-8">
|
|
9
|
+
{/* Breadcrumbs */}
|
|
10
|
+
<Breadcrumbs className="mb-6" />
|
|
11
|
+
|
|
12
|
+
{/* Page Header */}
|
|
13
|
+
<div className="mb-8">
|
|
14
|
+
<h1 className="text-3xl font-bold text-foreground">All Products</h1>
|
|
15
|
+
<p className="mt-2 text-muted-foreground">
|
|
16
|
+
Browse our complete collection of products
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<Suspense fallback={<Spinner />}>
|
|
21
|
+
<ProductsClient />
|
|
22
|
+
</Suspense>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Suspense } from "react";
|
|
4
|
+
import { useSearchParams, useRouter } from "next/navigation";
|
|
5
|
+
import { ProductGrid } from "@/components/product/product-grid";
|
|
6
|
+
import { ProductFilters } from "@/components/product/product-filters";
|
|
7
|
+
import { ProductSort, type SortOption } from "@/components/product/product-sort";
|
|
8
|
+
import { Pagination } from "@/components/ui/pagination";
|
|
9
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
10
|
+
import { useProducts, useCategories } from "@/lib/graphql/hooks";
|
|
11
|
+
|
|
12
|
+
export function ProductsClient() {
|
|
13
|
+
const searchParams = useSearchParams();
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
|
|
16
|
+
// Parse URL parameters (all lowercase)
|
|
17
|
+
const page = parseInt(searchParams.get("page") || "1", 10);
|
|
18
|
+
const sort = (searchParams.get("sort") as SortOption) || "relevance";
|
|
19
|
+
const categories = searchParams.get("categories")?.split(",").filter(Boolean) || [];
|
|
20
|
+
const priceMin = searchParams.get("price_min");
|
|
21
|
+
const priceMax = searchParams.get("price_max");
|
|
22
|
+
|
|
23
|
+
const limit = 20;
|
|
24
|
+
|
|
25
|
+
// Fetch categories for filter sidebar
|
|
26
|
+
const { data: categoriesData } = useCategories();
|
|
27
|
+
const allCategories = categoriesData?.categories ?? [];
|
|
28
|
+
|
|
29
|
+
// Build GraphQL query string for filters
|
|
30
|
+
let queryString = "";
|
|
31
|
+
if (categories.length > 0) {
|
|
32
|
+
// Use category handles in query
|
|
33
|
+
queryString = categories.map(cat => `category:${cat}`).join(" OR ");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Fetch products using GraphQL
|
|
37
|
+
// Backend should normalize sort values (e.g., "price-asc" → PRICE + reverse: false)
|
|
38
|
+
const { data, isLoading, error } = useProducts({
|
|
39
|
+
first: limit,
|
|
40
|
+
query: queryString || undefined,
|
|
41
|
+
sortKey: sort as any, // Backend normalizes this
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const products = data?.products ?? [];
|
|
45
|
+
const totalCount = data?.totalCount ?? 0;
|
|
46
|
+
const totalPages = Math.ceil(totalCount / limit);
|
|
47
|
+
|
|
48
|
+
// Build selected filters object
|
|
49
|
+
const selectedFilters: Record<string, any> = {};
|
|
50
|
+
if (categories.length > 0) {
|
|
51
|
+
selectedFilters.categories = categories;
|
|
52
|
+
}
|
|
53
|
+
if (priceMin) {
|
|
54
|
+
selectedFilters.price_min = priceMin;
|
|
55
|
+
}
|
|
56
|
+
if (priceMax) {
|
|
57
|
+
selectedFilters.price_max = priceMax;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Update URL with new filters
|
|
61
|
+
const updateFilters = (updates: Record<string, any>) => {
|
|
62
|
+
const newParams = new URLSearchParams(searchParams.toString());
|
|
63
|
+
|
|
64
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
65
|
+
if (value === null || value === undefined || value === "") {
|
|
66
|
+
newParams.delete(key);
|
|
67
|
+
} else if (Array.isArray(value)) {
|
|
68
|
+
if (value.length > 0) {
|
|
69
|
+
newParams.set(key, value.join(","));
|
|
70
|
+
} else {
|
|
71
|
+
newParams.delete(key);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
newParams.set(key, value.toString());
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Reset to page 1 when filters change
|
|
79
|
+
newParams.set("page", "1");
|
|
80
|
+
router.push(`/products?${newParams.toString()}`);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Handle filter changes
|
|
84
|
+
const handleFilterChange = (filterId: string, value: any) => {
|
|
85
|
+
if (filterId === "categories") {
|
|
86
|
+
// Toggle category in array
|
|
87
|
+
const newCategories = categories.includes(value)
|
|
88
|
+
? categories.filter(c => c !== value)
|
|
89
|
+
: [...categories, value];
|
|
90
|
+
updateFilters({ categories: newCategories });
|
|
91
|
+
} else if (filterId === "price_min") {
|
|
92
|
+
updateFilters({ price_min: value });
|
|
93
|
+
} else if (filterId === "price_max") {
|
|
94
|
+
updateFilters({ price_max: value });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Build filter options from categories
|
|
99
|
+
const filterOptions = [
|
|
100
|
+
{
|
|
101
|
+
id: "categories",
|
|
102
|
+
label: "Categories",
|
|
103
|
+
type: "checkbox" as const,
|
|
104
|
+
options: allCategories.map((category: any) => ({
|
|
105
|
+
label: category.name,
|
|
106
|
+
value: category.slug,
|
|
107
|
+
count: category.productCount || 0,
|
|
108
|
+
})),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "price",
|
|
112
|
+
label: "Price Range",
|
|
113
|
+
type: "range" as const,
|
|
114
|
+
min: 0,
|
|
115
|
+
max: 1000,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div className="flex flex-col gap-8 lg:flex-row">
|
|
121
|
+
{/* Sidebar Filters */}
|
|
122
|
+
<aside className="w-full lg:w-64 lg:flex-shrink-0">
|
|
123
|
+
<div className="sticky top-4">
|
|
124
|
+
<h2 className="mb-4 text-lg font-semibold text-foreground">
|
|
125
|
+
Filters
|
|
126
|
+
</h2>
|
|
127
|
+
<Suspense fallback={<Skeleton className="h-96 w-full" />}>
|
|
128
|
+
<ProductFilters
|
|
129
|
+
filters={filterOptions}
|
|
130
|
+
selectedFilters={selectedFilters}
|
|
131
|
+
onFilterChange={handleFilterChange}
|
|
132
|
+
onClearAll={() => router.push("/products")}
|
|
133
|
+
/>
|
|
134
|
+
</Suspense>
|
|
135
|
+
</div>
|
|
136
|
+
</aside>
|
|
137
|
+
|
|
138
|
+
{/* Main Content */}
|
|
139
|
+
<div className="flex-1">
|
|
140
|
+
{/* Sort & Results Count */}
|
|
141
|
+
<div className="mb-6 flex flex-col justify-between gap-4 sm:flex-row sm:items-center">
|
|
142
|
+
<p className="text-sm text-muted-foreground">
|
|
143
|
+
{totalCount > 0
|
|
144
|
+
? `Showing ${(page - 1) * limit + 1}-${Math.min(page * limit, totalCount)} of ${totalCount} products`
|
|
145
|
+
: "No products found"}
|
|
146
|
+
</p>
|
|
147
|
+
<ProductSort
|
|
148
|
+
value={sort}
|
|
149
|
+
onChange={(newSort) => {
|
|
150
|
+
const newParams = new URLSearchParams(searchParams.toString());
|
|
151
|
+
newParams.set("sort", newSort);
|
|
152
|
+
router.push(`/products?${newParams.toString()}`);
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
{/* Products Grid */}
|
|
158
|
+
{isLoading ? (
|
|
159
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
160
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
161
|
+
<Skeleton key={i} className="aspect-square w-full" />
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
) : (
|
|
165
|
+
<ProductGrid
|
|
166
|
+
products={products}
|
|
167
|
+
columns={3}
|
|
168
|
+
priorityCount={6}
|
|
169
|
+
showBadges
|
|
170
|
+
emptyMessage="No products match your filters"
|
|
171
|
+
onResetFilters={() => router.push("/products")}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* Pagination */}
|
|
176
|
+
{totalPages > 1 && (
|
|
177
|
+
<div className="mt-8 flex justify-center">
|
|
178
|
+
<Pagination
|
|
179
|
+
currentPage={page}
|
|
180
|
+
totalPages={totalPages}
|
|
181
|
+
onPageChange={(newPage: number) => {
|
|
182
|
+
const newParams = new URLSearchParams(searchParams.toString());
|
|
183
|
+
newParams.set("page", newPage.toString());
|
|
184
|
+
router.push(`/products?${newParams.toString()}`);
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
|
2
|
+
import { RotateCcw, CheckCircle, XCircle } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export default function ReturnsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="container mx-auto px-4 py-8">
|
|
7
|
+
<Breadcrumbs className="mb-6" />
|
|
8
|
+
|
|
9
|
+
<div className="mx-auto max-w-3xl">
|
|
10
|
+
<h1 className="mb-6 text-4xl font-bold text-foreground">Returns & Refunds</h1>
|
|
11
|
+
|
|
12
|
+
<div className="space-y-8">
|
|
13
|
+
<div>
|
|
14
|
+
<div className="mb-4 flex items-center gap-3">
|
|
15
|
+
<RotateCcw className="h-6 w-6 text-primary" />
|
|
16
|
+
<h2 className="text-2xl font-semibold text-foreground">Return Policy</h2>
|
|
17
|
+
</div>
|
|
18
|
+
<p className="text-muted-foreground">
|
|
19
|
+
We want you to be completely satisfied with your purchase. If you're not happy with your order, you can return it within 30 days of delivery for a full refund.
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<div className="mb-4 flex items-center gap-3">
|
|
25
|
+
<CheckCircle className="h-6 w-6 text-green-600" />
|
|
26
|
+
<h2 className="text-2xl font-semibold text-foreground">Eligible Returns</h2>
|
|
27
|
+
</div>
|
|
28
|
+
<ul className="list-inside list-disc space-y-2 text-muted-foreground">
|
|
29
|
+
<li>Items must be unused and in original condition</li>
|
|
30
|
+
<li>Items must be in original packaging</li>
|
|
31
|
+
<li>Return within 30 days of delivery</li>
|
|
32
|
+
<li>Include proof of purchase</li>
|
|
33
|
+
</ul>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<div className="mb-4 flex items-center gap-3">
|
|
38
|
+
<XCircle className="h-6 w-6 text-red-600" />
|
|
39
|
+
<h2 className="text-2xl font-semibold text-foreground">Non-Returnable Items</h2>
|
|
40
|
+
</div>
|
|
41
|
+
<ul className="list-inside list-disc space-y-2 text-muted-foreground">
|
|
42
|
+
<li>Personalized or custom-made items</li>
|
|
43
|
+
<li>Perishable goods</li>
|
|
44
|
+
<li>Intimate or sanitary goods</li>
|
|
45
|
+
<li>Sale or clearance items</li>
|
|
46
|
+
</ul>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div>
|
|
50
|
+
<h2 className="mb-4 text-2xl font-semibold text-foreground">How to Return</h2>
|
|
51
|
+
<ol className="list-inside list-decimal space-y-2 text-muted-foreground">
|
|
52
|
+
<li>Contact our customer service team to initiate a return</li>
|
|
53
|
+
<li>Pack the item securely in its original packaging</li>
|
|
54
|
+
<li>Include your order number and reason for return</li>
|
|
55
|
+
<li>Ship the package to the address provided</li>
|
|
56
|
+
<li>Refund will be processed within 5-7 business days after we receive your return</li>
|
|
57
|
+
</ol>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div>
|
|
61
|
+
<h2 className="mb-4 text-2xl font-semibold text-foreground">Refund Policy</h2>
|
|
62
|
+
<p className="text-muted-foreground">
|
|
63
|
+
Once we receive and inspect your return, we'll send you an email notification. If approved, your refund will be processed and automatically applied to your original payment method within 5-7 business days.
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div>
|
|
68
|
+
<h2 className="mb-4 text-2xl font-semibold text-foreground">Exchanges</h2>
|
|
69
|
+
<p className="text-muted-foreground">
|
|
70
|
+
We only replace items if they are defective or damaged. If you need to exchange an item, contact our customer service team.
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* robots.txt Configuration
|
|
3
|
+
*
|
|
4
|
+
* Controls search engine crawler access to the site.
|
|
5
|
+
*
|
|
6
|
+
* Requirements: 13.5
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MetadataRoute } from "next";
|
|
10
|
+
|
|
11
|
+
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate robots.txt rules
|
|
15
|
+
*
|
|
16
|
+
* Allows:
|
|
17
|
+
* - All public pages (products, collections, categories)
|
|
18
|
+
*
|
|
19
|
+
* Disallows:
|
|
20
|
+
* - API routes (/api/*)
|
|
21
|
+
* - Checkout pages (/checkout/*)
|
|
22
|
+
* - Account pages (/account/*)
|
|
23
|
+
* - Admin routes (/admin/*)
|
|
24
|
+
* - Cart page (/cart)
|
|
25
|
+
* - Auth pages (/auth/*)
|
|
26
|
+
*/
|
|
27
|
+
export default function robots(): MetadataRoute.Robots {
|
|
28
|
+
return {
|
|
29
|
+
rules: [
|
|
30
|
+
{
|
|
31
|
+
userAgent: "*",
|
|
32
|
+
allow: "/",
|
|
33
|
+
disallow: [
|
|
34
|
+
"/api/",
|
|
35
|
+
"/checkout/",
|
|
36
|
+
"/account/",
|
|
37
|
+
"/admin/",
|
|
38
|
+
"/cart",
|
|
39
|
+
"/auth/",
|
|
40
|
+
"/_next/",
|
|
41
|
+
"/private/",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
// Block bad bots explicitly
|
|
45
|
+
{
|
|
46
|
+
userAgent: ["GPTBot", "ChatGPT-User", "CCBot", "anthropic-ai"],
|
|
47
|
+
disallow: ["/"],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
sitemap: `${SITE_URL}/sitemap.xml`,
|
|
51
|
+
host: SITE_URL,
|
|
52
|
+
};
|
|
53
|
+
}
|