@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,1752 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { useCartStore } from "@/stores/cart-store";
|
|
7
|
+
import { useCartSync } from "@/hooks/use-cart-sync";
|
|
8
|
+
import {
|
|
9
|
+
useCheckoutCreate,
|
|
10
|
+
useCheckoutEmailUpdate,
|
|
11
|
+
useCheckoutShippingAddressUpdate,
|
|
12
|
+
useCheckoutBillingAddressUpdate,
|
|
13
|
+
useCheckoutShippingLineUpdate,
|
|
14
|
+
useCheckoutDiscountCodeApply,
|
|
15
|
+
useCheckoutDiscountCodeRemove,
|
|
16
|
+
useCheckoutGiftCardApply,
|
|
17
|
+
useCheckoutGiftCardRemove,
|
|
18
|
+
useCheckoutComplete,
|
|
19
|
+
useCheckout,
|
|
20
|
+
} from "@/lib/graphql/hooks";
|
|
21
|
+
import { formatAmount } from "@/lib/format";
|
|
22
|
+
import { Button } from "@/components/ui/button";
|
|
23
|
+
import { Input } from "@/components/ui/input";
|
|
24
|
+
import { Label } from "@/components/ui/label";
|
|
25
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
26
|
+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
27
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
28
|
+
import {
|
|
29
|
+
Select,
|
|
30
|
+
SelectContent,
|
|
31
|
+
SelectItem,
|
|
32
|
+
SelectTrigger,
|
|
33
|
+
SelectValue,
|
|
34
|
+
} from "@/components/ui/select";
|
|
35
|
+
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
|
36
|
+
import { PaymentStep } from "@/components/checkout/payment-step";
|
|
37
|
+
import type { PaymentMethod } from "@/components/checkout/payment-method-card";
|
|
38
|
+
import { toast } from "sonner";
|
|
39
|
+
import { z } from "zod";
|
|
40
|
+
import {
|
|
41
|
+
Loader2,
|
|
42
|
+
ChevronLeft,
|
|
43
|
+
ChevronRight,
|
|
44
|
+
Check,
|
|
45
|
+
Package,
|
|
46
|
+
Truck,
|
|
47
|
+
CreditCard,
|
|
48
|
+
ClipboardCheck,
|
|
49
|
+
Tag,
|
|
50
|
+
X,
|
|
51
|
+
User,
|
|
52
|
+
ShoppingBag,
|
|
53
|
+
Gift,
|
|
54
|
+
} from "lucide-react";
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// CONSTANTS
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
const COUNTRIES = [
|
|
61
|
+
{ code: "PL", name: "Polska" },
|
|
62
|
+
{ code: "DE", name: "Niemcy" },
|
|
63
|
+
{ code: "CZ", name: "Czechy" },
|
|
64
|
+
{ code: "SK", name: "Słowacja" },
|
|
65
|
+
{ code: "AT", name: "Austria" },
|
|
66
|
+
{ code: "FR", name: "Francja" },
|
|
67
|
+
{ code: "NL", name: "Holandia" },
|
|
68
|
+
{ code: "BE", name: "Belgia" },
|
|
69
|
+
{ code: "IT", name: "Włochy" },
|
|
70
|
+
{ code: "ES", name: "Hiszpania" },
|
|
71
|
+
{ code: "GB", name: "Wielka Brytania" },
|
|
72
|
+
{ code: "US", name: "Stany Zjednoczone" },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// VALIDATION SCHEMAS
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
const contactSchema = z.object({
|
|
80
|
+
email: z.string().email("Podaj prawidłowy adres email"),
|
|
81
|
+
phone: z
|
|
82
|
+
.string()
|
|
83
|
+
.min(9, "Numer telefonu jest wymagany")
|
|
84
|
+
.regex(/^\+?[0-9\s\-]{9,15}$/, "Nieprawidłowy format numeru telefonu"),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const addressSchema = z.object({
|
|
88
|
+
firstName: z.string().min(1, "Imię jest wymagane"),
|
|
89
|
+
lastName: z.string().min(1, "Nazwisko jest wymagane"),
|
|
90
|
+
address1: z.string().min(1, "Adres jest wymagany"),
|
|
91
|
+
address2: z.string().optional(),
|
|
92
|
+
city: z.string().min(1, "Miasto jest wymagane"),
|
|
93
|
+
province: z.string().optional(),
|
|
94
|
+
zip: z.string().min(1, "Kod pocztowy jest wymagany"),
|
|
95
|
+
country: z.string().min(2, "Kraj jest wymagany"),
|
|
96
|
+
phone: z.string().optional(),
|
|
97
|
+
company: z.string().optional(),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// TYPES
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
type CheckoutStep = "contact" | "shipping" | "delivery" | "payment" | "review";
|
|
105
|
+
|
|
106
|
+
interface ShippingRate {
|
|
107
|
+
handle: string;
|
|
108
|
+
title: string;
|
|
109
|
+
price: {
|
|
110
|
+
amount: string;
|
|
111
|
+
currencyCode: string;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface AddressForm {
|
|
116
|
+
firstName: string;
|
|
117
|
+
lastName: string;
|
|
118
|
+
address1: string;
|
|
119
|
+
address2: string;
|
|
120
|
+
city: string;
|
|
121
|
+
province: string;
|
|
122
|
+
zip: string;
|
|
123
|
+
country: string;
|
|
124
|
+
phone: string;
|
|
125
|
+
company: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface CheckoutFormState {
|
|
129
|
+
email: string;
|
|
130
|
+
phone: string;
|
|
131
|
+
shippingAddress: AddressForm;
|
|
132
|
+
billingAddress: AddressForm;
|
|
133
|
+
sameAsBilling: boolean;
|
|
134
|
+
selectedShippingRate: string | null;
|
|
135
|
+
selectedPaymentMethodId: string | null;
|
|
136
|
+
discountCode: string;
|
|
137
|
+
appliedDiscountCode: string | null;
|
|
138
|
+
giftCardCode: string;
|
|
139
|
+
acceptTerms: boolean;
|
|
140
|
+
acceptMarketing: boolean;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const emptyAddress: AddressForm = {
|
|
144
|
+
firstName: "",
|
|
145
|
+
lastName: "",
|
|
146
|
+
address1: "",
|
|
147
|
+
address2: "",
|
|
148
|
+
city: "",
|
|
149
|
+
province: "",
|
|
150
|
+
zip: "",
|
|
151
|
+
country: "PL",
|
|
152
|
+
phone: "",
|
|
153
|
+
company: "",
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// ADDRESS FORM FIELDS COMPONENT (extracted to prevent re-mount on state change)
|
|
158
|
+
// ============================================================================
|
|
159
|
+
|
|
160
|
+
interface AddressFormFieldsProps {
|
|
161
|
+
prefix: "shipping" | "billing";
|
|
162
|
+
values: AddressForm;
|
|
163
|
+
onChange: (field: keyof AddressForm, value: string) => void;
|
|
164
|
+
errors: Record<string, string>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFieldsProps) {
|
|
168
|
+
return (
|
|
169
|
+
<div className="space-y-4">
|
|
170
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
171
|
+
<div>
|
|
172
|
+
<Label htmlFor={`${prefix}_firstName`}>Imię *</Label>
|
|
173
|
+
<Input
|
|
174
|
+
id={`${prefix}_firstName`}
|
|
175
|
+
value={values.firstName}
|
|
176
|
+
onChange={(e) => onChange("firstName", e.target.value)}
|
|
177
|
+
className={errors[`${prefix}_firstName`] ? "border-destructive" : ""}
|
|
178
|
+
/>
|
|
179
|
+
{errors[`${prefix}_firstName`] && (
|
|
180
|
+
<p className="mt-1 text-sm text-destructive">{errors[`${prefix}_firstName`]}</p>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
<div>
|
|
184
|
+
<Label htmlFor={`${prefix}_lastName`}>Nazwisko *</Label>
|
|
185
|
+
<Input
|
|
186
|
+
id={`${prefix}_lastName`}
|
|
187
|
+
value={values.lastName}
|
|
188
|
+
onChange={(e) => onChange("lastName", e.target.value)}
|
|
189
|
+
className={errors[`${prefix}_lastName`] ? "border-destructive" : ""}
|
|
190
|
+
/>
|
|
191
|
+
{errors[`${prefix}_lastName`] && (
|
|
192
|
+
<p className="mt-1 text-sm text-destructive">{errors[`${prefix}_lastName`]}</p>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div>
|
|
198
|
+
<Label htmlFor={`${prefix}_company`}>Firma (opcjonalnie)</Label>
|
|
199
|
+
<Input
|
|
200
|
+
id={`${prefix}_company`}
|
|
201
|
+
value={values.company}
|
|
202
|
+
onChange={(e) => onChange("company", e.target.value)}
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div>
|
|
207
|
+
<Label htmlFor={`${prefix}_address1`}>Adres *</Label>
|
|
208
|
+
<Input
|
|
209
|
+
id={`${prefix}_address1`}
|
|
210
|
+
placeholder="Ulica i numer"
|
|
211
|
+
value={values.address1}
|
|
212
|
+
onChange={(e) => onChange("address1", e.target.value)}
|
|
213
|
+
className={errors[`${prefix}_address1`] ? "border-destructive" : ""}
|
|
214
|
+
/>
|
|
215
|
+
{errors[`${prefix}_address1`] && (
|
|
216
|
+
<p className="mt-1 text-sm text-destructive">{errors[`${prefix}_address1`]}</p>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div>
|
|
221
|
+
<Label htmlFor={`${prefix}_address2`}>Mieszkanie, piętro (opcjonalnie)</Label>
|
|
222
|
+
<Input
|
|
223
|
+
id={`${prefix}_address2`}
|
|
224
|
+
value={values.address2}
|
|
225
|
+
onChange={(e) => onChange("address2", e.target.value)}
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
230
|
+
<div>
|
|
231
|
+
<Label htmlFor={`${prefix}_zip`}>Kod pocztowy *</Label>
|
|
232
|
+
<Input
|
|
233
|
+
id={`${prefix}_zip`}
|
|
234
|
+
placeholder="00-000"
|
|
235
|
+
value={values.zip}
|
|
236
|
+
onChange={(e) => onChange("zip", e.target.value)}
|
|
237
|
+
className={errors[`${prefix}_zip`] ? "border-destructive" : ""}
|
|
238
|
+
/>
|
|
239
|
+
{errors[`${prefix}_zip`] && (
|
|
240
|
+
<p className="mt-1 text-sm text-destructive">{errors[`${prefix}_zip`]}</p>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
<div>
|
|
244
|
+
<Label htmlFor={`${prefix}_city`}>Miasto *</Label>
|
|
245
|
+
<Input
|
|
246
|
+
id={`${prefix}_city`}
|
|
247
|
+
value={values.city}
|
|
248
|
+
onChange={(e) => onChange("city", e.target.value)}
|
|
249
|
+
className={errors[`${prefix}_city`] ? "border-destructive" : ""}
|
|
250
|
+
/>
|
|
251
|
+
{errors[`${prefix}_city`] && (
|
|
252
|
+
<p className="mt-1 text-sm text-destructive">{errors[`${prefix}_city`]}</p>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
258
|
+
<div>
|
|
259
|
+
<Label htmlFor={`${prefix}_country`}>Kraj *</Label>
|
|
260
|
+
<Select value={values.country} onValueChange={(value) => onChange("country", value)}>
|
|
261
|
+
<SelectTrigger className={errors[`${prefix}_country`] ? "border-destructive" : ""}>
|
|
262
|
+
<SelectValue placeholder="Wybierz kraj" />
|
|
263
|
+
</SelectTrigger>
|
|
264
|
+
<SelectContent>
|
|
265
|
+
{COUNTRIES.map((country) => (
|
|
266
|
+
<SelectItem key={country.code} value={country.code}>
|
|
267
|
+
{country.name}
|
|
268
|
+
</SelectItem>
|
|
269
|
+
))}
|
|
270
|
+
</SelectContent>
|
|
271
|
+
</Select>
|
|
272
|
+
{errors[`${prefix}_country`] && (
|
|
273
|
+
<p className="mt-1 text-sm text-destructive">{errors[`${prefix}_country`]}</p>
|
|
274
|
+
)}
|
|
275
|
+
</div>
|
|
276
|
+
<div>
|
|
277
|
+
<Label htmlFor={`${prefix}_province`}>Województwo (opcjonalnie)</Label>
|
|
278
|
+
<Input
|
|
279
|
+
id={`${prefix}_province`}
|
|
280
|
+
value={values.province}
|
|
281
|
+
onChange={(e) => onChange("province", e.target.value)}
|
|
282
|
+
/>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// CHECKOUT PAGE
|
|
291
|
+
// ============================================================================
|
|
292
|
+
|
|
293
|
+
export default function CheckoutPage() {
|
|
294
|
+
const router = useRouter();
|
|
295
|
+
const { cartId, clearCart } = useCartStore();
|
|
296
|
+
const { items, isLoading: isCartLoading } = useCartSync();
|
|
297
|
+
|
|
298
|
+
// Step management
|
|
299
|
+
const [currentStep, setCurrentStep] = useState<CheckoutStep>("contact");
|
|
300
|
+
const [checkoutId, setCheckoutId] = useState<string | null>(null);
|
|
301
|
+
|
|
302
|
+
// Detect if all items are gift cards (skip shipping)
|
|
303
|
+
const allGiftCards = items.length > 0 && items.every((item) => item.productType === "GIFT_CARD");
|
|
304
|
+
const hasGiftCards = items.some((item) => item.productType === "GIFT_CARD");
|
|
305
|
+
|
|
306
|
+
// Gift card recipient state
|
|
307
|
+
const [giftCardRecipients, setGiftCardRecipients] = useState<
|
|
308
|
+
Record<string, { recipientEmail?: string; recipientName?: string; message?: string }>
|
|
309
|
+
>({});
|
|
310
|
+
|
|
311
|
+
// Form state
|
|
312
|
+
const [formState, setFormState] = useState<CheckoutFormState>({
|
|
313
|
+
email: "",
|
|
314
|
+
phone: "",
|
|
315
|
+
shippingAddress: { ...emptyAddress },
|
|
316
|
+
billingAddress: { ...emptyAddress },
|
|
317
|
+
sameAsBilling: true,
|
|
318
|
+
selectedShippingRate: null,
|
|
319
|
+
selectedPaymentMethodId: null,
|
|
320
|
+
discountCode: "",
|
|
321
|
+
appliedDiscountCode: null,
|
|
322
|
+
giftCardCode: "",
|
|
323
|
+
acceptTerms: false,
|
|
324
|
+
acceptMarketing: false,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Validation errors
|
|
328
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
329
|
+
|
|
330
|
+
// Mutations
|
|
331
|
+
const createCheckout = useCheckoutCreate();
|
|
332
|
+
const updateEmail = useCheckoutEmailUpdate();
|
|
333
|
+
const updateShippingAddress = useCheckoutShippingAddressUpdate();
|
|
334
|
+
const updateBillingAddress = useCheckoutBillingAddressUpdate();
|
|
335
|
+
const updateShippingLine = useCheckoutShippingLineUpdate();
|
|
336
|
+
const applyDiscount = useCheckoutDiscountCodeApply();
|
|
337
|
+
const removeDiscount = useCheckoutDiscountCodeRemove();
|
|
338
|
+
const applyGiftCard = useCheckoutGiftCardApply();
|
|
339
|
+
const removeGiftCard = useCheckoutGiftCardRemove();
|
|
340
|
+
const completeCheckout = useCheckoutComplete();
|
|
341
|
+
|
|
342
|
+
// Query checkout for shipping rates
|
|
343
|
+
const { data: checkoutData, refetch: refetchCheckout } = useCheckout(checkoutId);
|
|
344
|
+
const checkout = checkoutData?.checkout;
|
|
345
|
+
|
|
346
|
+
// Sync discount codes from checkout (transferred from cart) to form state
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
if (checkout?.discountCodes?.length > 0 && !formState.appliedDiscountCode) {
|
|
349
|
+
const applicableCode = checkout.discountCodes.find((dc: any) => dc.applicable);
|
|
350
|
+
if (applicableCode) {
|
|
351
|
+
setFormState((prev) => ({
|
|
352
|
+
...prev,
|
|
353
|
+
appliedDiscountCode: applicableCode.code,
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}, [checkout?.discountCodes]);
|
|
358
|
+
|
|
359
|
+
// Loading state
|
|
360
|
+
const isLoading =
|
|
361
|
+
createCheckout.isPending ||
|
|
362
|
+
updateEmail.isPending ||
|
|
363
|
+
updateShippingAddress.isPending ||
|
|
364
|
+
updateBillingAddress.isPending ||
|
|
365
|
+
updateShippingLine.isPending ||
|
|
366
|
+
applyDiscount.isPending ||
|
|
367
|
+
removeDiscount.isPending ||
|
|
368
|
+
applyGiftCard.isPending ||
|
|
369
|
+
removeGiftCard.isPending ||
|
|
370
|
+
completeCheckout.isPending;
|
|
371
|
+
|
|
372
|
+
// Calculate totals from checkout or cart
|
|
373
|
+
const currencyCode = checkout?.currencyCode || items[0]?.price.currencyCode || "PLN";
|
|
374
|
+
|
|
375
|
+
// Use centralized formatPrice with proper locale based on currency
|
|
376
|
+
const formatPrice = (amount: string | number) => formatAmount(amount, currencyCode);
|
|
377
|
+
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// STEP 1: Create checkout and update contact info
|
|
380
|
+
// ============================================================================
|
|
381
|
+
|
|
382
|
+
const handleContactSubmit = async () => {
|
|
383
|
+
// Validate contact info
|
|
384
|
+
const result = contactSchema.safeParse({
|
|
385
|
+
email: formState.email,
|
|
386
|
+
phone: formState.phone,
|
|
387
|
+
});
|
|
388
|
+
if (!result.success) {
|
|
389
|
+
const fieldErrors: Record<string, string> = {};
|
|
390
|
+
result.error.errors.forEach((err) => {
|
|
391
|
+
if (err.path[0]) {
|
|
392
|
+
fieldErrors[err.path[0] as string] = err.message;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
setErrors(fieldErrors);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
setErrors({});
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
// Create checkout if not exists
|
|
402
|
+
if (!checkoutId) {
|
|
403
|
+
const createResult = await createCheckout.mutateAsync({
|
|
404
|
+
input: {
|
|
405
|
+
cartId,
|
|
406
|
+
email: formState.email,
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (createResult.checkoutCreate.userErrors?.length > 0) {
|
|
411
|
+
toast.error(createResult.checkoutCreate.userErrors[0].message);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (createResult.checkoutCreate.checkout) {
|
|
416
|
+
setCheckoutId(createResult.checkoutCreate.checkout.id);
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
// Update email on existing checkout
|
|
420
|
+
const emailResult = await updateEmail.mutateAsync({
|
|
421
|
+
checkoutId,
|
|
422
|
+
email: formState.email,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
if (emailResult.checkoutEmailUpdate.userErrors?.length > 0) {
|
|
426
|
+
toast.error(emailResult.checkoutEmailUpdate.userErrors[0].message);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
setCurrentStep(allGiftCards ? "payment" : "shipping");
|
|
432
|
+
} catch (error: any) {
|
|
433
|
+
toast.error(error.message || "Nie udało się przetworzyć danych kontaktowych");
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// ============================================================================
|
|
438
|
+
// STEP 2: Update shipping address
|
|
439
|
+
// ============================================================================
|
|
440
|
+
|
|
441
|
+
const handleShippingSubmit = async () => {
|
|
442
|
+
// Validate address
|
|
443
|
+
const result = addressSchema.safeParse(formState.shippingAddress);
|
|
444
|
+
if (!result.success) {
|
|
445
|
+
const fieldErrors: Record<string, string> = {};
|
|
446
|
+
result.error.errors.forEach((err) => {
|
|
447
|
+
if (err.path[0]) {
|
|
448
|
+
fieldErrors[`shipping_${err.path[0]}`] = err.message;
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
setErrors(fieldErrors);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Validate billing address if different
|
|
456
|
+
if (!formState.sameAsBilling) {
|
|
457
|
+
const billingResult = addressSchema.safeParse(formState.billingAddress);
|
|
458
|
+
if (!billingResult.success) {
|
|
459
|
+
const fieldErrors: Record<string, string> = {};
|
|
460
|
+
billingResult.error.errors.forEach((err) => {
|
|
461
|
+
if (err.path[0]) {
|
|
462
|
+
fieldErrors[`billing_${err.path[0]}`] = err.message;
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
setErrors(fieldErrors);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
setErrors({});
|
|
471
|
+
|
|
472
|
+
if (!checkoutId) {
|
|
473
|
+
toast.error("Checkout nie znaleziony. Wróć i spróbuj ponownie.");
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
// Update shipping address
|
|
479
|
+
const addressResult = await updateShippingAddress.mutateAsync({
|
|
480
|
+
checkoutId,
|
|
481
|
+
shippingAddress: {
|
|
482
|
+
...formState.shippingAddress,
|
|
483
|
+
phone: formState.phone,
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
if (addressResult.checkoutShippingAddressUpdate.userErrors?.length > 0) {
|
|
488
|
+
toast.error(addressResult.checkoutShippingAddressUpdate.userErrors[0].message);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Update billing address
|
|
493
|
+
const billingToUse = formState.sameAsBilling
|
|
494
|
+
? formState.shippingAddress
|
|
495
|
+
: formState.billingAddress;
|
|
496
|
+
|
|
497
|
+
const billingResult = await updateBillingAddress.mutateAsync({
|
|
498
|
+
checkoutId,
|
|
499
|
+
billingAddress: {
|
|
500
|
+
...billingToUse,
|
|
501
|
+
phone: formState.phone,
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
if (billingResult.checkoutBillingAddressUpdate.userErrors?.length > 0) {
|
|
506
|
+
toast.error(billingResult.checkoutBillingAddressUpdate.userErrors[0].message);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Refetch checkout to get shipping rates
|
|
511
|
+
await refetchCheckout();
|
|
512
|
+
setCurrentStep("delivery");
|
|
513
|
+
} catch (error: any) {
|
|
514
|
+
toast.error(error.message || "Nie udało się zaktualizować adresu");
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// ============================================================================
|
|
519
|
+
// STEP 3: Select shipping method
|
|
520
|
+
// ============================================================================
|
|
521
|
+
|
|
522
|
+
const handleDeliverySubmit = async () => {
|
|
523
|
+
if (!formState.selectedShippingRate) {
|
|
524
|
+
setErrors({ shipping: "Wybierz metodę dostawy" });
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
setErrors({});
|
|
528
|
+
|
|
529
|
+
if (!checkoutId) {
|
|
530
|
+
toast.error("Checkout nie znaleziony. Wróć i spróbuj ponownie.");
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
const shippingResult = await updateShippingLine.mutateAsync({
|
|
536
|
+
checkoutId,
|
|
537
|
+
shippingRateHandle: formState.selectedShippingRate,
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
if (shippingResult.checkoutShippingLineUpdate.userErrors?.length > 0) {
|
|
541
|
+
toast.error(shippingResult.checkoutShippingLineUpdate.userErrors[0].message);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
await refetchCheckout();
|
|
546
|
+
setCurrentStep("payment");
|
|
547
|
+
} catch (error: any) {
|
|
548
|
+
toast.error(error.message || "Nie udało się wybrać metody dostawy");
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// STEP 4: Select payment method
|
|
554
|
+
// ============================================================================
|
|
555
|
+
|
|
556
|
+
const handlePaymentMethodSelect = (paymentMethodId: string) => {
|
|
557
|
+
// Validate payment method supports current currency
|
|
558
|
+
const selectedMethod = checkout?.availablePaymentMethods?.find(
|
|
559
|
+
(pm: any) => pm.id === paymentMethodId
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
if (selectedMethod?.supportedCurrencies?.length > 0) {
|
|
563
|
+
const supportsCurrency = selectedMethod.supportedCurrencies.includes(currencyCode);
|
|
564
|
+
if (!supportsCurrency) {
|
|
565
|
+
setErrors({
|
|
566
|
+
payment: `Ta metoda płatności nie obsługuje waluty ${currencyCode}. Wybierz inną metodę.`,
|
|
567
|
+
});
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
setErrors({});
|
|
573
|
+
setFormState((prev) => ({ ...prev, selectedPaymentMethodId: paymentMethodId }));
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const handlePaymentSubmit = async () => {
|
|
577
|
+
if (!formState.selectedPaymentMethodId) {
|
|
578
|
+
setErrors({ payment: "Wybierz metodę płatności" });
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Validate selected payment method still exists and is valid
|
|
583
|
+
const selectedMethod = checkout?.availablePaymentMethods?.find(
|
|
584
|
+
(pm: any) => pm.id === formState.selectedPaymentMethodId
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
if (!selectedMethod) {
|
|
588
|
+
setErrors({ payment: "Wybrana metoda płatności jest niedostępna. Wybierz inną metodę." });
|
|
589
|
+
setFormState((prev) => ({ ...prev, selectedPaymentMethodId: null }));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Validate currency support
|
|
594
|
+
if (selectedMethod.supportedCurrencies?.length > 0) {
|
|
595
|
+
const supportsCurrency = selectedMethod.supportedCurrencies.includes(currencyCode);
|
|
596
|
+
if (!supportsCurrency) {
|
|
597
|
+
setErrors({
|
|
598
|
+
payment: `Metoda "${selectedMethod.name}" nie obsługuje waluty ${currencyCode}. Wybierz inną metodę.`,
|
|
599
|
+
});
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
setErrors({});
|
|
605
|
+
setCurrentStep("review");
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// ============================================================================
|
|
609
|
+
// DISCOUNT CODE
|
|
610
|
+
// ============================================================================
|
|
611
|
+
|
|
612
|
+
const handleApplyDiscount = async () => {
|
|
613
|
+
if (!formState.discountCode.trim()) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!checkoutId) {
|
|
618
|
+
toast.error("Checkout nie znaleziony");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
const result = await applyDiscount.mutateAsync({
|
|
624
|
+
checkoutId,
|
|
625
|
+
discountCode: formState.discountCode.trim(),
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const payload = result.checkoutDiscountCodeApply;
|
|
629
|
+
|
|
630
|
+
if (payload?.userErrors?.length > 0) {
|
|
631
|
+
toast.error(payload.userErrors[0].message);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (!payload?.checkout) {
|
|
636
|
+
toast.error("Nie udało się zastosować kodu rabatowego");
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
setFormState((prev) => ({
|
|
641
|
+
...prev,
|
|
642
|
+
appliedDiscountCode: prev.discountCode.trim(),
|
|
643
|
+
discountCode: "",
|
|
644
|
+
}));
|
|
645
|
+
await refetchCheckout();
|
|
646
|
+
toast.success("Kod rabatowy został zastosowany");
|
|
647
|
+
} catch (error: any) {
|
|
648
|
+
toast.error(error.message || "Nie udało się zastosować kodu rabatowego");
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
const handleRemoveDiscount = async () => {
|
|
653
|
+
if (!checkoutId || !formState.appliedDiscountCode) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const result = await removeDiscount.mutateAsync({
|
|
659
|
+
checkoutId,
|
|
660
|
+
discountCode: formState.appliedDiscountCode,
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
if (result.checkoutDiscountCodeRemove.userErrors?.length > 0) {
|
|
664
|
+
toast.error(result.checkoutDiscountCodeRemove.userErrors[0].message);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
setFormState((prev) => ({
|
|
669
|
+
...prev,
|
|
670
|
+
appliedDiscountCode: null,
|
|
671
|
+
}));
|
|
672
|
+
await refetchCheckout();
|
|
673
|
+
toast.success("Kod rabatowy został usunięty");
|
|
674
|
+
} catch (error: any) {
|
|
675
|
+
toast.error(error.message || "Nie udało się usunąć kodu rabatowego");
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// ============================================================================
|
|
680
|
+
// GIFT CARD
|
|
681
|
+
// ============================================================================
|
|
682
|
+
|
|
683
|
+
const handleApplyGiftCard = async () => {
|
|
684
|
+
if (!formState.giftCardCode.trim()) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!checkoutId) {
|
|
689
|
+
toast.error("Checkout nie znaleziony");
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
const result = await applyGiftCard.mutateAsync({
|
|
695
|
+
checkoutId,
|
|
696
|
+
giftCardCode: formState.giftCardCode.trim().toUpperCase(),
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
if (result.checkoutGiftCardApply.userErrors?.length > 0) {
|
|
700
|
+
const error = result.checkoutGiftCardApply.userErrors[0];
|
|
701
|
+
// Translate common error codes to user-friendly messages
|
|
702
|
+
const errorMessages: Record<string, string> = {
|
|
703
|
+
GIFT_CARD_NOT_FOUND: "Nie znaleziono karty podarunkowej o podanym kodzie",
|
|
704
|
+
GIFT_CARD_EXPIRED: "Ta karta podarunkowa wygasła",
|
|
705
|
+
GIFT_CARD_DISABLED: "Ta karta podarunkowa jest wyłączona",
|
|
706
|
+
GIFT_CARD_NO_BALANCE: "Ta karta podarunkowa nie ma dostępnego salda",
|
|
707
|
+
GIFT_CARD_ALREADY_APPLIED: "Ta karta jest już zastosowana",
|
|
708
|
+
};
|
|
709
|
+
toast.error(errorMessages[error.code] || error.message);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
setFormState((prev) => ({
|
|
714
|
+
...prev,
|
|
715
|
+
giftCardCode: "",
|
|
716
|
+
}));
|
|
717
|
+
await refetchCheckout();
|
|
718
|
+
toast.success("Karta podarunkowa została zastosowana");
|
|
719
|
+
} catch (error: any) {
|
|
720
|
+
toast.error(error.message || "Nie udało się zastosować karty podarunkowej");
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const handleRemoveGiftCard = async (giftCardCode: string) => {
|
|
725
|
+
if (!checkoutId) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
try {
|
|
730
|
+
const result = await removeGiftCard.mutateAsync({
|
|
731
|
+
checkoutId,
|
|
732
|
+
giftCardCode,
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
if (result.checkoutGiftCardRemove.userErrors?.length > 0) {
|
|
736
|
+
toast.error(result.checkoutGiftCardRemove.userErrors[0].message);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
await refetchCheckout();
|
|
741
|
+
toast.success("Karta podarunkowa została usunięta");
|
|
742
|
+
} catch (error: any) {
|
|
743
|
+
toast.error(error.message || "Nie udało się usunąć karty podarunkowej");
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// ============================================================================
|
|
748
|
+
// STEP 5: Complete checkout
|
|
749
|
+
// ============================================================================
|
|
750
|
+
|
|
751
|
+
const handleCompleteCheckout = async () => {
|
|
752
|
+
if (!formState.acceptTerms) {
|
|
753
|
+
setErrors({ terms: "Musisz zaakceptować regulamin" });
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (!formState.selectedPaymentMethodId) {
|
|
758
|
+
setErrors({ payment: "Wybierz metodę płatności" });
|
|
759
|
+
setCurrentStep("payment");
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
setErrors({});
|
|
764
|
+
|
|
765
|
+
if (!checkoutId) {
|
|
766
|
+
toast.error("Checkout nie znaleziony. Wróć i spróbuj ponownie.");
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
const completeResult = await completeCheckout.mutateAsync({
|
|
772
|
+
checkoutId,
|
|
773
|
+
input: {
|
|
774
|
+
paymentMethodId: formState.selectedPaymentMethodId,
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
if (completeResult.checkoutComplete.userErrors?.length > 0) {
|
|
779
|
+
const error = completeResult.checkoutComplete.userErrors[0];
|
|
780
|
+
const errorCode = error.code;
|
|
781
|
+
const errorMessage = error.message;
|
|
782
|
+
|
|
783
|
+
// Handle specific payment-related error codes
|
|
784
|
+
switch (errorCode) {
|
|
785
|
+
case "INVALID_PAYMENT_METHOD":
|
|
786
|
+
case "PAYMENT_METHOD_NOT_FOUND":
|
|
787
|
+
setErrors({ payment: "Wybrana metoda płatności jest niedostępna. Wybierz inną metodę." });
|
|
788
|
+
setFormState((prev) => ({ ...prev, selectedPaymentMethodId: null }));
|
|
789
|
+
setCurrentStep("payment");
|
|
790
|
+
toast.error("Metoda płatności jest niedostępna");
|
|
791
|
+
break;
|
|
792
|
+
case "PAYMENT_METHOD_CURRENCY_NOT_SUPPORTED":
|
|
793
|
+
setErrors({ payment: `Metoda płatności nie obsługuje wybranej waluty (${currencyCode}).` });
|
|
794
|
+
setCurrentStep("payment");
|
|
795
|
+
toast.error("Waluta nie jest obsługiwana przez tę metodę płatności");
|
|
796
|
+
break;
|
|
797
|
+
case "PAYMENT_DECLINED":
|
|
798
|
+
toast.error("Płatność została odrzucona. Spróbuj ponownie lub wybierz inną metodę.");
|
|
799
|
+
setCurrentStep("payment");
|
|
800
|
+
break;
|
|
801
|
+
case "CHECKOUT_EXPIRED":
|
|
802
|
+
toast.error("Sesja checkout wygasła. Proszę odświeżyć stronę i spróbować ponownie.");
|
|
803
|
+
break;
|
|
804
|
+
case "INVENTORY_NOT_AVAILABLE":
|
|
805
|
+
toast.error("Niektóre produkty są niedostępne. Sprawdź swój koszyk.");
|
|
806
|
+
break;
|
|
807
|
+
default:
|
|
808
|
+
toast.error(errorMessage || "Nie udało się sfinalizować zamówienia");
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Handle payment redirect or order completion
|
|
814
|
+
if (completeResult.checkoutComplete.paymentUrl) {
|
|
815
|
+
// Redirect to external payment provider (PayU, Stripe, P24)
|
|
816
|
+
clearCart();
|
|
817
|
+
window.location.href = completeResult.checkoutComplete.paymentUrl;
|
|
818
|
+
} else if (completeResult.checkoutComplete.order) {
|
|
819
|
+
// Order completed directly (COD, free order, bank transfer)
|
|
820
|
+
clearCart();
|
|
821
|
+
const orderId = completeResult.checkoutComplete.order.id;
|
|
822
|
+
const paymentType = formState.selectedPaymentMethodId
|
|
823
|
+
? checkout?.availablePaymentMethods?.find(
|
|
824
|
+
(pm: any) => pm.id === formState.selectedPaymentMethodId
|
|
825
|
+
)?.type
|
|
826
|
+
: null;
|
|
827
|
+
|
|
828
|
+
// Pass payment type to success page for conditional display
|
|
829
|
+
const searchParams = paymentType === 'BANK_TRANSFER' ? '?payment=bank_transfer' : '';
|
|
830
|
+
router.push(`/checkout/success/${orderId}${searchParams}`);
|
|
831
|
+
} else {
|
|
832
|
+
// Fallback: no payment URL and no order - show error
|
|
833
|
+
toast.error("Wystąpił nieoczekiwany błąd. Skontaktuj się z obsługą.");
|
|
834
|
+
}
|
|
835
|
+
} catch (error: any) {
|
|
836
|
+
toast.error(error.message || "Nie udało się sfinalizować zamówienia");
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
// ============================================================================
|
|
841
|
+
// NAVIGATION
|
|
842
|
+
// ============================================================================
|
|
843
|
+
|
|
844
|
+
const goBack = () => {
|
|
845
|
+
const stepOrder: CheckoutStep[] = allGiftCards
|
|
846
|
+
? ["contact", "payment", "review"]
|
|
847
|
+
: ["contact", "shipping", "delivery", "payment", "review"];
|
|
848
|
+
const currentIndex = stepOrder.indexOf(currentStep);
|
|
849
|
+
if (currentIndex > 0) {
|
|
850
|
+
setCurrentStep(stepOrder[currentIndex - 1]);
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// ============================================================================
|
|
855
|
+
// RENDER HELPERS
|
|
856
|
+
// ============================================================================
|
|
857
|
+
|
|
858
|
+
const availableShippingRates: ShippingRate[] = checkout?.availableShippingRates || [];
|
|
859
|
+
const shippingRatesReady = checkout?.shippingRatesReady ?? false;
|
|
860
|
+
|
|
861
|
+
// Wait for cart data to load
|
|
862
|
+
if (isCartLoading) {
|
|
863
|
+
return (
|
|
864
|
+
<div className="container mx-auto px-4 py-16 text-center">
|
|
865
|
+
<Loader2 className="mx-auto h-8 w-8 animate-spin text-muted-foreground" />
|
|
866
|
+
<p className="mt-4 text-muted-foreground">Ładowanie...</p>
|
|
867
|
+
</div>
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Empty cart check
|
|
872
|
+
if (items.length === 0 && !checkoutId) {
|
|
873
|
+
return (
|
|
874
|
+
<div className="container mx-auto px-4 py-16 text-center">
|
|
875
|
+
<ShoppingBag className="mx-auto h-16 w-16 text-muted-foreground" />
|
|
876
|
+
<h1 className="mt-4 text-2xl font-bold">Twój koszyk jest pusty</h1>
|
|
877
|
+
<p className="mt-2 text-muted-foreground">
|
|
878
|
+
Dodaj produkty do koszyka, aby kontynuować zakupy.
|
|
879
|
+
</p>
|
|
880
|
+
<Button className="mt-6" asChild>
|
|
881
|
+
<Link href="/products">Przeglądaj produkty</Link>
|
|
882
|
+
</Button>
|
|
883
|
+
</div>
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// ============================================================================
|
|
888
|
+
// STEP CONFIG
|
|
889
|
+
// ============================================================================
|
|
890
|
+
|
|
891
|
+
const allSteps: { id: CheckoutStep; label: string; icon: React.ReactNode }[] = [
|
|
892
|
+
{ id: "contact", label: "Kontakt", icon: <User className="h-4 w-4" /> },
|
|
893
|
+
{ id: "shipping", label: "Adres", icon: <Package className="h-4 w-4" /> },
|
|
894
|
+
{ id: "delivery", label: "Dostawa", icon: <Truck className="h-4 w-4" /> },
|
|
895
|
+
{ id: "payment", label: "Płatność", icon: <CreditCard className="h-4 w-4" /> },
|
|
896
|
+
{ id: "review", label: "Podsumowanie", icon: <ClipboardCheck className="h-4 w-4" /> },
|
|
897
|
+
];
|
|
898
|
+
// Skip shipping and delivery steps when all items are gift cards
|
|
899
|
+
const steps = allGiftCards
|
|
900
|
+
? allSteps.filter((s) => s.id !== "shipping" && s.id !== "delivery")
|
|
901
|
+
: allSteps;
|
|
902
|
+
|
|
903
|
+
const currentStepIndex = steps.findIndex((s) => s.id === currentStep);
|
|
904
|
+
|
|
905
|
+
// ============================================================================
|
|
906
|
+
// RENDER
|
|
907
|
+
// ============================================================================
|
|
908
|
+
|
|
909
|
+
return (
|
|
910
|
+
<div className="container mx-auto px-4 py-8">
|
|
911
|
+
<Breadcrumbs className="mb-6" />
|
|
912
|
+
|
|
913
|
+
<div className="mb-8 flex items-center justify-between">
|
|
914
|
+
<h1 className="text-3xl font-bold">Checkout</h1>
|
|
915
|
+
<Link href="/auth/login" className="text-sm text-primary hover:underline">
|
|
916
|
+
Masz konto? Zaloguj się
|
|
917
|
+
</Link>
|
|
918
|
+
</div>
|
|
919
|
+
|
|
920
|
+
{/* Step Indicator */}
|
|
921
|
+
<div className="mb-8">
|
|
922
|
+
<div className="flex items-center justify-between">
|
|
923
|
+
{steps.map((step, index) => (
|
|
924
|
+
<div key={step.id} className="flex items-center">
|
|
925
|
+
<div
|
|
926
|
+
className={`flex h-10 w-10 items-center justify-center rounded-full text-sm font-medium transition-colors ${
|
|
927
|
+
index < currentStepIndex
|
|
928
|
+
? "bg-primary text-primary-foreground"
|
|
929
|
+
: index === currentStepIndex
|
|
930
|
+
? "bg-primary text-primary-foreground ring-4 ring-primary/20"
|
|
931
|
+
: "bg-muted text-muted-foreground"
|
|
932
|
+
}`}
|
|
933
|
+
>
|
|
934
|
+
{index < currentStepIndex ? <Check className="h-5 w-5" /> : step.icon}
|
|
935
|
+
</div>
|
|
936
|
+
<span
|
|
937
|
+
className={`ml-2 hidden text-sm font-medium sm:inline ${
|
|
938
|
+
index <= currentStepIndex ? "text-foreground" : "text-muted-foreground"
|
|
939
|
+
}`}
|
|
940
|
+
>
|
|
941
|
+
{step.label}
|
|
942
|
+
</span>
|
|
943
|
+
{index < steps.length - 1 && (
|
|
944
|
+
<div
|
|
945
|
+
className={`mx-2 h-0.5 w-8 sm:mx-4 sm:w-16 transition-colors ${
|
|
946
|
+
index < currentStepIndex ? "bg-primary" : "bg-muted"
|
|
947
|
+
}`}
|
|
948
|
+
/>
|
|
949
|
+
)}
|
|
950
|
+
</div>
|
|
951
|
+
))}
|
|
952
|
+
</div>
|
|
953
|
+
</div>
|
|
954
|
+
|
|
955
|
+
<div className="grid gap-8 lg:grid-cols-3">
|
|
956
|
+
{/* Main Content */}
|
|
957
|
+
<div className="lg:col-span-2 space-y-6">
|
|
958
|
+
{/* Step 1: Contact */}
|
|
959
|
+
{currentStep === "contact" && (
|
|
960
|
+
<Card>
|
|
961
|
+
<CardHeader>
|
|
962
|
+
<CardTitle className="flex items-center gap-2">
|
|
963
|
+
<User className="h-5 w-5" />
|
|
964
|
+
Dane kontaktowe
|
|
965
|
+
</CardTitle>
|
|
966
|
+
<CardDescription>
|
|
967
|
+
Podaj swój email i telefon - wyślemy na nie potwierdzenie zamówienia
|
|
968
|
+
</CardDescription>
|
|
969
|
+
</CardHeader>
|
|
970
|
+
<CardContent className="space-y-4">
|
|
971
|
+
<div>
|
|
972
|
+
<Label htmlFor="email">Email *</Label>
|
|
973
|
+
<Input
|
|
974
|
+
id="email"
|
|
975
|
+
type="email"
|
|
976
|
+
placeholder="twoj@email.pl"
|
|
977
|
+
value={formState.email}
|
|
978
|
+
onChange={(e) =>
|
|
979
|
+
setFormState((prev) => ({ ...prev, email: e.target.value }))
|
|
980
|
+
}
|
|
981
|
+
className={errors.email ? "border-destructive" : ""}
|
|
982
|
+
/>
|
|
983
|
+
{errors.email && (
|
|
984
|
+
<p className="mt-1 text-sm text-destructive">{errors.email}</p>
|
|
985
|
+
)}
|
|
986
|
+
</div>
|
|
987
|
+
|
|
988
|
+
<div>
|
|
989
|
+
<Label htmlFor="phone">Telefon *</Label>
|
|
990
|
+
<Input
|
|
991
|
+
id="phone"
|
|
992
|
+
type="tel"
|
|
993
|
+
placeholder="+48 123 456 789"
|
|
994
|
+
value={formState.phone}
|
|
995
|
+
onChange={(e) =>
|
|
996
|
+
setFormState((prev) => ({ ...prev, phone: e.target.value }))
|
|
997
|
+
}
|
|
998
|
+
className={errors.phone ? "border-destructive" : ""}
|
|
999
|
+
/>
|
|
1000
|
+
{errors.phone && (
|
|
1001
|
+
<p className="mt-1 text-sm text-destructive">{errors.phone}</p>
|
|
1002
|
+
)}
|
|
1003
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
1004
|
+
Kurier może potrzebować kontaktu w sprawie dostawy
|
|
1005
|
+
</p>
|
|
1006
|
+
</div>
|
|
1007
|
+
|
|
1008
|
+
<div className="flex items-center space-x-2 pt-2">
|
|
1009
|
+
<Checkbox
|
|
1010
|
+
id="marketing"
|
|
1011
|
+
checked={formState.acceptMarketing}
|
|
1012
|
+
onCheckedChange={(checked) =>
|
|
1013
|
+
setFormState((prev) => ({ ...prev, acceptMarketing: checked === true }))
|
|
1014
|
+
}
|
|
1015
|
+
/>
|
|
1016
|
+
<Label htmlFor="marketing" className="text-sm font-normal cursor-pointer">
|
|
1017
|
+
Chcę otrzymywać informacje o promocjach i nowościach
|
|
1018
|
+
</Label>
|
|
1019
|
+
</div>
|
|
1020
|
+
|
|
1021
|
+
{/* Gift card recipient fields */}
|
|
1022
|
+
{hasGiftCards && (
|
|
1023
|
+
<div className="space-y-4 border-t pt-4">
|
|
1024
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
1025
|
+
<Gift className="h-4 w-4" />
|
|
1026
|
+
Dane odbiorcy karty podarunkowej
|
|
1027
|
+
</div>
|
|
1028
|
+
<p className="text-xs text-muted-foreground">
|
|
1029
|
+
Opcjonalnie: podaj dane osoby, która otrzyma kartę podarunkową
|
|
1030
|
+
</p>
|
|
1031
|
+
{items
|
|
1032
|
+
.filter((item) => item.productType === "GIFT_CARD")
|
|
1033
|
+
.map((item) => (
|
|
1034
|
+
<div key={item.variantId} className="space-y-3 rounded-lg border p-4">
|
|
1035
|
+
<p className="text-sm font-medium">{item.productTitle} — {item.variantTitle}</p>
|
|
1036
|
+
<div>
|
|
1037
|
+
<Label htmlFor={`recipient-email-${item.variantId}`}>E-mail odbiorcy</Label>
|
|
1038
|
+
<Input
|
|
1039
|
+
id={`recipient-email-${item.variantId}`}
|
|
1040
|
+
type="email"
|
|
1041
|
+
placeholder="odbiorca@email.pl"
|
|
1042
|
+
value={giftCardRecipients[item.variantId]?.recipientEmail || ""}
|
|
1043
|
+
onChange={(e) =>
|
|
1044
|
+
setGiftCardRecipients((prev) => ({
|
|
1045
|
+
...prev,
|
|
1046
|
+
[item.variantId]: {
|
|
1047
|
+
...prev[item.variantId],
|
|
1048
|
+
recipientEmail: e.target.value,
|
|
1049
|
+
},
|
|
1050
|
+
}))
|
|
1051
|
+
}
|
|
1052
|
+
/>
|
|
1053
|
+
</div>
|
|
1054
|
+
<div>
|
|
1055
|
+
<Label htmlFor={`recipient-name-${item.variantId}`}>Imię odbiorcy</Label>
|
|
1056
|
+
<Input
|
|
1057
|
+
id={`recipient-name-${item.variantId}`}
|
|
1058
|
+
type="text"
|
|
1059
|
+
placeholder="Jan"
|
|
1060
|
+
value={giftCardRecipients[item.variantId]?.recipientName || ""}
|
|
1061
|
+
onChange={(e) =>
|
|
1062
|
+
setGiftCardRecipients((prev) => ({
|
|
1063
|
+
...prev,
|
|
1064
|
+
[item.variantId]: {
|
|
1065
|
+
...prev[item.variantId],
|
|
1066
|
+
recipientName: e.target.value,
|
|
1067
|
+
},
|
|
1068
|
+
}))
|
|
1069
|
+
}
|
|
1070
|
+
/>
|
|
1071
|
+
</div>
|
|
1072
|
+
<div>
|
|
1073
|
+
<Label htmlFor={`recipient-message-${item.variantId}`}>Wiadomość</Label>
|
|
1074
|
+
<Input
|
|
1075
|
+
id={`recipient-message-${item.variantId}`}
|
|
1076
|
+
type="text"
|
|
1077
|
+
placeholder="Wszystkiego najlepszego!"
|
|
1078
|
+
value={giftCardRecipients[item.variantId]?.message || ""}
|
|
1079
|
+
onChange={(e) =>
|
|
1080
|
+
setGiftCardRecipients((prev) => ({
|
|
1081
|
+
...prev,
|
|
1082
|
+
[item.variantId]: {
|
|
1083
|
+
...prev[item.variantId],
|
|
1084
|
+
message: e.target.value,
|
|
1085
|
+
},
|
|
1086
|
+
}))
|
|
1087
|
+
}
|
|
1088
|
+
/>
|
|
1089
|
+
</div>
|
|
1090
|
+
</div>
|
|
1091
|
+
))}
|
|
1092
|
+
</div>
|
|
1093
|
+
)}
|
|
1094
|
+
|
|
1095
|
+
<Button onClick={handleContactSubmit} disabled={isLoading} className="w-full" size="lg">
|
|
1096
|
+
{isLoading ? (
|
|
1097
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
1098
|
+
) : (
|
|
1099
|
+
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1100
|
+
)}
|
|
1101
|
+
{allGiftCards ? "Dalej: Płatność" : "Dalej: Adres dostawy"}
|
|
1102
|
+
</Button>
|
|
1103
|
+
</CardContent>
|
|
1104
|
+
</Card>
|
|
1105
|
+
)}
|
|
1106
|
+
|
|
1107
|
+
{/* Step 2: Shipping Address */}
|
|
1108
|
+
{currentStep === "shipping" && (
|
|
1109
|
+
<>
|
|
1110
|
+
<Card>
|
|
1111
|
+
<CardHeader>
|
|
1112
|
+
<CardTitle className="flex items-center gap-2">
|
|
1113
|
+
<Package className="h-5 w-5" />
|
|
1114
|
+
Adres dostawy
|
|
1115
|
+
</CardTitle>
|
|
1116
|
+
<CardDescription>Podaj adres, na który mamy wysłać zamówienie</CardDescription>
|
|
1117
|
+
</CardHeader>
|
|
1118
|
+
<CardContent>
|
|
1119
|
+
<AddressFormFields
|
|
1120
|
+
prefix="shipping"
|
|
1121
|
+
values={formState.shippingAddress}
|
|
1122
|
+
errors={errors}
|
|
1123
|
+
onChange={(field, value) =>
|
|
1124
|
+
setFormState((prev) => ({
|
|
1125
|
+
...prev,
|
|
1126
|
+
shippingAddress: { ...prev.shippingAddress, [field]: value },
|
|
1127
|
+
}))
|
|
1128
|
+
}
|
|
1129
|
+
/>
|
|
1130
|
+
</CardContent>
|
|
1131
|
+
</Card>
|
|
1132
|
+
|
|
1133
|
+
<Card>
|
|
1134
|
+
<CardHeader>
|
|
1135
|
+
<CardTitle className="flex items-center gap-2">
|
|
1136
|
+
<CreditCard className="h-5 w-5" />
|
|
1137
|
+
Adres rozliczeniowy
|
|
1138
|
+
</CardTitle>
|
|
1139
|
+
</CardHeader>
|
|
1140
|
+
<CardContent className="space-y-4">
|
|
1141
|
+
<div className="flex items-center space-x-2">
|
|
1142
|
+
<Checkbox
|
|
1143
|
+
id="sameAsBilling"
|
|
1144
|
+
checked={formState.sameAsBilling}
|
|
1145
|
+
onCheckedChange={(checked) =>
|
|
1146
|
+
setFormState((prev) => ({ ...prev, sameAsBilling: checked === true }))
|
|
1147
|
+
}
|
|
1148
|
+
/>
|
|
1149
|
+
<Label htmlFor="sameAsBilling" className="cursor-pointer">
|
|
1150
|
+
Taki sam jak adres dostawy
|
|
1151
|
+
</Label>
|
|
1152
|
+
</div>
|
|
1153
|
+
|
|
1154
|
+
{!formState.sameAsBilling && (
|
|
1155
|
+
<AddressFormFields
|
|
1156
|
+
prefix="billing"
|
|
1157
|
+
values={formState.billingAddress}
|
|
1158
|
+
errors={errors}
|
|
1159
|
+
onChange={(field, value) =>
|
|
1160
|
+
setFormState((prev) => ({
|
|
1161
|
+
...prev,
|
|
1162
|
+
billingAddress: { ...prev.billingAddress, [field]: value },
|
|
1163
|
+
}))
|
|
1164
|
+
}
|
|
1165
|
+
/>
|
|
1166
|
+
)}
|
|
1167
|
+
</CardContent>
|
|
1168
|
+
</Card>
|
|
1169
|
+
|
|
1170
|
+
<div className="flex gap-4">
|
|
1171
|
+
<Button variant="outline" onClick={goBack} size="lg">
|
|
1172
|
+
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1173
|
+
Wstecz
|
|
1174
|
+
</Button>
|
|
1175
|
+
<Button onClick={handleShippingSubmit} disabled={isLoading} className="flex-1" size="lg">
|
|
1176
|
+
{isLoading ? (
|
|
1177
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
1178
|
+
) : (
|
|
1179
|
+
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1180
|
+
)}
|
|
1181
|
+
Dalej: Metoda dostawy
|
|
1182
|
+
</Button>
|
|
1183
|
+
</div>
|
|
1184
|
+
</>
|
|
1185
|
+
)}
|
|
1186
|
+
|
|
1187
|
+
{/* Step 3: Delivery Method */}
|
|
1188
|
+
{currentStep === "delivery" && (
|
|
1189
|
+
<Card>
|
|
1190
|
+
<CardHeader>
|
|
1191
|
+
<CardTitle className="flex items-center gap-2">
|
|
1192
|
+
<Truck className="h-5 w-5" />
|
|
1193
|
+
Metoda dostawy
|
|
1194
|
+
</CardTitle>
|
|
1195
|
+
<CardDescription>Wybierz sposób dostarczenia zamówienia</CardDescription>
|
|
1196
|
+
</CardHeader>
|
|
1197
|
+
<CardContent className="space-y-4">
|
|
1198
|
+
{!shippingRatesReady ? (
|
|
1199
|
+
<div className="flex items-center justify-center py-8">
|
|
1200
|
+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
1201
|
+
<span className="ml-2 text-muted-foreground">Ładowanie metod dostawy...</span>
|
|
1202
|
+
</div>
|
|
1203
|
+
) : availableShippingRates.length === 0 ? (
|
|
1204
|
+
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-center">
|
|
1205
|
+
<p className="text-destructive">
|
|
1206
|
+
Brak dostępnych metod dostawy dla podanego adresu.
|
|
1207
|
+
</p>
|
|
1208
|
+
<Button variant="outline" onClick={goBack} className="mt-4">
|
|
1209
|
+
Zmień adres dostawy
|
|
1210
|
+
</Button>
|
|
1211
|
+
</div>
|
|
1212
|
+
) : (
|
|
1213
|
+
<RadioGroup
|
|
1214
|
+
value={formState.selectedShippingRate || ""}
|
|
1215
|
+
onValueChange={(value) =>
|
|
1216
|
+
setFormState((prev) => ({ ...prev, selectedShippingRate: value }))
|
|
1217
|
+
}
|
|
1218
|
+
>
|
|
1219
|
+
{availableShippingRates.map((rate) => (
|
|
1220
|
+
<div
|
|
1221
|
+
key={rate.handle}
|
|
1222
|
+
className={`flex items-center space-x-3 rounded-lg border p-4 transition-colors cursor-pointer hover:bg-muted/50 ${
|
|
1223
|
+
formState.selectedShippingRate === rate.handle
|
|
1224
|
+
? "border-primary bg-primary/5"
|
|
1225
|
+
: ""
|
|
1226
|
+
}`}
|
|
1227
|
+
onClick={() =>
|
|
1228
|
+
setFormState((prev) => ({ ...prev, selectedShippingRate: rate.handle }))
|
|
1229
|
+
}
|
|
1230
|
+
>
|
|
1231
|
+
<RadioGroupItem value={rate.handle} id={rate.handle} />
|
|
1232
|
+
<Label
|
|
1233
|
+
htmlFor={rate.handle}
|
|
1234
|
+
className="flex flex-1 cursor-pointer items-center justify-between"
|
|
1235
|
+
>
|
|
1236
|
+
<div className="flex items-center gap-3">
|
|
1237
|
+
<Truck className="h-5 w-5 text-muted-foreground" />
|
|
1238
|
+
<span className="font-medium">{rate.title}</span>
|
|
1239
|
+
</div>
|
|
1240
|
+
<span className="font-semibold">{formatPrice(rate.price.amount)}</span>
|
|
1241
|
+
</Label>
|
|
1242
|
+
</div>
|
|
1243
|
+
))}
|
|
1244
|
+
</RadioGroup>
|
|
1245
|
+
)}
|
|
1246
|
+
{errors.shipping && (
|
|
1247
|
+
<p className="text-sm text-destructive">{errors.shipping}</p>
|
|
1248
|
+
)}
|
|
1249
|
+
<div className="flex gap-4 pt-4">
|
|
1250
|
+
<Button variant="outline" onClick={goBack} size="lg">
|
|
1251
|
+
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1252
|
+
Wstecz
|
|
1253
|
+
</Button>
|
|
1254
|
+
<Button
|
|
1255
|
+
onClick={handleDeliverySubmit}
|
|
1256
|
+
disabled={isLoading || !formState.selectedShippingRate}
|
|
1257
|
+
className="flex-1"
|
|
1258
|
+
size="lg"
|
|
1259
|
+
>
|
|
1260
|
+
{isLoading ? (
|
|
1261
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
1262
|
+
) : (
|
|
1263
|
+
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1264
|
+
)}
|
|
1265
|
+
Dalej: Płatność
|
|
1266
|
+
</Button>
|
|
1267
|
+
</div>
|
|
1268
|
+
</CardContent>
|
|
1269
|
+
</Card>
|
|
1270
|
+
)}
|
|
1271
|
+
|
|
1272
|
+
{/* Step 4: Payment */}
|
|
1273
|
+
{currentStep === "payment" && checkout && (
|
|
1274
|
+
<Card>
|
|
1275
|
+
<CardHeader>
|
|
1276
|
+
<CardTitle className="flex items-center gap-2">
|
|
1277
|
+
<CreditCard className="h-5 w-5" />
|
|
1278
|
+
Metoda płatności
|
|
1279
|
+
</CardTitle>
|
|
1280
|
+
<CardDescription>Wybierz sposób zapłaty za zamówienie</CardDescription>
|
|
1281
|
+
</CardHeader>
|
|
1282
|
+
<CardContent className="space-y-4">
|
|
1283
|
+
{/* PaymentStep component handles payment method selection */}
|
|
1284
|
+
<PaymentStep
|
|
1285
|
+
availablePaymentMethods={(checkout.availablePaymentMethods || []).map(
|
|
1286
|
+
(pm: any): PaymentMethod => ({
|
|
1287
|
+
id: pm.id,
|
|
1288
|
+
name: pm.name,
|
|
1289
|
+
provider: pm.provider,
|
|
1290
|
+
type: pm.type,
|
|
1291
|
+
icon: pm.icon,
|
|
1292
|
+
description: pm.description,
|
|
1293
|
+
isDefault: pm.isDefault,
|
|
1294
|
+
supportedCurrencies: pm.supportedCurrencies,
|
|
1295
|
+
position: pm.position || 0,
|
|
1296
|
+
})
|
|
1297
|
+
)}
|
|
1298
|
+
selectedPaymentMethodId={formState.selectedPaymentMethodId}
|
|
1299
|
+
onPaymentMethodSelect={handlePaymentMethodSelect}
|
|
1300
|
+
isLoading={isLoading}
|
|
1301
|
+
error={errors.payment}
|
|
1302
|
+
checkoutReady={!!checkout}
|
|
1303
|
+
/>
|
|
1304
|
+
{errors.payment && (
|
|
1305
|
+
<p className="text-sm text-destructive">{errors.payment}</p>
|
|
1306
|
+
)}
|
|
1307
|
+
<div className="flex gap-4 pt-4">
|
|
1308
|
+
<Button variant="outline" onClick={goBack} size="lg">
|
|
1309
|
+
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1310
|
+
Wstecz
|
|
1311
|
+
</Button>
|
|
1312
|
+
<Button
|
|
1313
|
+
onClick={handlePaymentSubmit}
|
|
1314
|
+
disabled={isLoading || !formState.selectedPaymentMethodId}
|
|
1315
|
+
className="flex-1"
|
|
1316
|
+
size="lg"
|
|
1317
|
+
>
|
|
1318
|
+
{isLoading ? (
|
|
1319
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
1320
|
+
) : (
|
|
1321
|
+
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1322
|
+
)}
|
|
1323
|
+
Dalej: Podsumowanie
|
|
1324
|
+
</Button>
|
|
1325
|
+
</div>
|
|
1326
|
+
</CardContent>
|
|
1327
|
+
</Card>
|
|
1328
|
+
)}
|
|
1329
|
+
|
|
1330
|
+
{/* Step 5: Review */}
|
|
1331
|
+
{currentStep === "review" && checkout && (
|
|
1332
|
+
<Card>
|
|
1333
|
+
<CardHeader>
|
|
1334
|
+
<CardTitle className="flex items-center gap-2">
|
|
1335
|
+
<ClipboardCheck className="h-5 w-5" />
|
|
1336
|
+
Podsumowanie zamówienia
|
|
1337
|
+
</CardTitle>
|
|
1338
|
+
<CardDescription>Sprawdź wszystkie dane przed finalizacją</CardDescription>
|
|
1339
|
+
</CardHeader>
|
|
1340
|
+
<CardContent className="space-y-6">
|
|
1341
|
+
{/* Contact */}
|
|
1342
|
+
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1343
|
+
<div>
|
|
1344
|
+
<h3 className="mb-1 font-medium">Kontakt</h3>
|
|
1345
|
+
<p className="text-sm text-muted-foreground">{checkout.email}</p>
|
|
1346
|
+
<p className="text-sm text-muted-foreground">{formState.phone}</p>
|
|
1347
|
+
</div>
|
|
1348
|
+
<Button
|
|
1349
|
+
variant="ghost"
|
|
1350
|
+
size="sm"
|
|
1351
|
+
onClick={() => setCurrentStep("contact")}
|
|
1352
|
+
>
|
|
1353
|
+
Zmień
|
|
1354
|
+
</Button>
|
|
1355
|
+
</div>
|
|
1356
|
+
|
|
1357
|
+
{/* Shipping Address (hidden for gift-card-only orders) */}
|
|
1358
|
+
{!allGiftCards && (
|
|
1359
|
+
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1360
|
+
<div>
|
|
1361
|
+
<h3 className="mb-1 font-medium">Adres dostawy</h3>
|
|
1362
|
+
{checkout.shippingAddress && (
|
|
1363
|
+
<p className="text-sm text-muted-foreground">
|
|
1364
|
+
{checkout.shippingAddress.firstName} {checkout.shippingAddress.lastName}
|
|
1365
|
+
<br />
|
|
1366
|
+
{checkout.shippingAddress.address1}
|
|
1367
|
+
{checkout.shippingAddress.address2 && (
|
|
1368
|
+
<>
|
|
1369
|
+
<br />
|
|
1370
|
+
{checkout.shippingAddress.address2}
|
|
1371
|
+
</>
|
|
1372
|
+
)}
|
|
1373
|
+
<br />
|
|
1374
|
+
{checkout.shippingAddress.zip} {checkout.shippingAddress.city}
|
|
1375
|
+
<br />
|
|
1376
|
+
{COUNTRIES.find((c) => c.code === checkout.shippingAddress?.country)?.name ||
|
|
1377
|
+
checkout.shippingAddress.country}
|
|
1378
|
+
</p>
|
|
1379
|
+
)}
|
|
1380
|
+
</div>
|
|
1381
|
+
<Button
|
|
1382
|
+
variant="ghost"
|
|
1383
|
+
size="sm"
|
|
1384
|
+
onClick={() => setCurrentStep("shipping")}
|
|
1385
|
+
>
|
|
1386
|
+
Zmień
|
|
1387
|
+
</Button>
|
|
1388
|
+
</div>
|
|
1389
|
+
)}
|
|
1390
|
+
|
|
1391
|
+
{/* Shipping Method (hidden for gift-card-only orders) */}
|
|
1392
|
+
{!allGiftCards && (
|
|
1393
|
+
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1394
|
+
<div>
|
|
1395
|
+
<h3 className="mb-1 font-medium">Metoda dostawy</h3>
|
|
1396
|
+
{checkout.shippingLine && (
|
|
1397
|
+
<p className="text-sm text-muted-foreground">
|
|
1398
|
+
{checkout.shippingLine.title} -{" "}
|
|
1399
|
+
{formatPrice(checkout.shippingLine.price.amount)}
|
|
1400
|
+
</p>
|
|
1401
|
+
)}
|
|
1402
|
+
</div>
|
|
1403
|
+
<Button
|
|
1404
|
+
variant="ghost"
|
|
1405
|
+
size="sm"
|
|
1406
|
+
onClick={() => setCurrentStep("delivery")}
|
|
1407
|
+
>
|
|
1408
|
+
Zmień
|
|
1409
|
+
</Button>
|
|
1410
|
+
</div>
|
|
1411
|
+
)}
|
|
1412
|
+
|
|
1413
|
+
{/* Payment Method */}
|
|
1414
|
+
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1415
|
+
<div>
|
|
1416
|
+
<h3 className="mb-1 font-medium">Metoda płatności</h3>
|
|
1417
|
+
{formState.selectedPaymentMethodId && (
|
|
1418
|
+
<p className="text-sm text-muted-foreground">
|
|
1419
|
+
{checkout.availablePaymentMethods?.find(
|
|
1420
|
+
(pm: any) => pm.id === formState.selectedPaymentMethodId
|
|
1421
|
+
)?.name || "Wybrana metoda płatności"}
|
|
1422
|
+
</p>
|
|
1423
|
+
)}
|
|
1424
|
+
</div>
|
|
1425
|
+
<Button
|
|
1426
|
+
variant="ghost"
|
|
1427
|
+
size="sm"
|
|
1428
|
+
onClick={() => setCurrentStep("payment")}
|
|
1429
|
+
>
|
|
1430
|
+
Zmień
|
|
1431
|
+
</Button>
|
|
1432
|
+
</div>
|
|
1433
|
+
|
|
1434
|
+
{/* Terms acceptance */}
|
|
1435
|
+
<div className="space-y-4 rounded-lg border p-4">
|
|
1436
|
+
<div className="flex items-start space-x-2">
|
|
1437
|
+
<Checkbox
|
|
1438
|
+
id="terms"
|
|
1439
|
+
checked={formState.acceptTerms}
|
|
1440
|
+
onCheckedChange={(checked) =>
|
|
1441
|
+
setFormState((prev) => ({ ...prev, acceptTerms: checked === true }))
|
|
1442
|
+
}
|
|
1443
|
+
className={errors.terms ? "border-destructive" : ""}
|
|
1444
|
+
/>
|
|
1445
|
+
<div className="grid gap-1.5 leading-none">
|
|
1446
|
+
<Label htmlFor="terms" className="cursor-pointer">
|
|
1447
|
+
Akceptuję{" "}
|
|
1448
|
+
<Link href="/terms" className="text-primary hover:underline">
|
|
1449
|
+
regulamin sklepu
|
|
1450
|
+
</Link>{" "}
|
|
1451
|
+
oraz{" "}
|
|
1452
|
+
<Link href="/privacy" className="text-primary hover:underline">
|
|
1453
|
+
politykę prywatności
|
|
1454
|
+
</Link>{" "}
|
|
1455
|
+
*
|
|
1456
|
+
</Label>
|
|
1457
|
+
{errors.terms && (
|
|
1458
|
+
<p className="text-sm text-destructive">{errors.terms}</p>
|
|
1459
|
+
)}
|
|
1460
|
+
</div>
|
|
1461
|
+
</div>
|
|
1462
|
+
</div>
|
|
1463
|
+
|
|
1464
|
+
<div className="flex gap-4">
|
|
1465
|
+
<Button variant="outline" onClick={goBack} size="lg">
|
|
1466
|
+
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1467
|
+
Wstecz
|
|
1468
|
+
</Button>
|
|
1469
|
+
<Button
|
|
1470
|
+
onClick={handleCompleteCheckout}
|
|
1471
|
+
disabled={isLoading || !formState.acceptTerms}
|
|
1472
|
+
className="flex-1"
|
|
1473
|
+
size="lg"
|
|
1474
|
+
>
|
|
1475
|
+
{isLoading ? (
|
|
1476
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
1477
|
+
) : (
|
|
1478
|
+
<CreditCard className="mr-2 h-4 w-4" />
|
|
1479
|
+
)}
|
|
1480
|
+
Zapłać i zamów
|
|
1481
|
+
</Button>
|
|
1482
|
+
</div>
|
|
1483
|
+
</CardContent>
|
|
1484
|
+
</Card>
|
|
1485
|
+
)}
|
|
1486
|
+
</div>
|
|
1487
|
+
|
|
1488
|
+
{/* Order Summary Sidebar */}
|
|
1489
|
+
<div className="lg:col-span-1">
|
|
1490
|
+
<Card className="sticky top-4">
|
|
1491
|
+
<CardHeader>
|
|
1492
|
+
<CardTitle className="flex items-center gap-2">
|
|
1493
|
+
<ShoppingBag className="h-5 w-5" />
|
|
1494
|
+
Twoje zamówienie
|
|
1495
|
+
</CardTitle>
|
|
1496
|
+
</CardHeader>
|
|
1497
|
+
<CardContent className="space-y-4">
|
|
1498
|
+
{/* Line Items */}
|
|
1499
|
+
<div className="space-y-3">
|
|
1500
|
+
{(checkout?.lineItems || items).map((item: any) => (
|
|
1501
|
+
<div
|
|
1502
|
+
key={item.id || item.lineId || item.variantId}
|
|
1503
|
+
className="flex gap-3 text-sm"
|
|
1504
|
+
>
|
|
1505
|
+
<div className="relative h-16 w-16 flex-shrink-0 overflow-hidden rounded-md border bg-muted">
|
|
1506
|
+
{item.image?.url ? (
|
|
1507
|
+
<img
|
|
1508
|
+
src={item.image.url}
|
|
1509
|
+
alt={item.title || item.productTitle}
|
|
1510
|
+
className="h-full w-full object-cover"
|
|
1511
|
+
/>
|
|
1512
|
+
) : (
|
|
1513
|
+
<div className="flex h-full w-full items-center justify-center">
|
|
1514
|
+
<Package className="h-6 w-6 text-muted-foreground" />
|
|
1515
|
+
</div>
|
|
1516
|
+
)}
|
|
1517
|
+
<span className="absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
1518
|
+
{item.quantity}
|
|
1519
|
+
</span>
|
|
1520
|
+
</div>
|
|
1521
|
+
<div className="flex-1">
|
|
1522
|
+
<p className="font-medium line-clamp-2">
|
|
1523
|
+
{item.title || item.productTitle}
|
|
1524
|
+
</p>
|
|
1525
|
+
{item.variantTitle && item.variantTitle !== "Default" && (
|
|
1526
|
+
<p className="text-xs text-muted-foreground">{item.variantTitle}</p>
|
|
1527
|
+
)}
|
|
1528
|
+
</div>
|
|
1529
|
+
<p className="font-medium">
|
|
1530
|
+
{formatPrice(
|
|
1531
|
+
item.totalPrice?.amount ||
|
|
1532
|
+
parseFloat(item.price.amount) * item.quantity
|
|
1533
|
+
)}
|
|
1534
|
+
</p>
|
|
1535
|
+
</div>
|
|
1536
|
+
))}
|
|
1537
|
+
</div>
|
|
1538
|
+
|
|
1539
|
+
{/* Discount Code */}
|
|
1540
|
+
{checkoutId && (
|
|
1541
|
+
<div className="border-t pt-4">
|
|
1542
|
+
{formState.appliedDiscountCode ? (
|
|
1543
|
+
<div className="flex items-center justify-between rounded-lg bg-green-50 p-3 dark:bg-green-950">
|
|
1544
|
+
<div className="flex items-center gap-2">
|
|
1545
|
+
<Tag className="h-4 w-4 text-green-600" />
|
|
1546
|
+
<span className="text-sm font-medium text-green-600">
|
|
1547
|
+
{formState.appliedDiscountCode}
|
|
1548
|
+
</span>
|
|
1549
|
+
</div>
|
|
1550
|
+
<Button
|
|
1551
|
+
variant="ghost"
|
|
1552
|
+
size="sm"
|
|
1553
|
+
onClick={handleRemoveDiscount}
|
|
1554
|
+
disabled={isLoading}
|
|
1555
|
+
className="h-8 w-8 p-0"
|
|
1556
|
+
>
|
|
1557
|
+
<X className="h-4 w-4" />
|
|
1558
|
+
</Button>
|
|
1559
|
+
</div>
|
|
1560
|
+
) : (
|
|
1561
|
+
<div className="flex gap-2">
|
|
1562
|
+
<Input
|
|
1563
|
+
placeholder="Kod rabatowy"
|
|
1564
|
+
value={formState.discountCode}
|
|
1565
|
+
onChange={(e) =>
|
|
1566
|
+
setFormState((prev) => ({ ...prev, discountCode: e.target.value }))
|
|
1567
|
+
}
|
|
1568
|
+
className="flex-1"
|
|
1569
|
+
/>
|
|
1570
|
+
<Button
|
|
1571
|
+
variant="outline"
|
|
1572
|
+
onClick={handleApplyDiscount}
|
|
1573
|
+
disabled={isLoading || !formState.discountCode.trim()}
|
|
1574
|
+
>
|
|
1575
|
+
{applyDiscount.isPending ? (
|
|
1576
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
1577
|
+
) : (
|
|
1578
|
+
"Zastosuj"
|
|
1579
|
+
)}
|
|
1580
|
+
</Button>
|
|
1581
|
+
</div>
|
|
1582
|
+
)}
|
|
1583
|
+
</div>
|
|
1584
|
+
)}
|
|
1585
|
+
|
|
1586
|
+
{/* Gift Card */}
|
|
1587
|
+
{checkoutId && (
|
|
1588
|
+
<div className="border-t pt-4 space-y-3">
|
|
1589
|
+
<p className="text-sm font-medium">Karta podarunkowa</p>
|
|
1590
|
+
|
|
1591
|
+
{/* Applied Gift Cards */}
|
|
1592
|
+
{checkout?.appliedGiftCards && checkout.appliedGiftCards.length > 0 && (
|
|
1593
|
+
<div className="space-y-2">
|
|
1594
|
+
{checkout.appliedGiftCards.map((giftCard: any) => (
|
|
1595
|
+
<div
|
|
1596
|
+
key={giftCard.lastCharacters}
|
|
1597
|
+
className="flex items-center justify-between rounded-lg bg-purple-50 p-3 dark:bg-purple-950"
|
|
1598
|
+
>
|
|
1599
|
+
<div className="flex items-center gap-2">
|
|
1600
|
+
<Gift className="h-4 w-4 text-purple-600" />
|
|
1601
|
+
<div className="text-sm">
|
|
1602
|
+
<span className="font-medium text-purple-600">
|
|
1603
|
+
{giftCard.maskedCode}
|
|
1604
|
+
</span>
|
|
1605
|
+
<span className="text-purple-600/70 ml-2">
|
|
1606
|
+
-{formatPrice(giftCard.appliedAmount?.amount || "0")}
|
|
1607
|
+
</span>
|
|
1608
|
+
</div>
|
|
1609
|
+
</div>
|
|
1610
|
+
<Button
|
|
1611
|
+
variant="ghost"
|
|
1612
|
+
size="sm"
|
|
1613
|
+
onClick={() => handleRemoveGiftCard(giftCard.maskedCode)}
|
|
1614
|
+
disabled={isLoading}
|
|
1615
|
+
className="h-8 w-8 p-0"
|
|
1616
|
+
>
|
|
1617
|
+
<X className="h-4 w-4" />
|
|
1618
|
+
</Button>
|
|
1619
|
+
</div>
|
|
1620
|
+
))}
|
|
1621
|
+
</div>
|
|
1622
|
+
)}
|
|
1623
|
+
|
|
1624
|
+
{/* Gift Card Input */}
|
|
1625
|
+
<div className="flex gap-2">
|
|
1626
|
+
<Input
|
|
1627
|
+
placeholder="Kod karty podarunkowej"
|
|
1628
|
+
value={formState.giftCardCode}
|
|
1629
|
+
onChange={(e) =>
|
|
1630
|
+
setFormState((prev) => ({ ...prev, giftCardCode: e.target.value.toUpperCase() }))
|
|
1631
|
+
}
|
|
1632
|
+
onKeyDown={(e) => {
|
|
1633
|
+
if (e.key === "Enter") {
|
|
1634
|
+
e.preventDefault();
|
|
1635
|
+
handleApplyGiftCard();
|
|
1636
|
+
}
|
|
1637
|
+
}}
|
|
1638
|
+
className="flex-1 uppercase"
|
|
1639
|
+
/>
|
|
1640
|
+
<Button
|
|
1641
|
+
variant="outline"
|
|
1642
|
+
onClick={handleApplyGiftCard}
|
|
1643
|
+
disabled={isLoading || !formState.giftCardCode.trim()}
|
|
1644
|
+
>
|
|
1645
|
+
{applyGiftCard.isPending ? (
|
|
1646
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
1647
|
+
) : (
|
|
1648
|
+
"Zastosuj"
|
|
1649
|
+
)}
|
|
1650
|
+
</Button>
|
|
1651
|
+
</div>
|
|
1652
|
+
</div>
|
|
1653
|
+
)}
|
|
1654
|
+
|
|
1655
|
+
{/* Totals */}
|
|
1656
|
+
<div className="border-t pt-4 space-y-2">
|
|
1657
|
+
{/* Subtotal */}
|
|
1658
|
+
<div className="flex justify-between text-sm">
|
|
1659
|
+
<span className="text-muted-foreground">Produkty</span>
|
|
1660
|
+
<span>
|
|
1661
|
+
{formatPrice(
|
|
1662
|
+
checkout?.subtotalPrice?.amount ||
|
|
1663
|
+
items.reduce(
|
|
1664
|
+
(sum, item) => sum + parseFloat(item.price.amount) * item.quantity,
|
|
1665
|
+
0
|
|
1666
|
+
)
|
|
1667
|
+
)}
|
|
1668
|
+
</span>
|
|
1669
|
+
</div>
|
|
1670
|
+
|
|
1671
|
+
{/* Shipping */}
|
|
1672
|
+
{checkout?.shippingLine ? (
|
|
1673
|
+
<div className="flex justify-between text-sm">
|
|
1674
|
+
<span className="text-muted-foreground">Dostawa</span>
|
|
1675
|
+
<span>{formatPrice(checkout.totalShippingPrice?.amount || "0")}</span>
|
|
1676
|
+
</div>
|
|
1677
|
+
) : (
|
|
1678
|
+
<div className="flex justify-between text-sm">
|
|
1679
|
+
<span className="text-muted-foreground">Dostawa</span>
|
|
1680
|
+
<span className="text-muted-foreground">Obliczona w następnym kroku</span>
|
|
1681
|
+
</div>
|
|
1682
|
+
)}
|
|
1683
|
+
|
|
1684
|
+
{/* Discount */}
|
|
1685
|
+
{checkout?.totalDiscounts && parseFloat(checkout.totalDiscounts.amount) > 0 && (
|
|
1686
|
+
<div className="flex justify-between text-sm text-green-600">
|
|
1687
|
+
<span>Rabat</span>
|
|
1688
|
+
<span>-{formatPrice(checkout.totalDiscounts.amount)}</span>
|
|
1689
|
+
</div>
|
|
1690
|
+
)}
|
|
1691
|
+
|
|
1692
|
+
{/* Tax */}
|
|
1693
|
+
{checkout?.totalTax && parseFloat(checkout.totalTax.amount) > 0 && (
|
|
1694
|
+
<div className="flex justify-between text-sm">
|
|
1695
|
+
<span className="text-muted-foreground">Podatek VAT</span>
|
|
1696
|
+
<span>{formatPrice(checkout.totalTax.amount)}</span>
|
|
1697
|
+
</div>
|
|
1698
|
+
)}
|
|
1699
|
+
|
|
1700
|
+
{/* Gift Card Deduction */}
|
|
1701
|
+
{checkout?.totalGiftCardAmount && parseFloat(checkout.totalGiftCardAmount.amount) > 0 && (
|
|
1702
|
+
<div className="flex justify-between text-sm text-purple-600">
|
|
1703
|
+
<span>Karta podarunkowa</span>
|
|
1704
|
+
<span>-{formatPrice(checkout.totalGiftCardAmount.amount)}</span>
|
|
1705
|
+
</div>
|
|
1706
|
+
)}
|
|
1707
|
+
|
|
1708
|
+
{/* Total */}
|
|
1709
|
+
<div className="flex justify-between border-t pt-2 text-lg font-semibold">
|
|
1710
|
+
<span>Razem</span>
|
|
1711
|
+
<span>
|
|
1712
|
+
{formatPrice(
|
|
1713
|
+
checkout?.totalPrice?.amount ||
|
|
1714
|
+
items.reduce(
|
|
1715
|
+
(sum, item) => sum + parseFloat(item.price.amount) * item.quantity,
|
|
1716
|
+
0
|
|
1717
|
+
)
|
|
1718
|
+
)}
|
|
1719
|
+
</span>
|
|
1720
|
+
</div>
|
|
1721
|
+
|
|
1722
|
+
{/* Payment Due (if different from total due to gift cards) */}
|
|
1723
|
+
{checkout?.paymentDue &&
|
|
1724
|
+
checkout?.totalPrice &&
|
|
1725
|
+
parseFloat(checkout.paymentDue.amount) !== parseFloat(checkout.totalPrice.amount) && (
|
|
1726
|
+
<div className="flex justify-between border-t pt-2 text-lg font-bold text-primary">
|
|
1727
|
+
<span>Do zapłaty</span>
|
|
1728
|
+
<span>{formatPrice(checkout.paymentDue.amount)}</span>
|
|
1729
|
+
</div>
|
|
1730
|
+
)}
|
|
1731
|
+
</div>
|
|
1732
|
+
|
|
1733
|
+
{/* Security badges */}
|
|
1734
|
+
<div className="border-t pt-4">
|
|
1735
|
+
<div className="flex items-center justify-center gap-4 text-xs text-muted-foreground">
|
|
1736
|
+
<div className="flex items-center gap-1">
|
|
1737
|
+
<Check className="h-3 w-3" />
|
|
1738
|
+
Bezpieczna płatność
|
|
1739
|
+
</div>
|
|
1740
|
+
<div className="flex items-center gap-1">
|
|
1741
|
+
<Check className="h-3 w-3" />
|
|
1742
|
+
Szyfrowane dane
|
|
1743
|
+
</div>
|
|
1744
|
+
</div>
|
|
1745
|
+
</div>
|
|
1746
|
+
</CardContent>
|
|
1747
|
+
</Card>
|
|
1748
|
+
</div>
|
|
1749
|
+
</div>
|
|
1750
|
+
</div>
|
|
1751
|
+
);
|
|
1752
|
+
}
|