@salifbiaye/create-boutique 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 +1 -0
- package/cli/dist/commands.js +382 -0
- package/cli/dist/create.js +194 -0
- package/cli/dist/docker.js +19 -0
- package/cli/dist/generate.js +322 -0
- package/cli/dist/guide.js +72 -0
- package/cli/dist/index.js +156 -0
- package/cli/dist/paths.js +29 -0
- package/cli/dist/ports.js +54 -0
- package/cli/dist/project.js +119 -0
- package/cli/dist/themes.js +24 -0
- package/docs/fiche-client.en.html +137 -0
- package/docs/fiche-client.html +137 -0
- package/docs/guide-complet.en.html +259 -0
- package/docs/guide-complet.fr.html +259 -0
- package/package.json +51 -0
- package/templates/backend/.dockerignore +9 -0
- package/templates/backend/Dockerfile +30 -0
- package/templates/backend/README.md +78 -0
- package/templates/backend/TEMPLATE_VERSION +1 -0
- package/templates/backend/docker/entrypoint.sh +28 -0
- package/templates/backend/instrumentation.ts +24 -0
- package/templates/backend/medusa-config.ts +114 -0
- package/templates/backend/package-lock.json +16222 -0
- package/templates/backend/package.json +39 -0
- package/templates/backend/src/admin/README.md +33 -0
- package/templates/backend/src/admin/components/image-field.tsx +61 -0
- package/templates/backend/src/admin/i18n/README.md +58 -0
- package/templates/backend/src/admin/i18n/index.ts +8 -0
- package/templates/backend/src/admin/i18n/json/en.json +66 -0
- package/templates/backend/src/admin/i18n/json/fr.json +66 -0
- package/templates/backend/src/admin/lib/client.ts +9 -0
- package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +286 -0
- package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +113 -0
- package/templates/backend/src/admin/tsconfig.json +24 -0
- package/templates/backend/src/admin/vite-env.d.ts +1 -0
- package/templates/backend/src/admin/widgets/category-image.tsx +56 -0
- package/templates/backend/src/api/README.md +135 -0
- package/templates/backend/src/api/admin/boutique-campaigns/[salesChannelId]/route.ts +30 -0
- package/templates/backend/src/api/admin/boutique-campaigns/route.ts +24 -0
- package/templates/backend/src/api/middlewares.ts +28 -0
- package/templates/backend/src/api/rate-limit.ts +30 -0
- package/templates/backend/src/api/route.ts +6 -0
- package/templates/backend/src/api/store/campaign/route.ts +25 -0
- package/templates/backend/src/api/store/order-lookup/route.ts +74 -0
- package/templates/backend/src/emails/en.json +64 -0
- package/templates/backend/src/emails/fr.json +64 -0
- package/templates/backend/src/jobs/README.md +38 -0
- package/templates/backend/src/lib/emails.ts +94 -0
- package/templates/backend/src/lib/notify.ts +148 -0
- package/templates/backend/src/lib/project.ts +146 -0
- package/templates/backend/src/links/README.md +26 -0
- package/templates/backend/src/links/campaign-sales-channel.ts +9 -0
- package/templates/backend/src/modules/README.md +117 -0
- package/templates/backend/src/modules/campaign/index.ts +9 -0
- package/templates/backend/src/modules/campaign/migrations/.snapshot-boutique-campaign.json +272 -0
- package/templates/backend/src/modules/campaign/migrations/Migration20260928141440.ts +16 -0
- package/templates/backend/src/modules/campaign/migrations/Migration20260928145243.ts +13 -0
- package/templates/backend/src/modules/campaign/models/campaign.ts +28 -0
- package/templates/backend/src/modules/campaign/service.ts +6 -0
- package/templates/backend/src/modules/campaign/validators.ts +94 -0
- package/templates/backend/src/modules/resend/index.ts +6 -0
- package/templates/backend/src/modules/resend/service.ts +63 -0
- package/templates/backend/src/scripts/README.md +63 -0
- package/templates/backend/src/scripts/campaign-export.ts +15 -0
- package/templates/backend/src/scripts/campaign-import.ts +38 -0
- package/templates/backend/src/scripts/price-report.ts +34 -0
- package/templates/backend/src/scripts/seed.ts +418 -0
- package/templates/backend/src/scripts/storage.ts +63 -0
- package/templates/backend/src/subscribers/README.md +61 -0
- package/templates/backend/src/subscribers/account-emails.ts +27 -0
- package/templates/backend/src/subscribers/fulfillment-emails.ts +42 -0
- package/templates/backend/src/subscribers/order-emails.ts +12 -0
- package/templates/backend/src/subscribers/return-refund-emails.ts +50 -0
- package/templates/backend/src/workflows/README.md +81 -0
- package/templates/backend/src/workflows/steps/upsert-campaign.ts +53 -0
- package/templates/backend/src/workflows/upsert-campaign.ts +30 -0
- package/templates/backend/tsconfig.json +36 -0
- package/templates/storefront/.dockerignore +7 -0
- package/templates/storefront/.github/ISSUE_TEMPLATE/bug-report.yml +71 -0
- package/templates/storefront/.prettierrc +8 -0
- package/templates/storefront/Dockerfile +37 -0
- package/templates/storefront/LICENSE +21 -0
- package/templates/storefront/README.md +127 -0
- package/templates/storefront/locales/en.json +269 -0
- package/templates/storefront/locales/fr.json +269 -0
- package/templates/storefront/next-env.d.ts +7 -0
- package/templates/storefront/next-sitemap.js +19 -0
- package/templates/storefront/next.config.js +29 -0
- package/templates/storefront/package-lock.json +9031 -0
- package/templates/storefront/package.json +49 -0
- package/templates/storefront/postcss.config.js +6 -0
- package/templates/storefront/public/favicon.ico +0 -0
- package/templates/storefront/scripts/sync-design.mjs +30 -0
- package/templates/storefront/src/app/(checkout)/checkout/page.tsx +31 -0
- package/templates/storefront/src/app/(checkout)/layout.tsx +29 -0
- package/templates/storefront/src/app/(checkout)/not-found.tsx +20 -0
- package/templates/storefront/src/app/(main)/account/@dashboard/addresses/page.tsx +34 -0
- package/templates/storefront/src/app/(main)/account/@dashboard/loading.tsx +9 -0
- package/templates/storefront/src/app/(main)/account/@dashboard/orders/details/[id]/page.tsx +33 -0
- package/templates/storefront/src/app/(main)/account/@dashboard/orders/page.tsx +37 -0
- package/templates/storefront/src/app/(main)/account/@dashboard/page.tsx +22 -0
- package/templates/storefront/src/app/(main)/account/@dashboard/profile/page.tsx +53 -0
- package/templates/storefront/src/app/(main)/account/@login/page.tsx +12 -0
- package/templates/storefront/src/app/(main)/account/layout.tsx +20 -0
- package/templates/storefront/src/app/(main)/account/loading.tsx +9 -0
- package/templates/storefront/src/app/(main)/cart/loading.tsx +5 -0
- package/templates/storefront/src/app/(main)/cart/not-found.tsx +21 -0
- package/templates/storefront/src/app/(main)/cart/page.tsx +21 -0
- package/templates/storefront/src/app/(main)/categories/[...category]/page.tsx +55 -0
- package/templates/storefront/src/app/(main)/collections/[handle]/page.tsx +55 -0
- package/templates/storefront/src/app/(main)/layout.tsx +43 -0
- package/templates/storefront/src/app/(main)/legal/[page]/page.tsx +42 -0
- package/templates/storefront/src/app/(main)/not-found.tsx +21 -0
- package/templates/storefront/src/app/(main)/order/[id]/confirmed/loading.tsx +5 -0
- package/templates/storefront/src/app/(main)/order/[id]/confirmed/page.tsx +23 -0
- package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/accept/page.tsx +42 -0
- package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/decline/page.tsx +42 -0
- package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/page.tsx +35 -0
- package/templates/storefront/src/app/(main)/order/track/page.tsx +32 -0
- package/templates/storefront/src/app/(main)/page.tsx +68 -0
- package/templates/storefront/src/app/(main)/products/[handle]/page.tsx +86 -0
- package/templates/storefront/src/app/(main)/store/page.tsx +28 -0
- package/templates/storefront/src/app/api/health/route.ts +13 -0
- package/templates/storefront/src/app/layout.tsx +42 -0
- package/templates/storefront/src/app/not-found.tsx +21 -0
- package/templates/storefront/src/app/opengraph-image.jpg +0 -0
- package/templates/storefront/src/app/twitter-image.jpg +0 -0
- package/templates/storefront/src/instrumentation-node.ts +18 -0
- package/templates/storefront/src/instrumentation.ts +9 -0
- package/templates/storefront/src/lib/boutique/campaign.ts +116 -0
- package/templates/storefront/src/lib/boutique/config.ts +152 -0
- package/templates/storefront/src/lib/boutique/fonts.ts +29 -0
- package/templates/storefront/src/lib/boutique/i18n-client.tsx +34 -0
- package/templates/storefront/src/lib/boutique/i18n.ts +43 -0
- package/templates/storefront/src/lib/boutique/schema.ts +131 -0
- package/templates/storefront/src/lib/config.ts +39 -0
- package/templates/storefront/src/lib/constants.tsx +75 -0
- package/templates/storefront/src/lib/context/modal-context.tsx +34 -0
- package/templates/storefront/src/lib/data/cart.ts +434 -0
- package/templates/storefront/src/lib/data/categories.ts +49 -0
- package/templates/storefront/src/lib/data/collections.ts +59 -0
- package/templates/storefront/src/lib/data/cookies.ts +103 -0
- package/templates/storefront/src/lib/data/customer.ts +261 -0
- package/templates/storefront/src/lib/data/fulfillment.ts +68 -0
- package/templates/storefront/src/lib/data/onboarding.ts +9 -0
- package/templates/storefront/src/lib/data/order-lookup.ts +34 -0
- package/templates/storefront/src/lib/data/orders.ts +112 -0
- package/templates/storefront/src/lib/data/payment.ts +35 -0
- package/templates/storefront/src/lib/data/products.ts +127 -0
- package/templates/storefront/src/lib/data/regions.ts +70 -0
- package/templates/storefront/src/lib/data/variants.ts +38 -0
- package/templates/storefront/src/lib/hooks/use-in-view.tsx +29 -0
- package/templates/storefront/src/lib/hooks/use-toggle-state.tsx +46 -0
- package/templates/storefront/src/lib/util/compare-addresses.ts +28 -0
- package/templates/storefront/src/lib/util/get-percentage-diff.ts +6 -0
- package/templates/storefront/src/lib/util/get-product-price.ts +79 -0
- package/templates/storefront/src/lib/util/isEmpty.ts +11 -0
- package/templates/storefront/src/lib/util/medusa-error.ts +22 -0
- package/templates/storefront/src/lib/util/money.ts +35 -0
- package/templates/storefront/src/lib/util/product.ts +5 -0
- package/templates/storefront/src/lib/util/repeat.ts +5 -0
- package/templates/storefront/src/lib/util/sort-products.ts +50 -0
- package/templates/storefront/src/modules/account/components/account-info/index.tsx +140 -0
- package/templates/storefront/src/modules/account/components/account-nav/index.tsx +198 -0
- package/templates/storefront/src/modules/account/components/address-book/index.tsx +28 -0
- package/templates/storefront/src/modules/account/components/address-card/add-address.tsx +169 -0
- package/templates/storefront/src/modules/account/components/address-card/edit-address-modal.tsx +241 -0
- package/templates/storefront/src/modules/account/components/login/index.tsx +65 -0
- package/templates/storefront/src/modules/account/components/order-card/index.tsx +88 -0
- package/templates/storefront/src/modules/account/components/order-overview/index.tsx +47 -0
- package/templates/storefront/src/modules/account/components/overview/index.tsx +169 -0
- package/templates/storefront/src/modules/account/components/profile-billing-address/index.tsx +193 -0
- package/templates/storefront/src/modules/account/components/profile-email/index.tsx +75 -0
- package/templates/storefront/src/modules/account/components/profile-name/index.tsx +79 -0
- package/templates/storefront/src/modules/account/components/profile-password/index.tsx +72 -0
- package/templates/storefront/src/modules/account/components/profile-phone/index.tsx +74 -0
- package/templates/storefront/src/modules/account/components/register/index.tsx +107 -0
- package/templates/storefront/src/modules/account/components/transfer-request-form/index.tsx +83 -0
- package/templates/storefront/src/modules/account/templates/account-layout.tsx +43 -0
- package/templates/storefront/src/modules/account/templates/login-template.tsx +27 -0
- package/templates/storefront/src/modules/cart/components/cart-item-select/index.tsx +75 -0
- package/templates/storefront/src/modules/cart/components/empty-cart-message/index.tsx +25 -0
- package/templates/storefront/src/modules/cart/components/item/index.tsx +144 -0
- package/templates/storefront/src/modules/cart/components/sign-in-prompt/index.tsx +27 -0
- package/templates/storefront/src/modules/cart/templates/index.tsx +51 -0
- package/templates/storefront/src/modules/cart/templates/items.tsx +58 -0
- package/templates/storefront/src/modules/cart/templates/preview.tsx +51 -0
- package/templates/storefront/src/modules/cart/templates/summary.tsx +50 -0
- package/templates/storefront/src/modules/categories/templates/index.tsx +94 -0
- package/templates/storefront/src/modules/checkout/components/address-select/index.tsx +117 -0
- package/templates/storefront/src/modules/checkout/components/addresses/index.tsx +186 -0
- package/templates/storefront/src/modules/checkout/components/billing_address/index.tsx +114 -0
- package/templates/storefront/src/modules/checkout/components/country-select/index.tsx +50 -0
- package/templates/storefront/src/modules/checkout/components/discount-code/index.tsx +181 -0
- package/templates/storefront/src/modules/checkout/components/error-message/index.tsx +13 -0
- package/templates/storefront/src/modules/checkout/components/payment/index.tsx +261 -0
- package/templates/storefront/src/modules/checkout/components/payment-button/index.tsx +195 -0
- package/templates/storefront/src/modules/checkout/components/payment-container/index.tsx +130 -0
- package/templates/storefront/src/modules/checkout/components/payment-test/index.tsx +12 -0
- package/templates/storefront/src/modules/checkout/components/payment-wrapper/index.tsx +42 -0
- package/templates/storefront/src/modules/checkout/components/payment-wrapper/stripe-wrapper.tsx +54 -0
- package/templates/storefront/src/modules/checkout/components/review/index.tsx +54 -0
- package/templates/storefront/src/modules/checkout/components/shipping/index.tsx +417 -0
- package/templates/storefront/src/modules/checkout/components/shipping-address/index.tsx +219 -0
- package/templates/storefront/src/modules/checkout/components/submit-button/index.tsx +32 -0
- package/templates/storefront/src/modules/checkout/templates/checkout-form/index.tsx +38 -0
- package/templates/storefront/src/modules/checkout/templates/checkout-summary/index.tsx +31 -0
- package/templates/storefront/src/modules/collections/templates/index.tsx +44 -0
- package/templates/storefront/src/modules/common/components/cart-totals/index.tsx +84 -0
- package/templates/storefront/src/modules/common/components/checkbox/index.tsx +44 -0
- package/templates/storefront/src/modules/common/components/delete-button/index.tsx +42 -0
- package/templates/storefront/src/modules/common/components/divider/index.tsx +9 -0
- package/templates/storefront/src/modules/common/components/filter-radio-group/index.tsx +61 -0
- package/templates/storefront/src/modules/common/components/input/index.tsx +79 -0
- package/templates/storefront/src/modules/common/components/interactive-link/index.tsx +33 -0
- package/templates/storefront/src/modules/common/components/line-item-options/index.tsx +26 -0
- package/templates/storefront/src/modules/common/components/line-item-price/index.tsx +63 -0
- package/templates/storefront/src/modules/common/components/line-item-unit-price/index.tsx +62 -0
- package/templates/storefront/src/modules/common/components/localized-client-link/index.tsx +29 -0
- package/templates/storefront/src/modules/common/components/modal/index.tsx +118 -0
- package/templates/storefront/src/modules/common/components/native-select/index.tsx +76 -0
- package/templates/storefront/src/modules/common/components/radio/index.tsx +27 -0
- package/templates/storefront/src/modules/common/icons/back.tsx +37 -0
- package/templates/storefront/src/modules/common/icons/bancontact.tsx +26 -0
- package/templates/storefront/src/modules/common/icons/chevron-down.tsx +30 -0
- package/templates/storefront/src/modules/common/icons/eye-off.tsx +37 -0
- package/templates/storefront/src/modules/common/icons/eye.tsx +37 -0
- package/templates/storefront/src/modules/common/icons/fast-delivery.tsx +65 -0
- package/templates/storefront/src/modules/common/icons/ideal.tsx +26 -0
- package/templates/storefront/src/modules/common/icons/map-pin.tsx +37 -0
- package/templates/storefront/src/modules/common/icons/package.tsx +44 -0
- package/templates/storefront/src/modules/common/icons/paypal.tsx +30 -0
- package/templates/storefront/src/modules/common/icons/placeholder-image.tsx +44 -0
- package/templates/storefront/src/modules/common/icons/refresh.tsx +51 -0
- package/templates/storefront/src/modules/common/icons/spinner.tsx +37 -0
- package/templates/storefront/src/modules/common/icons/trash.tsx +51 -0
- package/templates/storefront/src/modules/common/icons/user.tsx +37 -0
- package/templates/storefront/src/modules/common/icons/x.tsx +37 -0
- package/templates/storefront/src/modules/layout/components/cart-mismatch-banner/index.tsx +59 -0
- package/templates/storefront/src/modules/order/components/help/index.tsx +26 -0
- package/templates/storefront/src/modules/order/components/item/index.tsx +57 -0
- package/templates/storefront/src/modules/order/components/items/index.tsx +44 -0
- package/templates/storefront/src/modules/order/components/onboarding-cta/index.tsx +28 -0
- package/templates/storefront/src/modules/order/components/order-details/index.tsx +64 -0
- package/templates/storefront/src/modules/order/components/order-summary/index.tsx +61 -0
- package/templates/storefront/src/modules/order/components/payment-details/index.tsx +64 -0
- package/templates/storefront/src/modules/order/components/shipping-details/index.tsx +74 -0
- package/templates/storefront/src/modules/order/components/transfer-actions/index.tsx +83 -0
- package/templates/storefront/src/modules/order/components/transfer-image/index.tsx +275 -0
- package/templates/storefront/src/modules/order/templates/order-completed-template.tsx +53 -0
- package/templates/storefront/src/modules/order/templates/order-details-template.tsx +46 -0
- package/templates/storefront/src/modules/products/components/thumbnail/index.tsx +70 -0
- package/templates/storefront/src/modules/products/templates/index.tsx +118 -0
- package/templates/storefront/src/modules/shipping/components/free-shipping-price-nudge/index.tsx +285 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-button/index.tsx +5 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-card-details/index.tsx +10 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-cart-item/index.tsx +35 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-cart-totals/index.tsx +30 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-code-form/index.tsx +13 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-line-item/index.tsx +35 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-order-confirmed-header/index.tsx +14 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-order-information/index.tsx +36 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-order-items/index.tsx +43 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-order-summary/index.tsx +15 -0
- package/templates/storefront/src/modules/skeletons/components/skeleton-product-preview/index.tsx +15 -0
- package/templates/storefront/src/modules/skeletons/templates/skeleton-cart-page/index.tsx +65 -0
- package/templates/storefront/src/modules/skeletons/templates/skeleton-order-confirmed/index.tsx +21 -0
- package/templates/storefront/src/modules/skeletons/templates/skeleton-product-grid/index.tsx +23 -0
- package/templates/storefront/src/modules/store/components/pagination/index.tsx +114 -0
- package/templates/storefront/src/modules/store/components/refinement-list/index.tsx +41 -0
- package/templates/storefront/src/modules/store/components/refinement-list/sort-products/index.tsx +48 -0
- package/templates/storefront/src/modules/store/templates/index.tsx +42 -0
- package/templates/storefront/src/modules/store/templates/paginated-products.tsx +80 -0
- package/templates/storefront/src/modules/ub/button.tsx +41 -0
- package/templates/storefront/src/modules/ub/campaign-blocks.tsx +197 -0
- package/templates/storefront/src/modules/ub/campaign-ended.tsx +28 -0
- package/templates/storefront/src/modules/ub/category-tiles.tsx +41 -0
- package/templates/storefront/src/modules/ub/countdown.tsx +46 -0
- package/templates/storefront/src/modules/ub/footer.tsx +56 -0
- package/templates/storefront/src/modules/ub/icons.tsx +42 -0
- package/templates/storefront/src/modules/ub/nav.tsx +90 -0
- package/templates/storefront/src/modules/ub/pdp/buy-box.tsx +140 -0
- package/templates/storefront/src/modules/ub/pdp/gallery.tsx +40 -0
- package/templates/storefront/src/modules/ub/product-card.tsx +66 -0
- package/templates/storefront/src/modules/ub/top-bar.tsx +20 -0
- package/templates/storefront/src/modules/ub/track-form.tsx +92 -0
- package/templates/storefront/src/proxy.ts +89 -0
- package/templates/storefront/src/styles/globals.css +98 -0
- package/templates/storefront/src/styles/ub/bundle.css +372 -0
- package/templates/storefront/src/styles/ub/fonts.css +17 -0
- package/templates/storefront/src/styles/ub/medusa-ui.css +90 -0
- package/templates/storefront/src/styles/ub/overrides.css +12 -0
- package/templates/storefront/src/styles/ub/tokens.css +9 -0
- package/templates/storefront/src/types/global.ts +24 -0
- package/templates/storefront/src/types/icon.ts +4 -0
- package/templates/storefront/tailwind.config.js +164 -0
- package/templates/storefront/themes/black-friday/campaign.en.json +31 -0
- package/templates/storefront/themes/black-friday/campaign.fr.json +31 -0
- package/templates/storefront/themes/black-friday/theme.json +9 -0
- package/templates/storefront/themes/cyber-monday/campaign.en.json +31 -0
- package/templates/storefront/themes/cyber-monday/campaign.fr.json +31 -0
- package/templates/storefront/themes/cyber-monday/theme.json +9 -0
- package/templates/storefront/themes/halloween/campaign.en.json +31 -0
- package/templates/storefront/themes/halloween/campaign.fr.json +31 -0
- package/templates/storefront/themes/halloween/theme.json +9 -0
- package/templates/storefront/themes/noel/campaign.en.json +31 -0
- package/templates/storefront/themes/noel/campaign.fr.json +31 -0
- package/templates/storefront/themes/noel/theme.json +9 -0
- package/templates/storefront/themes/nouvel-an/campaign.en.json +31 -0
- package/templates/storefront/themes/nouvel-an/campaign.fr.json +31 -0
- package/templates/storefront/themes/nouvel-an/theme.json +9 -0
- package/templates/storefront/themes/thanksgiving/campaign.en.json +31 -0
- package/templates/storefront/themes/thanksgiving/campaign.fr.json +31 -0
- package/templates/storefront/themes/thanksgiving/theme.json +9 -0
- package/templates/storefront/tsconfig.json +53 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import "server-only"
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { z } from "zod"
|
|
6
|
+
import { projectSchema, themeSchema, type Theme } from "./schema"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration de l'instance. La boutique est fixée par la variable
|
|
10
|
+
* d'environnement BOUTIQUE et par rien d'autre : aucun paramètre d'URL,
|
|
11
|
+
* cookie ou en-tête ne peut en changer.
|
|
12
|
+
*/
|
|
13
|
+
export type Boutique = {
|
|
14
|
+
project: string
|
|
15
|
+
slug: string
|
|
16
|
+
theme: Theme
|
|
17
|
+
storeName: string
|
|
18
|
+
currency: string
|
|
19
|
+
country: string
|
|
20
|
+
/** Pays livrés : "all" ou liste de codes pays (inclut toujours `country`) */
|
|
21
|
+
shipsTo: "all" | string[]
|
|
22
|
+
language: string
|
|
23
|
+
/** ex. "en-US" : pour Intl et <html lang> */
|
|
24
|
+
locale: string
|
|
25
|
+
port: number
|
|
26
|
+
publishableKey: string
|
|
27
|
+
merchantEmail?: string
|
|
28
|
+
/** URL publique des autres boutiques du projet (lien « campagne suivante ») */
|
|
29
|
+
siblings: Record<string, string>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const CONFIG_DIR = process.env.CONFIG_DIR || "/app/config"
|
|
33
|
+
export const KEYS_DIR = process.env.KEYS_DIR || "/app/keys"
|
|
34
|
+
export const THEMES_DIR = path.join(process.cwd(), "themes")
|
|
35
|
+
|
|
36
|
+
export class BoutiqueConfigError extends Error {
|
|
37
|
+
constructor(message: string) {
|
|
38
|
+
super(`[boutique] ${message}`)
|
|
39
|
+
this.name = "BoutiqueConfigError"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readJson(file: string): unknown {
|
|
44
|
+
let raw: string
|
|
45
|
+
try {
|
|
46
|
+
raw = fs.readFileSync(file, "utf8")
|
|
47
|
+
} catch {
|
|
48
|
+
throw new BoutiqueConfigError(`fichier introuvable : ${file}`)
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(raw)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
throw new BoutiqueConfigError(`JSON invalide dans ${file} : ${(e as Error).message}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseOrThrow<T>(schema: z.ZodType<T>, data: unknown, file: string): T {
|
|
58
|
+
const res = schema.safeParse(data)
|
|
59
|
+
if (!res.success) {
|
|
60
|
+
throw new BoutiqueConfigError(`${file} invalide :\n${z.prettifyError(res.error)}`)
|
|
61
|
+
}
|
|
62
|
+
return res.data
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function listThemes(): string[] {
|
|
66
|
+
if (!fs.existsSync(THEMES_DIR)) return []
|
|
67
|
+
return fs
|
|
68
|
+
.readdirSync(THEMES_DIR, { withFileTypes: true })
|
|
69
|
+
.filter((d) => d.isDirectory() && fs.existsSync(path.join(THEMES_DIR, d.name, "theme.json")))
|
|
70
|
+
.map((d) => d.name)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function loadTheme(id: string): Theme {
|
|
74
|
+
const file = path.join(THEMES_DIR, id, "theme.json")
|
|
75
|
+
if (!fs.existsSync(file)) {
|
|
76
|
+
throw new BoutiqueConfigError(`thème inconnu « ${id} ». Thèmes disponibles : ${listThemes().join(", ")}`)
|
|
77
|
+
}
|
|
78
|
+
return parseOrThrow(themeSchema, readJson(file), file)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function load(): Boutique {
|
|
82
|
+
const slug = process.env.BOUTIQUE
|
|
83
|
+
if (!slug) {
|
|
84
|
+
throw new BoutiqueConfigError("variable d'environnement BOUTIQUE absente (ex. BOUTIQUE=yout-black-friday)")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const projectFile = path.join(/*turbopackIgnore: true*/ CONFIG_DIR, "project.json")
|
|
88
|
+
const project = parseOrThrow(projectSchema, readJson(projectFile), projectFile)
|
|
89
|
+
|
|
90
|
+
const entry = project.boutiques.find((b) => b.slug === slug)
|
|
91
|
+
if (!entry) {
|
|
92
|
+
throw new BoutiqueConfigError(
|
|
93
|
+
`boutique « ${slug} » absente de ${projectFile}. Boutiques déclarées : ${project.boutiques.map((b) => b.slug).join(", ")}`
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const theme = loadTheme(entry.theme)
|
|
98
|
+
|
|
99
|
+
const keyFile = path.join(/*turbopackIgnore: true*/ KEYS_DIR, slug)
|
|
100
|
+
let publishableKey = ""
|
|
101
|
+
try {
|
|
102
|
+
publishableKey = fs.readFileSync(keyFile, "utf8").trim()
|
|
103
|
+
} catch {
|
|
104
|
+
// traité juste en dessous
|
|
105
|
+
}
|
|
106
|
+
if (!publishableKey.startsWith("pk_")) {
|
|
107
|
+
throw new BoutiqueConfigError(
|
|
108
|
+
`publishable key manquante ou invalide dans ${keyFile} (écrite par le backend au premier démarrage)`
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { defaults } = project
|
|
113
|
+
const country = entry.country ?? defaults.country
|
|
114
|
+
const shipsTo = entry.shipsTo ?? defaults.shipsTo
|
|
115
|
+
if (shipsTo !== "all" && !shipsTo.includes(country)) {
|
|
116
|
+
throw new BoutiqueConfigError(`${slug} : le pays principal « ${country} » doit faire partie de shipsTo`)
|
|
117
|
+
}
|
|
118
|
+
const language = defaults.language
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
project: project.project,
|
|
122
|
+
slug,
|
|
123
|
+
theme,
|
|
124
|
+
storeName: entry.storeName ?? defaults.storeName,
|
|
125
|
+
currency: entry.currency ?? defaults.currency,
|
|
126
|
+
country,
|
|
127
|
+
shipsTo,
|
|
128
|
+
language,
|
|
129
|
+
locale: `${language}-${country.toUpperCase()}`,
|
|
130
|
+
port: entry.port,
|
|
131
|
+
publishableKey,
|
|
132
|
+
merchantEmail: defaults.merchantEmail,
|
|
133
|
+
siblings: Object.fromEntries(
|
|
134
|
+
project.boutiques.map((b) => [b.slug, b.url ?? `http://localhost:${b.port}`])
|
|
135
|
+
),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let cached: Boutique | undefined
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Boutique de cette instance. Lue une fois (au démarrage via instrumentation.ts),
|
|
143
|
+
* puis gardée en mémoire : modifier project.json demande un redémarrage (`apply`).
|
|
144
|
+
*/
|
|
145
|
+
export function getBoutique(): Boutique {
|
|
146
|
+
if (!cached) {
|
|
147
|
+
cached = load()
|
|
148
|
+
// Lu par lib/util/money.ts, partagé avec les composants client (qui lisent <html lang>).
|
|
149
|
+
process.env.BOUTIQUE_LOCALE = cached.locale
|
|
150
|
+
}
|
|
151
|
+
return cached
|
|
152
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Inter, Oswald, Outfit, Plus_Jakarta_Sans, Poppins, Sora, Space_Grotesk } from "next/font/google"
|
|
2
|
+
import type { FontId, Theme } from "./schema"
|
|
3
|
+
|
|
4
|
+
// Polices des presets (design/bundle.css). Servies par next/font (auto-hébergées) :
|
|
5
|
+
// seules les 2 polices du thème de l'instance sont appliquées.
|
|
6
|
+
// Chaque police expose --nf-<id>, repris par --font-<id> dans styles/ub/fonts.css.
|
|
7
|
+
const inter = Inter({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-inter", preload: false })
|
|
8
|
+
const jakarta = Plus_Jakarta_Sans({ subsets: ["latin"], weight: ["500", "700", "800"], variable: "--nf-jakarta", preload: false })
|
|
9
|
+
const oswald = Oswald({ subsets: ["latin"], weight: ["500", "700"], variable: "--nf-oswald", preload: false })
|
|
10
|
+
const outfit = Outfit({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-outfit", preload: false })
|
|
11
|
+
const poppins = Poppins({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-poppins", preload: false })
|
|
12
|
+
const sora = Sora({ subsets: ["latin"], weight: ["400", "600", "700"], variable: "--nf-sora", preload: false })
|
|
13
|
+
const spaceGrotesk = Space_Grotesk({ subsets: ["latin"], weight: ["500", "700"], variable: "--nf-space-grotesk", preload: false })
|
|
14
|
+
|
|
15
|
+
const fonts: Partial<Record<FontId, { variable: string }>> = {
|
|
16
|
+
inter,
|
|
17
|
+
jakarta,
|
|
18
|
+
oswald,
|
|
19
|
+
outfit,
|
|
20
|
+
poppins,
|
|
21
|
+
sora,
|
|
22
|
+
"space-grotesk": spaceGrotesk,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Classes à poser sur <html> : variables CSS des polices du thème (+ Inter, police des formulaires du starter). */
|
|
26
|
+
export function themeFontClasses(theme: Theme): string {
|
|
27
|
+
const ids = new Set<FontId>([theme.fonts.head, theme.fonts.body, "inter"])
|
|
28
|
+
return [...ids].map((id) => fonts[id]?.variable).filter(Boolean).join(" ")
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext } from "react"
|
|
4
|
+
|
|
5
|
+
type Dict = Record<string, string>
|
|
6
|
+
|
|
7
|
+
const I18nContext = createContext<Dict>({})
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Dictionnaire de l'instance pour les composants client (checkout, panier, compte).
|
|
11
|
+
* Rempli par le layout racine (serveur) avec locales/<lang>.json + repli anglais.
|
|
12
|
+
*/
|
|
13
|
+
export function I18nProvider({ messages, children }: { messages: Dict; children: React.ReactNode }) {
|
|
14
|
+
return <I18nContext.Provider value={messages}>{children}</I18nContext.Provider>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function format(text: string, vars?: Record<string, string | number>) {
|
|
18
|
+
return vars ? text.replace(/\{(\w+)\}/g, (m, k: string) => (k in vars ? String(vars[k]) : m)) : text
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Traduction côté client : mêmes clés et mêmes variables que t() côté serveur.
|
|
23
|
+
* Une clé inconnue est renvoyée telle quelle : les composants partagés (Input, Checkbox…)
|
|
24
|
+
* peuvent donc recevoir une clé ou un texte déjà traduit.
|
|
25
|
+
*/
|
|
26
|
+
export function useT() {
|
|
27
|
+
const messages = useContext(I18nContext)
|
|
28
|
+
return (key: string, vars?: Record<string, string | number>) => format(messages[key] ?? key, vars)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Texte traduit dans du JSX, utilisable depuis un composant serveur ou client : <T k="cart.title" />. */
|
|
32
|
+
export function T({ k, vars }: { k: string; vars?: Record<string, string | number> }) {
|
|
33
|
+
return <>{useT()(k, vars)}</>
|
|
34
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "server-only"
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { getBoutique } from "./config"
|
|
6
|
+
|
|
7
|
+
type Dict = Record<string, string>
|
|
8
|
+
|
|
9
|
+
const LOCALES_DIR = path.join(process.cwd(), "locales")
|
|
10
|
+
const dicts = new Map<string, Dict>()
|
|
11
|
+
|
|
12
|
+
function dict(lang: string): Dict {
|
|
13
|
+
if (!dicts.has(lang)) {
|
|
14
|
+
const file = path.join(LOCALES_DIR, `${lang}.json`)
|
|
15
|
+
dicts.set(lang, fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : {})
|
|
16
|
+
}
|
|
17
|
+
return dicts.get(lang)!
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Traduction des textes d'interface (locales/<lang>.json), repli en anglais.
|
|
22
|
+
* Variables : t("cart.count", { count: 2 }) avec "{count} items" dans le fichier.
|
|
23
|
+
* Une clé absente partout renvoie la clé (signalé par `check`), jamais une chaîne vide.
|
|
24
|
+
*/
|
|
25
|
+
export function t(key: string, vars?: Record<string, string | number>): string {
|
|
26
|
+
const { language } = getBoutique()
|
|
27
|
+
const text = dict(language)[key] ?? dict("en")[key] ?? key
|
|
28
|
+
return vars ? text.replace(/\{(\w+)\}/g, (m, k: string) => (k in vars ? String(vars[k]) : m)) : text
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Sous-ensemble du dictionnaire à passer à un composant client (préfixe "cart." etc.). */
|
|
32
|
+
export function messages(prefix: string): Dict {
|
|
33
|
+
const { language } = getBoutique()
|
|
34
|
+
const out: Dict = {}
|
|
35
|
+
for (const d of [dict("en"), dict(language)]) {
|
|
36
|
+
for (const [k, v] of Object.entries(d)) if (k.startsWith(prefix)) out[k] = v
|
|
37
|
+
}
|
|
38
|
+
return out
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function formatDate(iso: string, opts: Intl.DateTimeFormatOptions = { dateStyle: "long" }): string {
|
|
42
|
+
return new Intl.DateTimeFormat(getBoutique().locale, opts).format(new Date(iso))
|
|
43
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schémas partagés : config/project.json, themes/<id>/theme.json,
|
|
5
|
+
* config/campaigns/<slug>.json. Les textes affichés sont du texte simple :
|
|
6
|
+
* aucun caractère < ou > (pas de HTML), longueurs bornées.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const plain = (max: number) =>
|
|
10
|
+
z
|
|
11
|
+
.string()
|
|
12
|
+
.trim()
|
|
13
|
+
.max(max)
|
|
14
|
+
.refine((v) => !/[<>]/.test(v), "texte simple uniquement (pas de HTML)")
|
|
15
|
+
|
|
16
|
+
export const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
17
|
+
|
|
18
|
+
const currency = z.string().regex(/^[a-z]{3}$/, "code devise ISO 4217 en minuscules (ex. usd)")
|
|
19
|
+
const country = z.string().regex(/^[a-z]{2}$/, "code pays ISO 3166-1 alpha-2 en minuscules (ex. us)")
|
|
20
|
+
const language = z.string().regex(/^[a-z]{2}$/, "code langue ISO 639-1 (ex. en)")
|
|
21
|
+
|
|
22
|
+
/** Pays livrés : "all" (monde entier) ou une liste de codes pays. */
|
|
23
|
+
const shipsTo = z.union([z.literal("all"), z.array(country).min(1)])
|
|
24
|
+
|
|
25
|
+
const overridable = {
|
|
26
|
+
storeName: plain(60).min(1),
|
|
27
|
+
currency,
|
|
28
|
+
/** Pays principal : format des prix et des dates (ex. en-US), pays proposé par défaut au checkout. */
|
|
29
|
+
country,
|
|
30
|
+
shipsTo,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const projectSchema = z.object({
|
|
34
|
+
project: z.string().regex(SLUG),
|
|
35
|
+
templateVersion: z.string().optional(),
|
|
36
|
+
defaults: z
|
|
37
|
+
.object({
|
|
38
|
+
...overridable,
|
|
39
|
+
shipsTo: shipsTo.default("all"),
|
|
40
|
+
language: language.default("en"),
|
|
41
|
+
merchantEmail: z.email().optional(),
|
|
42
|
+
email: z.object({ mode: z.enum(["platform", "merchant"]) }).loose().optional(),
|
|
43
|
+
})
|
|
44
|
+
.loose(),
|
|
45
|
+
boutiques: z
|
|
46
|
+
.array(
|
|
47
|
+
z
|
|
48
|
+
.object({
|
|
49
|
+
slug: z.string().regex(SLUG, "slug : minuscules, chiffres et tirets"),
|
|
50
|
+
theme: z.string().regex(SLUG),
|
|
51
|
+
port: z.number().int().min(1).max(65535),
|
|
52
|
+
url: z.url().optional(),
|
|
53
|
+
storeName: overridable.storeName.optional(),
|
|
54
|
+
currency: overridable.currency.optional(),
|
|
55
|
+
country: overridable.country.optional(),
|
|
56
|
+
shipsTo: overridable.shipsTo.optional(),
|
|
57
|
+
language: z.never({ error: "la langue se règle dans defaults, pas par boutique" }).optional(),
|
|
58
|
+
})
|
|
59
|
+
.strict()
|
|
60
|
+
)
|
|
61
|
+
.min(1, "au moins une boutique")
|
|
62
|
+
.refine((b) => new Set(b.map((x) => x.slug)).size === b.length, "slug en double"),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export type Project = z.infer<typeof projectSchema>
|
|
66
|
+
|
|
67
|
+
export const themeSchema = z.object({
|
|
68
|
+
id: z.string().regex(SLUG),
|
|
69
|
+
name: z.string().min(1),
|
|
70
|
+
reference: z.string().optional(),
|
|
71
|
+
fonts: z.object({
|
|
72
|
+
head: z.enum(["inter", "jakarta", "oswald", "outfit", "poppins", "serif", "sora", "space-grotesk"]),
|
|
73
|
+
body: z.enum(["inter", "jakarta", "oswald", "outfit", "poppins", "serif", "sora", "space-grotesk"]),
|
|
74
|
+
}),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export type Theme = z.infer<typeof themeSchema>
|
|
78
|
+
export type FontId = Theme["fonts"]["head"]
|
|
79
|
+
|
|
80
|
+
const date = z.iso.datetime({ offset: true })
|
|
81
|
+
|
|
82
|
+
/** Textes par défaut d'un thème (themes/<id>/campaign.<lang>.json), avec variables {discount}, {endDate}, {freeShipping}. */
|
|
83
|
+
export const campaignTextsSchema = z.object({
|
|
84
|
+
topbar: z.array(plain(80)).max(5),
|
|
85
|
+
hero: z.object({
|
|
86
|
+
eyebrow: plain(40),
|
|
87
|
+
title: plain(60).min(1),
|
|
88
|
+
highlight: plain(40).optional(),
|
|
89
|
+
sub: plain(160),
|
|
90
|
+
cta: plain(30),
|
|
91
|
+
cta2: plain(30).optional(),
|
|
92
|
+
image: z.string().max(500).optional(),
|
|
93
|
+
bubble: z.array(plain(12)).max(3),
|
|
94
|
+
}),
|
|
95
|
+
promo: z.object({
|
|
96
|
+
eyebrow: plain(40),
|
|
97
|
+
title: plain(60),
|
|
98
|
+
text: plain(160),
|
|
99
|
+
cta: plain(30),
|
|
100
|
+
seal: plain(20).optional(),
|
|
101
|
+
image: z.string().max(500).optional(),
|
|
102
|
+
}),
|
|
103
|
+
sections: z
|
|
104
|
+
.object({ categories: plain(40), products: plain(40) })
|
|
105
|
+
.optional(),
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
export type CampaignTexts = z.infer<typeof campaignTextsSchema>
|
|
109
|
+
|
|
110
|
+
/** Fichier d'une boutique (config/campaigns/<slug>.json) : tout est optionnel sauf les dates, et remplace les textes du thème. */
|
|
111
|
+
export const campaignFileSchema = z
|
|
112
|
+
.object({
|
|
113
|
+
startsAt: date,
|
|
114
|
+
endsAt: date,
|
|
115
|
+
nextCampaign: z.string().regex(SLUG).optional(),
|
|
116
|
+
vars: z
|
|
117
|
+
.object({ discount: plain(12), freeShipping: plain(20) })
|
|
118
|
+
.partial()
|
|
119
|
+
.optional(),
|
|
120
|
+
topbar: campaignTextsSchema.shape.topbar.optional(),
|
|
121
|
+
hero: campaignTextsSchema.shape.hero.partial().optional(),
|
|
122
|
+
promo: campaignTextsSchema.shape.promo.partial().optional(),
|
|
123
|
+
sections: campaignTextsSchema.shape.sections.unwrap().partial().optional(),
|
|
124
|
+
/** Logo de la boutique (URL du stockage), aussi utilisé comme favicon */
|
|
125
|
+
logo: z.url().max(500).optional(),
|
|
126
|
+
/** undefined = selon le thème */
|
|
127
|
+
showCountdown: z.boolean().optional(),
|
|
128
|
+
})
|
|
129
|
+
.refine((c) => new Date(c.startsAt) < new Date(c.endsAt), "startsAt doit précéder endsAt")
|
|
130
|
+
|
|
131
|
+
export type CampaignFile = z.infer<typeof campaignFileSchema>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "server-only"
|
|
2
|
+
|
|
3
|
+
import { getBoutique } from "@lib/boutique/config"
|
|
4
|
+
import Medusa, { FetchArgs, FetchInput } from "@medusajs/js-sdk"
|
|
5
|
+
|
|
6
|
+
// URL interne du backend (réseau Docker) : jamais exposée au navigateur.
|
|
7
|
+
const MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
8
|
+
|
|
9
|
+
let client: Medusa | undefined
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Client Medusa de la boutique : la publishable key vient de KEYS_DIR/<slug>,
|
|
13
|
+
* lue à l'exécution (pas de variable NEXT_PUBLIC_* figée au build).
|
|
14
|
+
* Créé au premier usage pour que `next build` n'ait besoin d'aucune boutique.
|
|
15
|
+
*/
|
|
16
|
+
function getClient(): Medusa {
|
|
17
|
+
if (client) return client
|
|
18
|
+
|
|
19
|
+
const boutique = getBoutique()
|
|
20
|
+
const medusa = new Medusa({
|
|
21
|
+
baseUrl: MEDUSA_BACKEND_URL,
|
|
22
|
+
debug: process.env.NODE_ENV === "development",
|
|
23
|
+
publishableKey: boutique.publishableKey,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Langue fixée par l'instance : les traductions Medusa suivent la langue du projet.
|
|
27
|
+
const originalFetch = medusa.client.fetch.bind(medusa.client)
|
|
28
|
+
medusa.client.fetch = async <T>(input: FetchInput, init?: FetchArgs): Promise<T> => {
|
|
29
|
+
const headers = { "x-medusa-locale": boutique.locale, ...(init?.headers ?? {}) }
|
|
30
|
+
return originalFetch(input, { ...init, headers })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
client = medusa
|
|
34
|
+
return client
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const sdk = new Proxy({} as Medusa, {
|
|
38
|
+
get: (_target, prop) => Reflect.get(getClient(), prop),
|
|
39
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { CreditCard } from "@medusajs/icons"
|
|
3
|
+
|
|
4
|
+
import Ideal from "@modules/common/icons/ideal"
|
|
5
|
+
import Bancontact from "@modules/common/icons/bancontact"
|
|
6
|
+
import PayPal from "@modules/common/icons/paypal"
|
|
7
|
+
|
|
8
|
+
/* Map of payment provider_id to their title and icon. Add in any payment providers you want to use. */
|
|
9
|
+
export const paymentInfoMap: Record<
|
|
10
|
+
string,
|
|
11
|
+
{ title: string; icon: React.JSX.Element }
|
|
12
|
+
> = {
|
|
13
|
+
pp_stripe_stripe: {
|
|
14
|
+
title: "Credit card",
|
|
15
|
+
icon: <CreditCard />,
|
|
16
|
+
},
|
|
17
|
+
"pp_medusa-payments_default": {
|
|
18
|
+
title: "Credit card",
|
|
19
|
+
icon: <CreditCard />,
|
|
20
|
+
},
|
|
21
|
+
"pp_stripe-ideal_stripe": {
|
|
22
|
+
title: "iDeal",
|
|
23
|
+
icon: <Ideal />,
|
|
24
|
+
},
|
|
25
|
+
"pp_stripe-bancontact_stripe": {
|
|
26
|
+
title: "Bancontact",
|
|
27
|
+
icon: <Bancontact />,
|
|
28
|
+
},
|
|
29
|
+
pp_paypal_paypal: {
|
|
30
|
+
title: "PayPal",
|
|
31
|
+
icon: <PayPal />,
|
|
32
|
+
},
|
|
33
|
+
pp_system_default: {
|
|
34
|
+
title: "Manual Payment",
|
|
35
|
+
icon: <CreditCard />,
|
|
36
|
+
},
|
|
37
|
+
// Add more payment providers here
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// This only checks if it is native stripe or medusa payments for card payments, it ignores the other stripe-based providers
|
|
41
|
+
export const isStripeLike = (providerId?: string) => {
|
|
42
|
+
return (
|
|
43
|
+
providerId?.startsWith("pp_stripe_") || providerId?.startsWith("pp_medusa-")
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const isPaypal = (providerId?: string) => {
|
|
48
|
+
return providerId?.startsWith("pp_paypal")
|
|
49
|
+
}
|
|
50
|
+
export const isManual = (providerId?: string) => {
|
|
51
|
+
return providerId?.startsWith("pp_system_default")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add currencies that don't need to be divided by 100
|
|
55
|
+
export const noDivisionCurrencies = [
|
|
56
|
+
"krw",
|
|
57
|
+
"jpy",
|
|
58
|
+
"vnd",
|
|
59
|
+
"clp",
|
|
60
|
+
"pyg",
|
|
61
|
+
"xaf",
|
|
62
|
+
"xof",
|
|
63
|
+
"bif",
|
|
64
|
+
"djf",
|
|
65
|
+
"gnf",
|
|
66
|
+
"kmf",
|
|
67
|
+
"mga",
|
|
68
|
+
"rwf",
|
|
69
|
+
"xpf",
|
|
70
|
+
"htg",
|
|
71
|
+
"vuv",
|
|
72
|
+
"xag",
|
|
73
|
+
"xdr",
|
|
74
|
+
"xau",
|
|
75
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext } from "react"
|
|
4
|
+
|
|
5
|
+
interface ModalContext {
|
|
6
|
+
close: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ModalContext = createContext<ModalContext | null>(null)
|
|
10
|
+
|
|
11
|
+
interface ModalProviderProps {
|
|
12
|
+
children?: React.ReactNode
|
|
13
|
+
close: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ModalProvider = ({ children, close }: ModalProviderProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<ModalContext.Provider
|
|
19
|
+
value={{
|
|
20
|
+
close,
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</ModalContext.Provider>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useModal = () => {
|
|
29
|
+
const context = useContext(ModalContext)
|
|
30
|
+
if (context === null) {
|
|
31
|
+
throw new Error("useModal must be used within a ModalProvider")
|
|
32
|
+
}
|
|
33
|
+
return context
|
|
34
|
+
}
|