├── .cursorrules ├── .gitignore ├── README.md ├── abandoned-cart ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── custom │ │ │ │ └── route.ts │ │ └── store │ │ │ └── custom │ │ │ └── route.ts │ ├── jobs │ │ ├── README.md │ │ └── send-abandoned-cart-notification.ts │ ├── links │ │ └── README.md │ ├── modules │ │ └── README.md │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ └── README.md │ └── workflows │ │ ├── README.md │ │ ├── send-abandoned-carts.ts │ │ └── steps │ │ └── send-abandoned-notifications.ts ├── tsconfig.json └── yarn.lock ├── algolia-integration ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── lib │ │ │ └── sdk.ts │ │ ├── routes │ │ │ └── settings │ │ │ │ └── algolia │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── admin │ │ │ └── algolia │ │ │ │ └── sync │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ └── store │ │ │ └── products │ │ │ └── search │ │ │ └── route.ts │ ├── modules │ │ └── algolia │ │ │ ├── index.ts │ │ │ └── service.ts │ ├── scripts │ │ └── seed.ts │ ├── subscribers │ │ ├── algolia-sync.ts │ │ ├── product-sync.ts │ │ └── product.delete.ts │ └── workflows │ │ ├── delete-products-from-algolia.ts │ │ ├── steps │ │ ├── delete-products-from-algolia.ts │ │ └── sync-products.ts │ │ └── sync-products.ts ├── tsconfig.json └── yarn.lock ├── bundled-products ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── components │ │ │ └── create-bundled-product.tsx │ │ ├── lib │ │ │ └── sdk.ts │ │ ├── routes │ │ │ └── bundled-products │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── bundled-products │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ └── store │ │ │ ├── bundle-products │ │ │ └── [id] │ │ │ │ └── route.ts │ │ │ └── carts │ │ │ └── [id] │ │ │ └── line-item-bundles │ │ │ ├── [bundle_id] │ │ │ └── route.ts │ │ │ └── route.ts │ ├── jobs │ │ └── README.md │ ├── links │ │ ├── README.md │ │ ├── bundle-item-product.ts │ │ └── bundle-product.ts │ ├── modules │ │ ├── README.md │ │ └── bundled-product │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-bundled-product.json │ │ │ └── Migration20250428093025.ts │ │ │ ├── models │ │ │ ├── bundle-item.ts │ │ │ └── bundle.ts │ │ │ └── service.ts │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ └── README.md │ └── workflows │ │ ├── README.md │ │ ├── add-bundle-to-cart.ts │ │ ├── create-bundled-product.ts │ │ ├── remove-bundle-from-cart.ts │ │ └── steps │ │ ├── create-bundle-items.ts │ │ ├── create-bundle.ts │ │ └── prepare-bundle-cart-data.ts ├── static │ ├── 1745848138941-Camera Bag Stock Picture (1).jpg │ ├── 1745848169323-Camera Stock Picture.jpg │ ├── 1745848190420-Camera Bag Stock Picture.jpg │ ├── 1745848209694-Camera Stock Picture.jpg │ ├── 1745852411867-Manfrotto Advanced Shoulder Bag.jpg │ ├── 1745922959236-Camera Stock Picture.jpg │ ├── 1745922994433-Camera Bag Stock Picture.jpg │ ├── 1745923607199-Manfrotto Advanced Shoulder Bag.jpg │ ├── 1745938409937-Camera Stock Picture.jpg │ ├── 1745938431753-Camera Bag Stock Picture.jpg │ └── 1745938962441-Manfrotto Advanced Shoulder Bag.jpg ├── tsconfig.json └── yarn.lock ├── custom-item-price ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ └── routes.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ └── tsconfig.json │ ├── api │ │ ├── README.md │ │ ├── middlewares.ts │ │ └── store │ │ │ └── carts │ │ │ └── [id] │ │ │ └── line-items-metals │ │ │ └── route.ts │ ├── modules │ │ └── metal-prices │ │ │ ├── __tests__ │ │ │ └── service.spec.ts │ │ │ ├── index.ts │ │ │ └── service.ts │ ├── scripts │ │ └── seed.ts │ └── workflows │ │ ├── add-custom-to-cart.ts │ │ └── steps │ │ └── get-variant-metal-prices.ts ├── tsconfig.json └── yarn.lock ├── digital-product ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── integration-tests │ ├── http │ │ ├── admin.spec.ts │ │ └── store.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── components │ │ │ └── create-digital-product-form │ │ │ │ └── index.tsx │ │ ├── routes │ │ │ └── digital-products │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ ├── types │ │ │ └── index.ts │ │ └── vite-env.d.ts │ ├── api │ │ ├── admin │ │ │ └── digital-products │ │ │ │ ├── route.ts │ │ │ │ └── upload │ │ │ │ └── [type] │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ ├── store │ │ │ ├── carts │ │ │ │ └── [id] │ │ │ │ │ └── complete-digital │ │ │ │ │ └── route.ts │ │ │ ├── customers │ │ │ │ └── me │ │ │ │ │ └── digital-products │ │ │ │ │ ├── [mediaId] │ │ │ │ │ └── download │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ └── digital-products │ │ │ │ └── [id] │ │ │ │ └── preview │ │ │ │ └── route.ts │ │ └── validation-schemas.ts │ ├── links │ │ ├── digital-product-order.ts │ │ └── digital-product-variant.ts │ ├── modules │ │ ├── digital-product-fulfillment │ │ │ ├── index.ts │ │ │ └── service.ts │ │ └── digital-product │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-digital-products.json │ │ │ ├── Migration20240917093019.ts │ │ │ └── Migration20241213160023.ts │ │ │ ├── models │ │ │ ├── digital-product-media.ts │ │ │ ├── digital-product-order.ts │ │ │ └── digital-product.ts │ │ │ ├── service.ts │ │ │ └── types │ │ │ └── index.ts │ ├── scripts │ │ └── seed.ts │ ├── subscribers │ │ ├── handle-digital-order.ts │ │ └── handle-product-deleted.ts │ └── workflows │ │ ├── create-digital-product-order │ │ ├── index.ts │ │ └── steps │ │ │ └── create-digital-product-order.ts │ │ ├── create-digital-product │ │ ├── index.ts │ │ └── steps │ │ │ ├── create-digital-product-medias.ts │ │ │ └── create-digital-product.ts │ │ ├── delete-product-digital-products │ │ ├── index.ts │ │ └── steps │ │ │ ├── delete-digital-products.ts │ │ │ └── retrieve-digital-products-to-delete.ts │ │ └── fulfill-digital-order │ │ ├── index.ts │ │ └── steps │ │ └── send-digital-order-notification.ts ├── tsconfig.json └── yarn.lock ├── express-checkout-storefront ├── .env.template ├── .gitignore ├── README.md ├── app │ ├── [handle] │ │ └── page.tsx │ ├── confirmation │ │ └── [id] │ │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ └── layout.tsx ├── components │ ├── Address │ │ └── index.tsx │ ├── Card │ │ └── index.tsx │ ├── Payment │ │ └── index.tsx │ ├── Product │ │ └── index.tsx │ ├── Router │ │ └── index.tsx │ ├── SecondCol │ │ └── index.tsx │ └── Shipping │ │ └── index.tsx ├── eslint.config.mjs ├── lib │ ├── price.ts │ └── sdk.ts ├── next.config.ts ├── package.json ├── postcss.config.mjs ├── providers │ ├── cart.tsx │ └── region.tsx ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── tailwind.config.ts ├── tsconfig.json └── yarn.lock ├── localization-contentful ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── lib │ │ │ └── sdk.ts │ │ ├── routes │ │ │ └── contentful │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── contentful │ │ │ │ └── sync │ │ │ │ └── route.ts │ │ ├── hooks │ │ │ └── contentful │ │ │ │ └── route.ts │ │ └── store │ │ │ ├── locales │ │ │ └── route.ts │ │ │ └── products │ │ │ └── [id] │ │ │ └── [locale] │ │ │ └── route.ts │ ├── jobs │ │ └── README.md │ ├── links │ │ ├── README.md │ │ └── product-contentful.ts │ ├── modules │ │ ├── README.md │ │ └── contentful │ │ │ ├── index.ts │ │ │ ├── loader │ │ │ └── create-content-models.ts │ │ │ └── service.ts │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ ├── README.md │ │ ├── create-product.ts │ │ └── sync-products.ts │ └── workflows │ │ ├── README.md │ │ ├── create-products-contentful.ts │ │ ├── handle-contentful-hook.ts │ │ └── steps │ │ ├── create-products-contentful.ts │ │ └── prepare-update-data.ts ├── tsconfig.json └── yarn.lock ├── loyalty-points ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── admin │ │ │ └── custom │ │ │ │ └── route.ts │ │ └── store │ │ │ ├── carts │ │ │ └── [id] │ │ │ │ └── loyalty-points │ │ │ │ └── route.ts │ │ │ └── customers │ │ │ └── me │ │ │ └── loyalty-points │ │ │ └── route.ts │ ├── modules │ │ └── loyalty │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-loyalty-points.json │ │ │ └── Migration20250407153111.ts │ │ │ ├── models │ │ │ └── loyalty-point.ts │ │ │ └── service.ts │ ├── scripts │ │ └── seed.ts │ ├── subscribers │ │ └── order-placed.ts │ ├── utils │ │ └── promo.ts │ └── workflows │ │ ├── apply-loyalty-on-cart.ts │ │ ├── handle-order-points.ts │ │ ├── hooks │ │ └── complete-cart.ts │ │ ├── remove-loyalty-from-cart.ts │ │ └── steps │ │ ├── add-purchase-as-points.ts │ │ ├── deduct-purchase-points.ts │ │ ├── get-cart-loyalty-promo-amount.ts │ │ ├── get-cart-loyalty-promo.ts │ │ └── validate-customer-exists.ts ├── tsconfig.json └── yarn.lock ├── marketplace ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── middlewares.ts │ │ ├── store │ │ │ └── carts │ │ │ │ └── [id] │ │ │ │ └── complete-vendor │ │ │ │ └── route.ts │ │ └── vendors │ │ │ ├── admins │ │ │ └── [id] │ │ │ │ └── route.ts │ │ │ ├── orders │ │ │ └── route.ts │ │ │ ├── products │ │ │ └── route.ts │ │ │ └── route.ts │ ├── links │ │ ├── vendor-order.ts │ │ └── vendor-product.ts │ ├── modules │ │ └── marketplace │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-V7vl.json │ │ │ ├── Migration20240708151444.ts │ │ │ └── Migration20250311091542.ts │ │ │ ├── models │ │ │ ├── vendor-admin.ts │ │ │ └── vendor.ts │ │ │ └── service.ts │ ├── scripts │ │ └── seed.ts │ └── workflows │ │ └── marketplace │ │ ├── create-vendor-orders │ │ ├── index.ts │ │ └── steps │ │ │ ├── create-vendor-orders.ts │ │ │ └── group-vendor-items.ts │ │ ├── create-vendor-product │ │ └── index.ts │ │ ├── create-vendor │ │ ├── index.ts │ │ └── steps │ │ │ ├── create-vendor-admin.ts │ │ │ └── create-vendor.ts │ │ └── delete-vendor-admin │ │ ├── index.ts │ │ └── steps │ │ └── delete-vendor-admin.ts ├── tsconfig.json └── yarn.lock ├── migrate-from-magento ├── .gitignore ├── README.md ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── components │ │ │ └── migration-form.tsx │ │ ├── lib │ │ │ └── sdk.ts │ │ ├── routes │ │ │ └── magento │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── magento │ │ │ │ └── migrations │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ └── store │ │ │ └── plugin │ │ │ └── route.ts │ ├── jobs │ │ ├── README.md │ │ └── migrate-magento.ts │ ├── links │ │ └── README.md │ ├── modules │ │ ├── README.md │ │ └── magento │ │ │ ├── index.ts │ │ │ ├── service.ts │ │ │ └── types.ts │ ├── providers │ │ └── README.md │ ├── subscribers │ │ ├── README.md │ │ └── migrate-magento.ts │ └── workflows │ │ ├── README.md │ │ ├── index.ts │ │ ├── migrate-categories-from-magento.ts │ │ ├── migrate-products-from-magento.ts │ │ └── steps │ │ ├── get-magento-categories.ts │ │ ├── get-magento-products.ts │ │ └── update-product-categories.ts ├── tsconfig.json └── yarn.lock ├── phone-auth ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ └── middlewares.ts │ ├── jobs │ │ └── README.md │ ├── links │ │ └── README.md │ ├── modules │ │ ├── README.md │ │ ├── phone-auth │ │ │ ├── index.ts │ │ │ └── service.ts │ │ └── twilio-sms │ │ │ ├── index.ts │ │ │ └── service.ts │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ ├── README.md │ │ └── send-otp.ts │ └── workflows │ │ └── README.md ├── tsconfig.json └── yarn.lock ├── product-reviews ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── lib │ │ │ └── sdk.ts │ │ ├── routes │ │ │ └── reviews │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── reviews │ │ │ │ ├── route.ts │ │ │ │ └── status │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ └── store │ │ │ ├── products │ │ │ └── [id] │ │ │ │ └── reviews │ │ │ │ └── route.ts │ │ │ └── reviews │ │ │ └── route.ts │ ├── jobs │ │ └── README.md │ ├── links │ │ ├── README.md │ │ └── review-product.ts │ ├── modules │ │ ├── README.md │ │ └── product-review │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-product-reviews.json │ │ │ ├── Migration20250312151153.ts │ │ │ └── Migration20250313081542.ts │ │ │ ├── models │ │ │ └── review.ts │ │ │ └── service.ts │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ └── README.md │ └── workflows │ │ ├── README.md │ │ ├── create-review.ts │ │ ├── steps │ │ ├── create-review.ts │ │ └── update-review.ts │ │ └── update-review.ts ├── tsconfig.json └── yarn.lock ├── quotes-management ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── components │ │ │ ├── amount.tsx │ │ │ ├── manage-item.tsx │ │ │ ├── manage-quote-form.tsx │ │ │ ├── quote-items.tsx │ │ │ └── totals-breakdown.tsx │ │ ├── hooks │ │ │ ├── order-preview.tsx │ │ │ └── quotes.tsx │ │ ├── lib │ │ │ └── sdk.ts │ │ ├── routes │ │ │ └── quotes │ │ │ │ ├── [id] │ │ │ │ ├── manage │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ ├── types.ts │ │ ├── utils │ │ │ └── format-amount.ts │ │ └── vite-env.d.ts │ ├── api │ │ ├── admin │ │ │ └── quotes │ │ │ │ ├── [id] │ │ │ │ ├── reject │ │ │ │ │ └── route.ts │ │ │ │ ├── route.ts │ │ │ │ └── send │ │ │ │ │ └── route.ts │ │ │ │ ├── query-config.ts │ │ │ │ ├── route.ts │ │ │ │ └── validators.ts │ │ ├── middlewares.ts │ │ └── store │ │ │ ├── customers │ │ │ └── me │ │ │ │ └── quotes │ │ │ │ ├── [id] │ │ │ │ ├── accept │ │ │ │ │ └── route.ts │ │ │ │ ├── preview │ │ │ │ │ └── route.ts │ │ │ │ └── reject │ │ │ │ │ └── route.ts │ │ │ │ ├── query-config.ts │ │ │ │ └── route.ts │ │ │ └── validators.ts │ ├── links │ │ ├── quote-cart.ts │ │ ├── quote-customer.ts │ │ ├── quote-order-change.ts │ │ └── quote-order.ts │ ├── modules │ │ └── quote │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-quotes-management.json │ │ │ ├── Migration20250303093636.ts │ │ │ └── Migration20250304145603.ts │ │ │ ├── models │ │ │ └── quote.ts │ │ │ └── service.ts │ ├── scripts │ │ └── seed.ts │ └── workflows │ │ ├── create-request-for-quote.ts │ │ ├── customer-accept-quote.ts │ │ ├── customer-reject-quote.ts │ │ ├── merchant-reject-quote.ts │ │ ├── merchant-send-quote.ts │ │ └── steps │ │ ├── create-quotes.ts │ │ ├── update-quotes.ts │ │ ├── validate-quote-can-accept.ts │ │ └── validate-quote-not-accepted.ts ├── tsconfig.json └── yarn.lock ├── re-order ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── custom │ │ │ │ └── route.ts │ │ └── store │ │ │ └── customers │ │ │ └── me │ │ │ └── orders │ │ │ └── [id] │ │ │ └── route.ts │ ├── jobs │ │ └── README.md │ ├── links │ │ └── README.md │ ├── modules │ │ └── README.md │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ └── README.md │ └── workflows │ │ └── reorder.ts ├── tsconfig.json └── yarn.lock ├── resend-integration ├── .env.template ├── .env.test ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ └── http │ │ ├── README.md │ │ └── health.spec.ts ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── admin │ │ │ └── custom │ │ │ │ └── route.ts │ │ └── store │ │ │ └── custom │ │ │ └── route.ts │ ├── modules │ │ └── resend │ │ │ ├── emails │ │ │ └── order-placed.tsx │ │ │ ├── index.ts │ │ │ └── service.ts │ ├── scripts │ │ └── seed.ts │ ├── subscribers │ │ └── order-placed.ts │ └── workflows │ │ ├── send-order-confirmation.ts │ │ └── steps │ │ └── send-notification.ts ├── tsconfig.json └── yarn.lock ├── restaurant-marketplace ├── .env.template ├── .gitignore ├── .npmrc ├── .yarnrc.yml ├── README.md ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── deliveries │ │ │ └── [id] │ │ │ │ ├── accept │ │ │ │ └── route.ts │ │ │ │ ├── claim │ │ │ │ └── route.ts │ │ │ │ ├── complete │ │ │ │ └── route.ts │ │ │ │ ├── middlewares.ts │ │ │ │ ├── pick-up │ │ │ │ └── route.ts │ │ │ │ ├── prepare │ │ │ │ └── route.ts │ │ │ │ ├── ready │ │ │ │ └── route.ts │ │ │ │ └── subscribe │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ ├── restaurants │ │ │ ├── [id] │ │ │ │ ├── admins │ │ │ │ │ └── [admin_id] │ │ │ │ │ │ └── route.ts │ │ │ │ ├── products │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── route.ts │ │ │ └── validation-schemas.ts │ │ ├── store │ │ │ └── deliveries │ │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ ├── users │ │ │ ├── route.ts │ │ │ └── validation-schemas.ts │ │ └── utils │ │ │ ├── is-delivery-driver.ts │ │ │ └── is-delivery-restaurant.ts │ ├── links │ │ ├── delivery-cart.ts │ │ ├── delivery-order.ts │ │ ├── restaurant-delivery.ts │ │ └── restaurant-products.ts │ ├── modules │ │ ├── README.md │ │ ├── delivery │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ │ ├── .snapshot-my-medusa-eats-2.json │ │ │ │ ├── Migration20240826073704.ts │ │ │ │ └── Migration20250310081142.ts │ │ │ ├── models │ │ │ │ ├── delivery.ts │ │ │ │ └── driver.ts │ │ │ ├── service.ts │ │ │ └── types │ │ │ │ └── index.ts │ │ └── restaurant │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-TGe-.json │ │ │ └── Migration20240823135518.ts │ │ │ ├── models │ │ │ ├── restaurant-admin.ts │ │ │ └── restaurant.ts │ │ │ ├── service.ts │ │ │ └── types │ │ │ └── index.ts │ ├── scripts │ │ └── seed.ts │ └── workflows │ │ ├── delivery │ │ ├── steps │ │ │ ├── await-delivery.ts │ │ │ ├── await-driver-claim.ts │ │ │ ├── await-pick-up.ts │ │ │ ├── await-preparation.ts │ │ │ ├── await-start-preparation.ts │ │ │ ├── create-delivery.ts │ │ │ ├── create-fulfillment.ts │ │ │ ├── create-order.ts │ │ │ ├── notify-restaurant.ts │ │ │ ├── set-step-failed.ts │ │ │ ├── set-step-success.ts │ │ │ ├── set-transaction-id.ts │ │ │ ├── update-delivery.ts │ │ │ └── validate-restaurant.ts │ │ └── workflows │ │ │ ├── claim-delivery.ts │ │ │ ├── create-delivery.ts │ │ │ ├── handle-delivery.ts │ │ │ └── update-delivery.ts │ │ ├── restaurant │ │ ├── steps │ │ │ ├── create-restaurant.ts │ │ │ └── delete-restaurant-admin.ts │ │ └── workflows │ │ │ ├── create-restaurant-products.ts │ │ │ ├── create-restaurant.ts │ │ │ └── delete-restaurant-admin.ts │ │ └── user │ │ ├── steps │ │ ├── create-driver.ts │ │ └── create-restaurant-admin.ts │ │ └── workflows │ │ └── create-user.ts ├── tsconfig.json └── yarn.lock ├── restock-notification ├── .env.template ├── .env.test ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ └── http │ │ ├── README.md │ │ └── health.spec.ts ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── custom │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ └── store │ │ │ ├── custom │ │ │ └── route.ts │ │ │ └── restock-subscriptions │ │ │ ├── route.ts │ │ │ └── validators.ts │ ├── jobs │ │ ├── README.md │ │ └── check-restock.ts │ ├── links │ │ ├── README.md │ │ └── restock-variant.ts │ ├── modules │ │ └── restock │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-restock-notification-2.json │ │ │ └── Migration20241203150813.ts │ │ │ ├── models │ │ │ └── restock-subscription.ts │ │ │ └── service.ts │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ └── README.md │ └── workflows │ │ ├── README.md │ │ ├── create-restock-subscription │ │ ├── index.ts │ │ └── steps │ │ │ ├── create-restock-subscription.ts │ │ │ ├── update-restock-subscription.ts │ │ │ └── validate-variant-out-of-stock.ts │ │ └── send-restock-notifications │ │ ├── index.ts │ │ └── steps │ │ ├── delete-restock-subscriptions.ts │ │ ├── get-distinct-subscriptions.ts │ │ ├── get-restocked.ts │ │ └── send-restock-notification.ts ├── tsconfig.json └── yarn.lock ├── sanity-integration ├── README.md ├── medusa │ ├── .env.template │ ├── .gitignore │ ├── .yarnrc.yml │ ├── instrumentation.ts │ ├── integration-tests │ │ └── http │ │ │ ├── README.md │ │ │ └── health.spec.ts │ ├── jest.config.js │ ├── medusa-config.ts │ ├── package.json │ ├── src │ │ ├── admin │ │ │ ├── hooks │ │ │ │ └── sanity.tsx │ │ │ ├── lib │ │ │ │ └── sdk.ts │ │ │ ├── routes │ │ │ │ └── sanity │ │ │ │ │ └── page.tsx │ │ │ ├── tsconfig.json │ │ │ ├── vite-env.d.ts │ │ │ └── widgets │ │ │ │ └── sanity-product.tsx │ │ ├── api │ │ │ ├── admin │ │ │ │ └── sanity │ │ │ │ │ ├── documents │ │ │ │ │ └── [id] │ │ │ │ │ │ ├── route.ts │ │ │ │ │ │ └── sync │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── syncs │ │ │ │ │ └── route.ts │ │ │ └── store │ │ │ │ └── custom │ │ │ │ └── route.ts │ │ ├── links │ │ │ └── product-sanity.ts │ │ ├── modules │ │ │ └── sanity │ │ │ │ ├── index.ts │ │ │ │ └── service.ts │ │ ├── scripts │ │ │ └── seed.ts │ │ ├── subscribers │ │ │ └── sanity-product-sync.ts │ │ └── workflows │ │ │ └── sanity-sync-products │ │ │ ├── index.ts │ │ │ └── steps │ │ │ └── sync.ts │ ├── tsconfig.json │ └── yarn.lock └── storefront │ ├── .env.template │ ├── .eslintrc.js │ ├── .github │ ├── scripts │ │ └── medusa-config.js │ └── workflows │ │ └── test-e2e.yaml │ ├── .gitignore │ ├── .prettierrc │ ├── .yarnrc.yml │ ├── LICENSE │ ├── check-env-variables.js │ ├── e2e │ ├── .env.example │ ├── README.md │ ├── data │ │ ├── reset.ts │ │ └── seed.ts │ ├── fixtures │ │ ├── account │ │ │ ├── account-page.ts │ │ │ ├── addresses-page.ts │ │ │ ├── index.ts │ │ │ ├── login-page.ts │ │ │ ├── modals │ │ │ │ └── address-modal.ts │ │ │ ├── order-page.ts │ │ │ ├── orders-page.ts │ │ │ ├── overview-page.ts │ │ │ ├── profile-page.ts │ │ │ └── register-page.ts │ │ ├── base │ │ │ ├── base-modal.ts │ │ │ ├── base-page.ts │ │ │ ├── cart-dropdown.ts │ │ │ ├── nav-menu.ts │ │ │ └── search-modal.ts │ │ ├── cart-page.ts │ │ ├── category-page.ts │ │ ├── checkout-page.ts │ │ ├── index.ts │ │ ├── modals │ │ │ └── mobile-actions-modal.ts │ │ ├── order-page.ts │ │ ├── product-page.ts │ │ └── store-page.ts │ ├── index.ts │ ├── tests │ │ ├── authenticated │ │ │ ├── address.spec.ts │ │ │ ├── orders.spec.ts │ │ │ └── profile.spec.ts │ │ ├── global │ │ │ ├── public-setup.ts │ │ │ ├── setup.ts │ │ │ └── teardown.ts │ │ └── public │ │ │ ├── cart.spec.ts │ │ │ ├── checkout.spec.ts │ │ │ ├── discount.spec.ts │ │ │ ├── giftcard.spec.ts │ │ │ ├── login.spec.ts │ │ │ ├── register.spec.ts │ │ │ └── search.spec.ts │ └── utils │ │ ├── index.ts │ │ └── locators.ts │ ├── next-env.d.ts │ ├── next-sitemap.js │ ├── next.config.js │ ├── package.json │ ├── playwright.config.ts │ ├── postcss.config.js │ ├── public │ └── favicon.ico │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── src │ ├── app │ │ ├── [countryCode] │ │ │ ├── (checkout) │ │ │ │ ├── checkout │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── not-found.tsx │ │ │ └── (main) │ │ │ │ ├── account │ │ │ │ ├── @dashboard │ │ │ │ │ ├── addresses │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ ├── orders │ │ │ │ │ │ ├── details │ │ │ │ │ │ │ └── [id] │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── profile │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── @login │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── loading.tsx │ │ │ │ ├── cart │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ │ │ ├── categories │ │ │ │ └── [...category] │ │ │ │ │ └── page.tsx │ │ │ │ ├── collections │ │ │ │ └── [handle] │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ ├── order │ │ │ │ └── [id] │ │ │ │ │ ├── confirmed │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ │ └── transfer │ │ │ │ │ └── [token] │ │ │ │ │ ├── accept │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── decline │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── products │ │ │ │ └── [handle] │ │ │ │ │ └── page.tsx │ │ │ │ └── store │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── not-found.tsx │ │ ├── opengraph-image.jpg │ │ └── twitter-image.jpg │ ├── lib │ │ ├── config.ts │ │ ├── constants.tsx │ │ ├── context │ │ │ └── modal-context.tsx │ │ ├── data │ │ │ ├── cart.ts │ │ │ ├── categories.ts │ │ │ ├── collections.ts │ │ │ ├── cookies.ts │ │ │ ├── customer.ts │ │ │ ├── fulfillment.ts │ │ │ ├── onboarding.ts │ │ │ ├── orders.ts │ │ │ ├── payment.ts │ │ │ ├── products.ts │ │ │ └── regions.ts │ │ ├── hooks │ │ │ ├── use-in-view.tsx │ │ │ └── use-toggle-state.tsx │ │ └── util │ │ │ ├── compare-addresses.ts │ │ │ ├── env.ts │ │ │ ├── get-precentage-diff.ts │ │ │ ├── get-product-price.ts │ │ │ ├── isEmpty.ts │ │ │ ├── medusa-error.ts │ │ │ ├── money.ts │ │ │ ├── repeat.ts │ │ │ └── sort-products.ts │ ├── middleware.ts │ ├── modules │ │ ├── account │ │ │ ├── components │ │ │ │ ├── account-info │ │ │ │ │ └── index.tsx │ │ │ │ ├── account-nav │ │ │ │ │ └── index.tsx │ │ │ │ ├── address-book │ │ │ │ │ └── index.tsx │ │ │ │ ├── address-card │ │ │ │ │ ├── add-address.tsx │ │ │ │ │ └── edit-address-modal.tsx │ │ │ │ ├── login │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-card │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-overview │ │ │ │ │ └── index.tsx │ │ │ │ ├── overview │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-billing-address │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-email │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-name │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-password │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-phone │ │ │ │ │ └── index.tsx │ │ │ │ ├── register │ │ │ │ │ └── index.tsx │ │ │ │ └── transfer-request-form │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── account-layout.tsx │ │ │ │ └── login-template.tsx │ │ ├── cart │ │ │ ├── components │ │ │ │ ├── cart-item-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── empty-cart-message │ │ │ │ │ └── index.tsx │ │ │ │ ├── item │ │ │ │ │ └── index.tsx │ │ │ │ └── sign-in-prompt │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── index.tsx │ │ │ │ ├── items.tsx │ │ │ │ ├── preview.tsx │ │ │ │ └── summary.tsx │ │ ├── categories │ │ │ └── templates │ │ │ │ └── index.tsx │ │ ├── checkout │ │ │ ├── components │ │ │ │ ├── address-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── addresses │ │ │ │ │ └── index.tsx │ │ │ │ ├── billing_address │ │ │ │ │ └── index.tsx │ │ │ │ ├── country-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── discount-code │ │ │ │ │ └── index.tsx │ │ │ │ ├── error-message │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-container │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-test │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-wrapper │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── stripe-wrapper.tsx │ │ │ │ ├── payment │ │ │ │ │ └── index.tsx │ │ │ │ ├── review │ │ │ │ │ └── index.tsx │ │ │ │ ├── shipping-address │ │ │ │ │ └── index.tsx │ │ │ │ ├── shipping │ │ │ │ │ └── index.tsx │ │ │ │ └── submit-button │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── checkout-form │ │ │ │ └── index.tsx │ │ │ │ └── checkout-summary │ │ │ │ └── index.tsx │ │ ├── collections │ │ │ └── templates │ │ │ │ └── index.tsx │ │ ├── common │ │ │ ├── components │ │ │ │ ├── cart-totals │ │ │ │ │ └── index.tsx │ │ │ │ ├── checkbox │ │ │ │ │ └── index.tsx │ │ │ │ ├── delete-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── divider │ │ │ │ │ └── index.tsx │ │ │ │ ├── filter-radio-group │ │ │ │ │ └── index.tsx │ │ │ │ ├── input │ │ │ │ │ └── index.tsx │ │ │ │ ├── interactive-link │ │ │ │ │ └── index.tsx │ │ │ │ ├── line-item-options │ │ │ │ │ └── index.tsx │ │ │ │ ├── line-item-price │ │ │ │ │ └── index.tsx │ │ │ │ ├── line-item-unit-price │ │ │ │ │ └── index.tsx │ │ │ │ ├── localized-client-link │ │ │ │ │ └── index.tsx │ │ │ │ ├── modal │ │ │ │ │ └── index.tsx │ │ │ │ ├── native-select │ │ │ │ │ └── index.tsx │ │ │ │ └── radio │ │ │ │ │ └── index.tsx │ │ │ └── icons │ │ │ │ ├── back.tsx │ │ │ │ ├── bancontact.tsx │ │ │ │ ├── chevron-down.tsx │ │ │ │ ├── eye-off.tsx │ │ │ │ ├── eye.tsx │ │ │ │ ├── fast-delivery.tsx │ │ │ │ ├── ideal.tsx │ │ │ │ ├── map-pin.tsx │ │ │ │ ├── medusa.tsx │ │ │ │ ├── nextjs.tsx │ │ │ │ ├── package.tsx │ │ │ │ ├── paypal.tsx │ │ │ │ ├── placeholder-image.tsx │ │ │ │ ├── refresh.tsx │ │ │ │ ├── spinner.tsx │ │ │ │ ├── trash.tsx │ │ │ │ ├── user.tsx │ │ │ │ └── x.tsx │ │ ├── home │ │ │ └── components │ │ │ │ ├── featured-products │ │ │ │ ├── index.tsx │ │ │ │ └── product-rail │ │ │ │ │ └── index.tsx │ │ │ │ └── hero │ │ │ │ └── index.tsx │ │ ├── layout │ │ │ ├── components │ │ │ │ ├── cart-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── cart-dropdown │ │ │ │ │ └── index.tsx │ │ │ │ ├── cart-mismatch-banner │ │ │ │ │ └── index.tsx │ │ │ │ ├── country-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── medusa-cta │ │ │ │ │ └── index.tsx │ │ │ │ └── side-menu │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── footer │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── nav │ │ │ │ └── index.tsx │ │ ├── order │ │ │ ├── components │ │ │ │ ├── help │ │ │ │ │ └── index.tsx │ │ │ │ ├── item │ │ │ │ │ └── index.tsx │ │ │ │ ├── items │ │ │ │ │ └── index.tsx │ │ │ │ ├── onboarding-cta │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-summary │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── shipping-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── transfer-actions │ │ │ │ │ └── index.tsx │ │ │ │ └── transfer-image │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── order-completed-template.tsx │ │ │ │ └── order-details-template.tsx │ │ ├── products │ │ │ ├── components │ │ │ │ ├── image-gallery │ │ │ │ │ └── index.tsx │ │ │ │ ├── product-actions │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── mobile-actions.tsx │ │ │ │ │ └── option-select.tsx │ │ │ │ ├── product-onboarding-cta │ │ │ │ │ └── index.tsx │ │ │ │ ├── product-preview │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── price.tsx │ │ │ │ ├── product-price │ │ │ │ │ └── index.tsx │ │ │ │ ├── product-tabs │ │ │ │ │ ├── accordion.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── related-products │ │ │ │ │ └── index.tsx │ │ │ │ └── thumbnail │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── index.tsx │ │ │ │ ├── product-actions-wrapper │ │ │ │ └── index.tsx │ │ │ │ └── product-info │ │ │ │ └── index.tsx │ │ ├── shipping │ │ │ └── components │ │ │ │ └── free-shipping-price-nudge │ │ │ │ └── index.tsx │ │ ├── skeletons │ │ │ ├── components │ │ │ │ ├── skeleton-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-cart-item │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-cart-totals │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-code-form │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-line-item │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-confirmed-header │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-information │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-items │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-summary │ │ │ │ │ └── index.tsx │ │ │ │ └── skeleton-product-preview │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── skeleton-cart-page │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-confirmed │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-product-grid │ │ │ │ └── index.tsx │ │ │ │ └── skeleton-related-products │ │ │ │ └── index.tsx │ │ └── store │ │ │ ├── components │ │ │ ├── pagination │ │ │ │ └── index.tsx │ │ │ └── refinement-list │ │ │ │ ├── index.tsx │ │ │ │ └── sort-products │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ ├── index.tsx │ │ │ └── paginated-products.tsx │ ├── sanity │ │ ├── env.ts │ │ ├── lib │ │ │ ├── client.ts │ │ │ ├── image.ts │ │ │ └── live.ts │ │ ├── schemaTypes │ │ │ ├── documents │ │ │ │ └── product.ts │ │ │ └── index.ts │ │ └── structure.ts │ ├── styles │ │ └── globals.css │ └── types │ │ ├── global.ts │ │ └── icon.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── yarn.lock ├── scripts └── update.sh ├── segment-integration ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ ├── http │ │ ├── README.md │ │ └── health.spec.ts │ └── setup.js ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ └── admin │ │ │ └── custom │ │ │ └── route.ts │ ├── jobs │ │ └── README.md │ ├── links │ │ └── README.md │ ├── modules │ │ ├── README.md │ │ └── segment │ │ │ ├── index.ts │ │ │ └── service.ts │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ ├── README.md │ │ └── order-placed.ts │ └── workflows │ │ ├── README.md │ │ ├── steps │ │ └── track-event.ts │ │ └── track-order-placed.ts ├── tsconfig.json └── yarn.lock ├── shipstation-integration ├── .env.template ├── .env.test ├── .gitignore ├── .yarnrc.yml ├── README.md ├── instrumentation.ts ├── integration-tests │ └── http │ │ ├── README.md │ │ └── health.spec.ts ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── vite-env.d.ts │ ├── api │ │ ├── README.md │ │ ├── admin │ │ │ └── custom │ │ │ │ └── route.ts │ │ └── store │ │ │ └── custom │ │ │ └── route.ts │ ├── jobs │ │ └── README.md │ ├── links │ │ └── README.md │ ├── modules │ │ ├── README.md │ │ └── shipstation │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ ├── service.ts │ │ │ └── types.ts │ ├── scripts │ │ ├── README.md │ │ └── seed.ts │ ├── subscribers │ │ └── README.md │ └── workflows │ │ └── README.md ├── tsconfig.json └── yarn.lock ├── stripe-saved-payment ├── README.md ├── medusa │ ├── .env.template │ ├── .gitignore │ ├── .yarnrc.yml │ ├── README.md │ ├── instrumentation.ts │ ├── integration-tests │ │ ├── http │ │ │ └── health.spec.ts │ │ └── setup.js │ ├── jest.config.js │ ├── medusa-config.ts │ ├── package.json │ ├── src │ │ ├── admin │ │ │ ├── tsconfig.json │ │ │ └── vite-env.d.ts │ │ ├── api │ │ │ ├── middlewares.ts │ │ │ └── store │ │ │ │ └── payment-methods │ │ │ │ └── [account_holder_id] │ │ │ │ └── route.ts │ │ └── scripts │ │ │ └── seed.ts │ ├── tsconfig.json │ └── yarn.lock └── storefront │ ├── .env.template │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .yarnrc.yml │ ├── LICENSE │ ├── README.md │ ├── check-env-variables.js │ ├── next-env.d.ts │ ├── next-sitemap.js │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── favicon.ico │ ├── src │ ├── app │ │ ├── [countryCode] │ │ │ ├── (checkout) │ │ │ │ ├── checkout │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── not-found.tsx │ │ │ └── (main) │ │ │ │ ├── account │ │ │ │ ├── @dashboard │ │ │ │ │ ├── addresses │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ ├── orders │ │ │ │ │ │ ├── details │ │ │ │ │ │ │ └── [id] │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── profile │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── @login │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── loading.tsx │ │ │ │ ├── cart │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ │ │ ├── categories │ │ │ │ └── [...category] │ │ │ │ │ └── page.tsx │ │ │ │ ├── collections │ │ │ │ └── [handle] │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ ├── order │ │ │ │ └── [id] │ │ │ │ │ ├── confirmed │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ │ └── transfer │ │ │ │ │ └── [token] │ │ │ │ │ ├── accept │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── decline │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── products │ │ │ │ └── [handle] │ │ │ │ │ └── page.tsx │ │ │ │ └── store │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── not-found.tsx │ │ ├── opengraph-image.jpg │ │ └── twitter-image.jpg │ ├── lib │ │ ├── config.ts │ │ ├── constants.tsx │ │ ├── context │ │ │ └── modal-context.tsx │ │ ├── data │ │ │ ├── cart.ts │ │ │ ├── categories.ts │ │ │ ├── collections.ts │ │ │ ├── cookies.ts │ │ │ ├── customer.ts │ │ │ ├── fulfillment.ts │ │ │ ├── onboarding.ts │ │ │ ├── orders.ts │ │ │ ├── payment.ts │ │ │ ├── products.ts │ │ │ └── regions.ts │ │ ├── hooks │ │ │ ├── use-in-view.tsx │ │ │ └── use-toggle-state.tsx │ │ └── util │ │ │ ├── compare-addresses.ts │ │ │ ├── env.ts │ │ │ ├── get-precentage-diff.ts │ │ │ ├── get-product-price.ts │ │ │ ├── isEmpty.ts │ │ │ ├── medusa-error.ts │ │ │ ├── money.ts │ │ │ ├── product.ts │ │ │ ├── repeat.ts │ │ │ └── sort-products.ts │ ├── middleware.ts │ ├── modules │ │ ├── account │ │ │ ├── components │ │ │ │ ├── account-info │ │ │ │ │ └── index.tsx │ │ │ │ ├── account-nav │ │ │ │ │ └── index.tsx │ │ │ │ ├── address-book │ │ │ │ │ └── index.tsx │ │ │ │ ├── address-card │ │ │ │ │ ├── add-address.tsx │ │ │ │ │ └── edit-address-modal.tsx │ │ │ │ ├── login │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-card │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-overview │ │ │ │ │ └── index.tsx │ │ │ │ ├── overview │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-billing-address │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-email │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-name │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-password │ │ │ │ │ └── index.tsx │ │ │ │ ├── profile-phone │ │ │ │ │ └── index.tsx │ │ │ │ ├── register │ │ │ │ │ └── index.tsx │ │ │ │ └── transfer-request-form │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── account-layout.tsx │ │ │ │ └── login-template.tsx │ │ ├── cart │ │ │ ├── components │ │ │ │ ├── cart-item-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── empty-cart-message │ │ │ │ │ └── index.tsx │ │ │ │ ├── item │ │ │ │ │ └── index.tsx │ │ │ │ └── sign-in-prompt │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── index.tsx │ │ │ │ ├── items.tsx │ │ │ │ ├── preview.tsx │ │ │ │ └── summary.tsx │ │ ├── categories │ │ │ └── templates │ │ │ │ └── index.tsx │ │ ├── checkout │ │ │ ├── components │ │ │ │ ├── address-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── addresses │ │ │ │ │ └── index.tsx │ │ │ │ ├── billing_address │ │ │ │ │ └── index.tsx │ │ │ │ ├── country-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── discount-code │ │ │ │ │ └── index.tsx │ │ │ │ ├── error-message │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-container │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-test │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-wrapper │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── stripe-wrapper.tsx │ │ │ │ ├── payment │ │ │ │ │ └── index.tsx │ │ │ │ ├── review │ │ │ │ │ └── index.tsx │ │ │ │ ├── shipping-address │ │ │ │ │ └── index.tsx │ │ │ │ ├── shipping │ │ │ │ │ └── index.tsx │ │ │ │ └── submit-button │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── checkout-form │ │ │ │ └── index.tsx │ │ │ │ └── checkout-summary │ │ │ │ └── index.tsx │ │ ├── collections │ │ │ └── templates │ │ │ │ └── index.tsx │ │ ├── common │ │ │ ├── components │ │ │ │ ├── cart-totals │ │ │ │ │ └── index.tsx │ │ │ │ ├── checkbox │ │ │ │ │ └── index.tsx │ │ │ │ ├── delete-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── divider │ │ │ │ │ └── index.tsx │ │ │ │ ├── filter-radio-group │ │ │ │ │ └── index.tsx │ │ │ │ ├── input │ │ │ │ │ └── index.tsx │ │ │ │ ├── interactive-link │ │ │ │ │ └── index.tsx │ │ │ │ ├── line-item-options │ │ │ │ │ └── index.tsx │ │ │ │ ├── line-item-price │ │ │ │ │ └── index.tsx │ │ │ │ ├── line-item-unit-price │ │ │ │ │ └── index.tsx │ │ │ │ ├── localized-client-link │ │ │ │ │ └── index.tsx │ │ │ │ ├── modal │ │ │ │ │ └── index.tsx │ │ │ │ ├── native-select │ │ │ │ │ └── index.tsx │ │ │ │ └── radio │ │ │ │ │ └── index.tsx │ │ │ └── icons │ │ │ │ ├── back.tsx │ │ │ │ ├── bancontact.tsx │ │ │ │ ├── chevron-down.tsx │ │ │ │ ├── eye-off.tsx │ │ │ │ ├── eye.tsx │ │ │ │ ├── fast-delivery.tsx │ │ │ │ ├── ideal.tsx │ │ │ │ ├── map-pin.tsx │ │ │ │ ├── medusa.tsx │ │ │ │ ├── nextjs.tsx │ │ │ │ ├── package.tsx │ │ │ │ ├── paypal.tsx │ │ │ │ ├── placeholder-image.tsx │ │ │ │ ├── refresh.tsx │ │ │ │ ├── spinner.tsx │ │ │ │ ├── trash.tsx │ │ │ │ ├── user.tsx │ │ │ │ └── x.tsx │ │ ├── home │ │ │ └── components │ │ │ │ ├── featured-products │ │ │ │ ├── index.tsx │ │ │ │ └── product-rail │ │ │ │ │ └── index.tsx │ │ │ │ └── hero │ │ │ │ └── index.tsx │ │ ├── layout │ │ │ ├── components │ │ │ │ ├── cart-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── cart-dropdown │ │ │ │ │ └── index.tsx │ │ │ │ ├── cart-mismatch-banner │ │ │ │ │ └── index.tsx │ │ │ │ ├── country-select │ │ │ │ │ └── index.tsx │ │ │ │ ├── medusa-cta │ │ │ │ │ └── index.tsx │ │ │ │ └── side-menu │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── footer │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── nav │ │ │ │ └── index.tsx │ │ ├── order │ │ │ ├── components │ │ │ │ ├── help │ │ │ │ │ └── index.tsx │ │ │ │ ├── item │ │ │ │ │ └── index.tsx │ │ │ │ ├── items │ │ │ │ │ └── index.tsx │ │ │ │ ├── onboarding-cta │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── order-summary │ │ │ │ │ └── index.tsx │ │ │ │ ├── payment-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── shipping-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── transfer-actions │ │ │ │ │ └── index.tsx │ │ │ │ └── transfer-image │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── order-completed-template.tsx │ │ │ │ └── order-details-template.tsx │ │ ├── products │ │ │ ├── components │ │ │ │ ├── image-gallery │ │ │ │ │ └── index.tsx │ │ │ │ ├── product-actions │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── mobile-actions.tsx │ │ │ │ │ └── option-select.tsx │ │ │ │ ├── product-onboarding-cta │ │ │ │ │ └── index.tsx │ │ │ │ ├── product-preview │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── price.tsx │ │ │ │ ├── product-price │ │ │ │ │ └── index.tsx │ │ │ │ ├── product-tabs │ │ │ │ │ ├── accordion.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── related-products │ │ │ │ │ └── index.tsx │ │ │ │ └── thumbnail │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── index.tsx │ │ │ │ ├── product-actions-wrapper │ │ │ │ └── index.tsx │ │ │ │ └── product-info │ │ │ │ └── index.tsx │ │ ├── shipping │ │ │ └── components │ │ │ │ └── free-shipping-price-nudge │ │ │ │ └── index.tsx │ │ ├── skeletons │ │ │ ├── components │ │ │ │ ├── skeleton-button │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-card-details │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-cart-item │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-cart-totals │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-code-form │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-line-item │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-confirmed-header │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-information │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-items │ │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-summary │ │ │ │ │ └── index.tsx │ │ │ │ └── skeleton-product-preview │ │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ │ ├── skeleton-cart-page │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-order-confirmed │ │ │ │ └── index.tsx │ │ │ │ ├── skeleton-product-grid │ │ │ │ └── index.tsx │ │ │ │ └── skeleton-related-products │ │ │ │ └── index.tsx │ │ └── store │ │ │ ├── components │ │ │ ├── pagination │ │ │ │ └── index.tsx │ │ │ └── refinement-list │ │ │ │ ├── index.tsx │ │ │ │ └── sort-products │ │ │ │ └── index.tsx │ │ │ └── templates │ │ │ ├── index.tsx │ │ │ └── paginated-products.tsx │ ├── styles │ │ └── globals.css │ └── types │ │ ├── global.ts │ │ └── icon.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── yarn.lock ├── subscription ├── .env.template ├── .gitignore ├── .yarnrc.yml ├── README.md ├── jest.config.js ├── medusa-config.ts ├── package.json ├── src │ ├── admin │ │ ├── lib │ │ │ └── sdk.ts │ │ ├── routes │ │ │ └── subscriptions │ │ │ │ ├── [id] │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ ├── tsconfig.json │ │ ├── types │ │ │ └── index.ts │ │ └── vite-env.d.ts │ ├── api │ │ ├── admin │ │ │ └── subscriptions │ │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ ├── middlewares.ts │ │ └── store │ │ │ ├── carts │ │ │ └── [id] │ │ │ │ └── subscribe │ │ │ │ └── route.ts │ │ │ └── customers │ │ │ └── me │ │ │ └── subscriptions │ │ │ ├── [id] │ │ │ └── route.ts │ │ │ └── route.ts │ ├── jobs │ │ ├── create-subscription-orders.ts │ │ └── expire-subscription-orders.ts │ ├── links │ │ ├── subscription-cart.ts │ │ ├── subscription-customer.ts │ │ └── subscription-order.ts │ ├── modules │ │ └── subscription │ │ │ ├── index.ts │ │ │ ├── migrations │ │ │ ├── .snapshot-medusa-subscriptions.json │ │ │ └── Migration20240715075104.ts │ │ │ ├── models │ │ │ └── subscription.ts │ │ │ ├── service.ts │ │ │ └── types │ │ │ └── index.ts │ ├── scripts │ │ └── seed.ts │ └── workflows │ │ ├── create-subscription-order │ │ ├── index.ts │ │ └── steps │ │ │ ├── create-subscription-order.ts │ │ │ ├── get-payment-method.ts │ │ │ └── update-subscription.ts │ │ └── create-subscription │ │ ├── index.ts │ │ └── steps │ │ └── create-subscription.ts ├── tsconfig.json └── yarn.lock └── wishlist-plugin ├── .gitignore ├── README.md ├── package.json ├── src ├── admin │ ├── README.md │ ├── lib │ │ └── sdk.ts │ ├── tsconfig.json │ ├── vite-env.d.ts │ └── widgets │ │ └── product-widget.tsx ├── api │ ├── README.md │ ├── admin │ │ └── products │ │ │ └── [id] │ │ │ └── wishlist │ │ │ └── route.ts │ ├── middlewares.ts │ └── store │ │ ├── customers │ │ └── me │ │ │ └── wishlists │ │ │ ├── items │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ ├── route.ts │ │ │ └── validators.ts │ │ │ ├── route.ts │ │ │ └── share │ │ │ └── route.ts │ │ └── wishlists │ │ └── [token] │ │ └── route.ts ├── jobs │ └── README.md ├── links │ ├── README.md │ ├── wishlist-customer.ts │ ├── wishlist-product.ts │ └── wishlist-sales-channel.ts ├── modules │ ├── README.md │ └── wishlist │ │ ├── index.ts │ │ ├── migrations │ │ ├── .snapshot-medusa-wishlist.json │ │ └── Migration20250123095853.ts │ │ ├── models │ │ ├── wishlist-item.ts │ │ └── wishlist.ts │ │ └── service.ts ├── subscribers │ └── README.md └── workflows │ ├── README.md │ ├── create-wishlist-item.ts │ ├── create-wishlist.ts │ ├── delete-wishlist-item.ts │ └── steps │ ├── create-wishlist-item.ts │ ├── create-wishlist.ts │ ├── delete-wishlist-item.ts │ ├── validate-customer-create-wishlist.ts │ ├── validate-item-in-wishlist.ts │ ├── validate-variant-wishlist.ts │ ├── validate-wishlist-exists.ts │ └── validate-wishlist-sales-channel.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .env.* 4 | !.env.template 5 | .DS_Store 6 | /uploads 7 | node_modules 8 | yarn-error.log 9 | 10 | .idea 11 | .vscode 12 | 13 | coverage 14 | 15 | !src/** 16 | 17 | ./tsconfig.tsbuildinfo 18 | package-lock.json 19 | medusa-db.sql 20 | build 21 | .cache 22 | 23 | .yarn/* 24 | !.yarn/patches 25 | !.yarn/plugins 26 | !.yarn/releases 27 | !.yarn/sdks 28 | !.yarn/versions 29 | 30 | .medusa -------------------------------------------------------------------------------- /abandoned-cart/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-abandoned-cart 9 | POSTGRES_URL= 10 | SENDGRID_API_KEY= 11 | SENDGRID_FROM= 12 | ABANDONED_CART_TEMPLATE_ID= -------------------------------------------------------------------------------- /abandoned-cart/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /abandoned-cart/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /abandoned-cart/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /abandoned-cart/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /abandoned-cart/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /abandoned-cart/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /abandoned-cart/src/api/admin/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ) { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /abandoned-cart/src/api/store/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ) { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /algolia-integration/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-algolia-integration 9 | POSTGRES_URL= 10 | 11 | ALGOLIA_APP_ID= 12 | ALGOLIA_API_KEY= 13 | ALGOLIA_PRODUCT_INDEX_NAME= -------------------------------------------------------------------------------- /algolia-integration/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /algolia-integration/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /algolia-integration/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /algolia-integration/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /algolia-integration/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: import.meta.env.VITE_BACKEND_URL || "/", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /algolia-integration/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /algolia-integration/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /algolia-integration/src/api/admin/algolia/sync/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | import { Modules } from "@medusajs/framework/utils"; 3 | 4 | export async function POST( 5 | req: MedusaRequest, 6 | res: MedusaResponse 7 | ) { 8 | const eventModuleService = req.scope.resolve(Modules.EVENT_BUS) 9 | await eventModuleService.emit({ 10 | name: "algolia.sync", 11 | data: {} 12 | }) 13 | res.send({ 14 | message: "Syncing data to Algolia" 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /algolia-integration/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineMiddlewares, 3 | validateAndTransformBody 4 | } from "@medusajs/framework/http" 5 | import { SearchSchema } from "./store/products/search/route" 6 | 7 | export default defineMiddlewares({ 8 | routes: [ 9 | { 10 | matcher: "/store/products/search", 11 | method: ["POST"], 12 | middlewares: [ 13 | validateAndTransformBody(SearchSchema) 14 | ] 15 | } 16 | ] 17 | }) 18 | -------------------------------------------------------------------------------- /algolia-integration/src/modules/algolia/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import AlgoliaModuleService from "./service" 3 | 4 | export const ALGOLIA_MODULE = "algolia" 5 | 6 | export default Module(ALGOLIA_MODULE, { 7 | service: AlgoliaModuleService, 8 | }) 9 | -------------------------------------------------------------------------------- /algolia-integration/src/subscribers/product-sync.ts: -------------------------------------------------------------------------------- 1 | import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" 2 | import { syncProductsWorkflow } from "../workflows/sync-products" 3 | 4 | export default async function handleProductEvents({ 5 | event: { data }, 6 | container, 7 | }: SubscriberArgs<{ id: string }>) { 8 | await syncProductsWorkflow(container) 9 | .run({ 10 | input: { 11 | filters: { 12 | id: data.id, 13 | }, 14 | }, 15 | }) 16 | } 17 | 18 | export const config: SubscriberConfig = { 19 | event: ["product.created", "product.updated"], 20 | } 21 | -------------------------------------------------------------------------------- /algolia-integration/src/subscribers/product.delete.ts: -------------------------------------------------------------------------------- 1 | import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" 2 | import { deleteProductsFromAlgoliaWorkflow } from "../workflows/delete-products-from-algolia" 3 | 4 | export default async function handleProductDeleted({ 5 | event: { data }, 6 | container, 7 | }: SubscriberArgs<{ id: string }>) { 8 | await deleteProductsFromAlgoliaWorkflow(container) 9 | .run({ 10 | input: { 11 | ids: [data.id] 12 | } 13 | }) 14 | } 15 | 16 | export const config: SubscriberConfig = { 17 | event: "product.deleted", 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /algolia-integration/src/workflows/delete-products-from-algolia.ts: -------------------------------------------------------------------------------- 1 | import { createWorkflow } from "@medusajs/framework/workflows-sdk" 2 | import { deleteProductsFromAlgoliaStep } from "./steps/delete-products-from-algolia" 3 | 4 | type DeleteProductsFromAlgoliaWorkflowInput = { 5 | ids: string[] 6 | } 7 | 8 | export const deleteProductsFromAlgoliaWorkflow = createWorkflow( 9 | "delete-products-from-algolia", 10 | (input: DeleteProductsFromAlgoliaWorkflowInput) => { 11 | deleteProductsFromAlgoliaStep(input) 12 | } 13 | ) -------------------------------------------------------------------------------- /bundled-products/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-bundled-products 9 | POSTGRES_URL= -------------------------------------------------------------------------------- /bundled-products/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /bundled-products/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /bundled-products/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /bundled-products/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /bundled-products/medusa-config.ts: -------------------------------------------------------------------------------- 1 | import { loadEnv, defineConfig } from '@medusajs/framework/utils' 2 | 3 | loadEnv(process.env.NODE_ENV || 'development', process.cwd()) 4 | 5 | module.exports = defineConfig({ 6 | projectConfig: { 7 | databaseUrl: process.env.DATABASE_URL, 8 | http: { 9 | storeCors: process.env.STORE_CORS!, 10 | adminCors: process.env.ADMIN_CORS!, 11 | authCors: process.env.AUTH_CORS!, 12 | jwtSecret: process.env.JWT_SECRET || "supersecret", 13 | cookieSecret: process.env.COOKIE_SECRET || "supersecret", 14 | } 15 | }, 16 | modules: [ 17 | { 18 | resolve: "./src/modules/bundled-product", 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /bundled-products/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: import.meta.env.VITE_BACKEND_URL || "/", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /bundled-products/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /bundled-products/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /bundled-products/src/api/store/carts/[id]/line-item-bundles/[bundle_id]/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | import { removeBundleFromCartWorkflow } from "../../../../../../workflows/remove-bundle-from-cart"; 3 | 4 | export async function DELETE( 5 | req: MedusaRequest, 6 | res: MedusaResponse 7 | ) { 8 | const { result: cart } = await removeBundleFromCartWorkflow(req.scope) 9 | .run({ 10 | input: { 11 | cart_id: req.params.id, 12 | bundle_id: req.params.bundle_id, 13 | } 14 | }) 15 | 16 | res.json({ 17 | cart 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /bundled-products/src/links/bundle-item-product.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils"; 2 | import ProductModule from "@medusajs/medusa/product"; 3 | import BundledProductsModule from "../modules/bundled-product"; 4 | 5 | export default defineLink( 6 | { 7 | linkable: BundledProductsModule.linkable.bundleItem, 8 | isList: true, 9 | }, 10 | ProductModule.linkable.product 11 | ) -------------------------------------------------------------------------------- /bundled-products/src/links/bundle-product.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils"; 2 | import ProductModule from "@medusajs/medusa/product"; 3 | import BundledProductsModule from "../modules/bundled-product"; 4 | 5 | export default defineLink( 6 | BundledProductsModule.linkable.bundle, 7 | ProductModule.linkable.product 8 | ) -------------------------------------------------------------------------------- /bundled-products/src/modules/bundled-product/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import BundledProductsModuleService from "./service" 3 | 4 | export const BUNDLED_PRODUCT_MODULE = "bundledProduct" 5 | 6 | export default Module(BUNDLED_PRODUCT_MODULE, { 7 | service: BundledProductsModuleService, 8 | }) 9 | -------------------------------------------------------------------------------- /bundled-products/src/modules/bundled-product/models/bundle-item.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import { Bundle } from "./bundle" 3 | 4 | export const BundleItem = model.define("bundle_item", { 5 | id: model.id().primaryKey(), 6 | quantity: model.number().default(1), 7 | bundle: model.belongsTo(() => Bundle, { 8 | mappedBy: "items", 9 | }), 10 | }) 11 | -------------------------------------------------------------------------------- /bundled-products/src/modules/bundled-product/models/bundle.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import { BundleItem } from "./bundle-item" 3 | 4 | export const Bundle = model.define("bundle", { 5 | id: model.id().primaryKey(), 6 | title: model.text(), 7 | items: model.hasMany(() => BundleItem, { 8 | mappedBy: "bundle", 9 | }), 10 | }) 11 | -------------------------------------------------------------------------------- /bundled-products/src/modules/bundled-product/service.ts: -------------------------------------------------------------------------------- 1 | import { MedusaService } from "@medusajs/framework/utils" 2 | import { Bundle } from "./models/bundle"; 3 | import { BundleItem } from "./models/bundle-item"; 4 | 5 | export default class BundledProductModuleService extends MedusaService({ 6 | Bundle, 7 | BundleItem, 8 | }) { 9 | } 10 | -------------------------------------------------------------------------------- /bundled-products/static/1745848138941-Camera Bag Stock Picture (1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745848138941-Camera Bag Stock Picture (1).jpg -------------------------------------------------------------------------------- /bundled-products/static/1745848169323-Camera Stock Picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745848169323-Camera Stock Picture.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745848190420-Camera Bag Stock Picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745848190420-Camera Bag Stock Picture.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745848209694-Camera Stock Picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745848209694-Camera Stock Picture.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745852411867-Manfrotto Advanced Shoulder Bag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745852411867-Manfrotto Advanced Shoulder Bag.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745922959236-Camera Stock Picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745922959236-Camera Stock Picture.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745922994433-Camera Bag Stock Picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745922994433-Camera Bag Stock Picture.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745923607199-Manfrotto Advanced Shoulder Bag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745923607199-Manfrotto Advanced Shoulder Bag.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745938409937-Camera Stock Picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745938409937-Camera Stock Picture.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745938431753-Camera Bag Stock Picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745938431753-Camera Bag Stock Picture.jpg -------------------------------------------------------------------------------- /bundled-products/static/1745938962441-Manfrotto Advanced Shoulder Bag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/bundled-products/static/1745938962441-Manfrotto Advanced Shoulder Bag.jpg -------------------------------------------------------------------------------- /custom-item-price/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-custom-item-price 9 | POSTGRES_URL= 10 | 11 | GOLD_API_TOKEN= 12 | GOLD_API_SANDBOX= -------------------------------------------------------------------------------- /custom-item-price/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /custom-item-price/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /custom-item-price/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /custom-item-price/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /custom-item-price/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineMiddlewares, 3 | validateAndTransformBody, 4 | } from "@medusajs/framework/http" 5 | import { StoreAddCartLineItem } from "@medusajs/medusa/api/store/carts/validators" 6 | 7 | export default defineMiddlewares({ 8 | routes: [ 9 | { 10 | matcher: "/store/carts/:id/line-items-metals", 11 | method: "POST", 12 | middlewares: [ 13 | validateAndTransformBody( 14 | StoreAddCartLineItem, 15 | ) 16 | ] 17 | } 18 | ], 19 | }) -------------------------------------------------------------------------------- /custom-item-price/src/api/store/carts/[id]/line-items-metals/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework" 2 | import { HttpTypes } from "@medusajs/framework/types" 3 | import { addCustomToCartWorkflow } from "../../../../../workflows/add-custom-to-cart" 4 | 5 | export const POST = async ( 6 | req: MedusaRequest, 7 | res: MedusaResponse 8 | ) => { 9 | const { id } = req.params 10 | const item = req.validatedBody 11 | 12 | const { result } = await addCustomToCartWorkflow(req.scope) 13 | .run({ 14 | input: { 15 | cart_id: id, 16 | item 17 | } 18 | }) 19 | 20 | res.status(200).json({ cart: result.cart }) 21 | } -------------------------------------------------------------------------------- /custom-item-price/src/modules/metal-prices/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils"; 2 | import MetalPricesModuleService from "./service"; 3 | 4 | export const METAL_PRICES_MODULE = "metalPrices" 5 | 6 | export default Module(METAL_PRICES_MODULE, { 7 | service: MetalPricesModuleService 8 | }) -------------------------------------------------------------------------------- /digital-product/.env.template: -------------------------------------------------------------------------------- 1 | MEDUSA_ADMIN_ONBOARDING_TYPE=default 2 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 3 | ADMIN_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 4 | AUTH_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 5 | REDIS_URL=redis://localhost:6379 6 | JWT_SECRET=supersecret 7 | COOKIE_SECRET=supersecret 8 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 9 | DB_NAME= -------------------------------------------------------------------------------- /digital-product/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | package-lock.json 16 | medusa-db.sql 17 | build 18 | .cache 19 | 20 | .yarn/* 21 | !.yarn/patches 22 | !.yarn/plugins 23 | !.yarn/releases 24 | !.yarn/sdks 25 | !.yarn/versions 26 | static -------------------------------------------------------------------------------- /digital-product/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /digital-product/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /digital-product/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /digital-product/src/admin/types/index.ts: -------------------------------------------------------------------------------- 1 | import { ProductVariantDTO } from "@medusajs/framework/types" 2 | 3 | export enum MediaType { 4 | MAIN = "main", 5 | PREVIEW = "preview" 6 | } 7 | 8 | export type DigitalProductMedia = { 9 | id: string 10 | type: MediaType 11 | fileId: string 12 | mimeType: string 13 | digitalProducts?: DigitalProduct 14 | } 15 | 16 | export type DigitalProduct = { 17 | id: string 18 | name: string 19 | medias?: DigitalProductMedia[] 20 | product_variant?:ProductVariantDTO 21 | } -------------------------------------------------------------------------------- /digital-product/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /digital-product/src/api/store/carts/[id]/complete-digital/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework"; 2 | import createDigitalProductOrderWorkflow from "../../../../../workflows/create-digital-product-order"; 3 | 4 | export const POST = async ( 5 | req: MedusaRequest, 6 | res: MedusaResponse 7 | ) => { 8 | const { result } = await createDigitalProductOrderWorkflow(req.scope) 9 | .run({ 10 | input: { 11 | cart_id: req.params.id 12 | } 13 | }) 14 | 15 | res.json({ 16 | type: "order", 17 | ...result 18 | }) 19 | } -------------------------------------------------------------------------------- /digital-product/src/api/validation-schemas.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AdminCreateProduct 3 | } from "@medusajs/medusa/api/admin/products/validators" 4 | import { z } from "zod" 5 | import { MediaType } from "../modules/digital-product/types" 6 | 7 | export const createDigitalProductsSchema = z.object({ 8 | name: z.string(), 9 | medias: z.array(z.object({ 10 | type: z.nativeEnum(MediaType), 11 | file_id: z.string(), 12 | mime_type: z.string() 13 | })), 14 | product: AdminCreateProduct() 15 | }) 16 | -------------------------------------------------------------------------------- /digital-product/src/links/digital-product-order.ts: -------------------------------------------------------------------------------- 1 | import DigitalProductModule from "../modules/digital-product" 2 | import OrderModule from "@medusajs/medusa/order" 3 | import { defineLink } from "@medusajs/framework/utils" 4 | 5 | export default defineLink( 6 | { 7 | linkable: DigitalProductModule.linkable.digitalProductOrder, 8 | deleteCascade: true 9 | }, 10 | OrderModule.linkable.order 11 | ) 12 | -------------------------------------------------------------------------------- /digital-product/src/links/digital-product-variant.ts: -------------------------------------------------------------------------------- 1 | import DigitalProductModule from "../modules/digital-product" 2 | import ProductModule from "@medusajs/medusa/product" 3 | import { defineLink } from "@medusajs/framework/utils" 4 | 5 | export default defineLink( 6 | { 7 | linkable: DigitalProductModule.linkable.digitalProduct, 8 | deleteCascade: true 9 | }, 10 | ProductModule.linkable.productVariant 11 | ) 12 | -------------------------------------------------------------------------------- /digital-product/src/modules/digital-product-fulfillment/index.ts: -------------------------------------------------------------------------------- 1 | import { ModuleProviderExports } from "@medusajs/framework/types" 2 | import DigitalProductFulfillmentService from "./service" 3 | 4 | const services = [DigitalProductFulfillmentService] 5 | 6 | const providerExport: ModuleProviderExports = { 7 | services, 8 | } 9 | 10 | export default providerExport 11 | -------------------------------------------------------------------------------- /digital-product/src/modules/digital-product/index.ts: -------------------------------------------------------------------------------- 1 | import DigitalProductModuleService from "./service" 2 | import { Module } from "@medusajs/framework/utils" 3 | 4 | export const DIGITAL_PRODUCT_MODULE = "digitalProduct" 5 | 6 | export default Module(DIGITAL_PRODUCT_MODULE, { 7 | service: DigitalProductModuleService, 8 | }) -------------------------------------------------------------------------------- /digital-product/src/modules/digital-product/models/digital-product-media.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import { MediaType } from "../types" 3 | import DigitalProduct from "./digital-product" 4 | 5 | const DigitalProductMedia = model.define("digital_product_media", { 6 | id: model.id().primaryKey(), 7 | type: model.enum(MediaType), 8 | fileId: model.text(), 9 | mimeType: model.text(), 10 | digitalProduct: model.belongsTo(() => DigitalProduct, { 11 | mappedBy: "medias" 12 | }) 13 | }) 14 | 15 | export default DigitalProductMedia -------------------------------------------------------------------------------- /digital-product/src/modules/digital-product/models/digital-product-order.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import { OrderStatus } from "../types" 3 | import DigitalProduct from "./digital-product" 4 | 5 | const DigitalProductOrder = model.define("digital_product_order", { 6 | id: model.id().primaryKey(), 7 | status: model.enum(OrderStatus), 8 | products: model.manyToMany(() => DigitalProduct, { 9 | mappedBy: "orders", 10 | pivotTable: "digitalproduct_digitalproductorders" 11 | }) 12 | }) 13 | 14 | export default DigitalProductOrder -------------------------------------------------------------------------------- /digital-product/src/modules/digital-product/models/digital-product.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import DigitalProductMedia from "./digital-product-media" 3 | import DigitalProductOrder from "./digital-product-order" 4 | 5 | const DigitalProduct = model.define("digital_product", { 6 | id: model.id().primaryKey(), 7 | name: model.text(), 8 | medias: model.hasMany(() => DigitalProductMedia, { 9 | mappedBy: "digitalProduct" 10 | }), 11 | orders: model.manyToMany(() => DigitalProductOrder, { 12 | mappedBy: "products" 13 | }) 14 | }) 15 | .cascades({ 16 | delete: ["medias"] 17 | }) 18 | 19 | export default DigitalProduct -------------------------------------------------------------------------------- /digital-product/src/modules/digital-product/service.ts: -------------------------------------------------------------------------------- 1 | import { MedusaService } from "@medusajs/framework/utils" 2 | import DigitalProduct from "./models/digital-product"; 3 | import DigitalProductOrder from "./models/digital-product-order"; 4 | import DigitalProductMedia from "./models/digital-product-media"; 5 | 6 | class DigitalProductModuleService extends MedusaService({ 7 | DigitalProduct, 8 | DigitalProductMedia, 9 | DigitalProductOrder 10 | }) { 11 | 12 | } 13 | 14 | export default DigitalProductModuleService -------------------------------------------------------------------------------- /digital-product/src/modules/digital-product/types/index.ts: -------------------------------------------------------------------------------- 1 | import { OrderDTO, InferTypeOf } from "@medusajs/framework/types" 2 | import DigitalProductOrder from "../models/digital-product-order" 3 | 4 | export enum MediaType { 5 | MAIN = "main", 6 | PREVIEW = "preview" 7 | } 8 | 9 | export enum OrderStatus { 10 | PENDING = "pending", 11 | SENT = "sent" 12 | } 13 | 14 | export type DigitalProductOrder = InferTypeOf & { 15 | order?: OrderDTO 16 | } -------------------------------------------------------------------------------- /digital-product/src/subscribers/handle-digital-order.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SubscriberArgs, 3 | SubscriberConfig, 4 | } from "@medusajs/framework" 5 | import { fulfillDigitalOrderWorkflow } from "../workflows/fulfill-digital-order" 6 | 7 | async function digitalProductOrderCreatedHandler({ 8 | event: { data }, 9 | container, 10 | }: SubscriberArgs<{ id: string }>) { 11 | await fulfillDigitalOrderWorkflow(container).run({ 12 | input: { 13 | id: data.id 14 | } 15 | }) 16 | } 17 | 18 | export default digitalProductOrderCreatedHandler 19 | 20 | export const config: SubscriberConfig = { 21 | event: "digital_product_order.created", 22 | } -------------------------------------------------------------------------------- /digital-product/src/subscribers/handle-product-deleted.ts: -------------------------------------------------------------------------------- 1 | import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"; 2 | import { 3 | deleteProductDigitalProductsWorkflow 4 | } from "../workflows/delete-product-digital-products"; 5 | 6 | export default async function handleProductDeleted({ 7 | event: { data }, 8 | container, 9 | }: SubscriberArgs<{ id: string }>) { 10 | await deleteProductDigitalProductsWorkflow(container) 11 | .run({ 12 | input: data, 13 | }) 14 | } 15 | 16 | export const config: SubscriberConfig = { 17 | event: "product.deleted", 18 | } -------------------------------------------------------------------------------- /express-checkout-storefront/.env.template: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_MEDUSA_BACKEND_URL= 2 | NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY= -------------------------------------------------------------------------------- /express-checkout-storefront/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | !.env.template 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | -------------------------------------------------------------------------------- /express-checkout-storefront/app/[handle]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from "../../components/Router" 2 | 3 | type Params = { 4 | params: Promise<{ handle: string }> 5 | } 6 | 7 | export default async function ExpressCheckoutPage ({ 8 | params 9 | }: Params) { 10 | const handle = (await params).handle 11 | 12 | return 13 | } -------------------------------------------------------------------------------- /express-checkout-storefront/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/express-checkout-storefront/app/favicon.ico -------------------------------------------------------------------------------- /express-checkout-storefront/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .select { 6 | @apply appearance-none border-none bg-no-repeat pr-4; 7 | background-image: url('data:image/svg+xml,'); 8 | background-size: 16px; 9 | background-position: right top 50%; 10 | background-color: transparent; 11 | } -------------------------------------------------------------------------------- /express-checkout-storefront/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /express-checkout-storefront/lib/price.ts: -------------------------------------------------------------------------------- 1 | export const formatPrice = (amount: number, currency?: string): string => { 2 | return new Intl.NumberFormat("en-US", { 3 | style: "currency", 4 | currency: currency || "usd", 5 | }) 6 | .format(amount) 7 | } -------------------------------------------------------------------------------- /express-checkout-storefront/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export let MEDUSA_BACKEND_URL = "http://localhost:9000" 4 | 5 | if (process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL) { 6 | MEDUSA_BACKEND_URL = process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL 7 | } 8 | 9 | export const sdk = new Medusa({ 10 | baseUrl: MEDUSA_BACKEND_URL, 11 | debug: process.env.NODE_ENV === "development", 12 | publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY, 13 | }) -------------------------------------------------------------------------------- /express-checkout-storefront/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /express-checkout-storefront/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /express-checkout-storefront/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /express-checkout-storefront/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /express-checkout-storefront/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /express-checkout-storefront/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import type { Config } from "tailwindcss"; 3 | 4 | // get the path of the dependency "@medusajs/ui" 5 | const medusaUI = path.join( 6 | path.dirname(require.resolve("@medusajs/ui")), 7 | "**/*.{js,jsx,ts,tsx}" 8 | ) 9 | 10 | export default { 11 | presets: [require("@medusajs/ui-preset")], 12 | content: [ 13 | "./app/**/*.{js,ts,jsx,tsx}", 14 | "./components/**/*.{js,ts,jsx,tsx}", 15 | "./providers/**/*.{js,ts,jsx,tsx}", 16 | medusaUI 17 | ], 18 | darkMode: "class", 19 | theme: { 20 | extend: {}, 21 | }, 22 | plugins: [], 23 | } satisfies Config; 24 | -------------------------------------------------------------------------------- /express-checkout-storefront/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /localization-contentful/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-localization-contentful 9 | POSTGRES_URL= 10 | 11 | # Contentful 12 | CONTENTFUL_MANAGEMNT_ACCESS_TOKEN= 13 | CONTENTFUL_DELIVERY_TOKEN= 14 | CONTENTFUL_SPACE_ID= 15 | CONTENTFUL_ENVIRONMENT=master 16 | CONTENTFUL_WEBHOOK_SECRET= #optional 17 | -------------------------------------------------------------------------------- /localization-contentful/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /localization-contentful/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /localization-contentful/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /localization-contentful/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /localization-contentful/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: import.meta.env.VITE_BACKEND_URL || "/", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /localization-contentful/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /localization-contentful/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /localization-contentful/src/api/admin/contentful/sync/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MedusaRequest, 3 | MedusaResponse, 4 | } from "@medusajs/framework/http" 5 | 6 | export const POST = async ( 7 | req: MedusaRequest, 8 | res: MedusaResponse 9 | ) => { 10 | const eventService = req.scope.resolve("event_bus") 11 | 12 | await eventService.emit({ 13 | name: "products.sync", 14 | data: {}, 15 | }) 16 | 17 | res.status(200).json({ 18 | message: "Products sync triggered successfully", 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /localization-contentful/src/links/product-contentful.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import ProductModule from "@medusajs/medusa/product" 3 | import { CONTENTFUL_MODULE } from "../modules/contentful" 4 | 5 | export default defineLink( 6 | { 7 | linkable: ProductModule.linkable.product, 8 | field: "id" 9 | }, 10 | { 11 | linkable: { 12 | serviceName: CONTENTFUL_MODULE, 13 | alias: "contentful_product", 14 | primaryKey: "product_id" 15 | } 16 | }, 17 | { 18 | readOnly: true 19 | } 20 | ) 21 | -------------------------------------------------------------------------------- /localization-contentful/src/modules/contentful/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import ContentfulModuleService from "./service" 3 | import createContentModelsLoader from "./loader/create-content-models" 4 | 5 | export const CONTENTFUL_MODULE = "contentful" 6 | 7 | export default Module(CONTENTFUL_MODULE, { 8 | service: ContentfulModuleService, 9 | loaders: [ 10 | createContentModelsLoader, 11 | ], 12 | }) 13 | -------------------------------------------------------------------------------- /localization-contentful/src/subscribers/create-product.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type SubscriberConfig, 3 | type SubscriberArgs, 4 | } from "@medusajs/framework" 5 | import { createProductsContentfulWorkflow } from "../workflows/create-products-contentful" 6 | 7 | export default async function handleProductCreate({ 8 | event: { data }, 9 | container, 10 | }: SubscriberArgs<{ id: string }>) { 11 | await createProductsContentfulWorkflow(container) 12 | .run({ 13 | input: { 14 | product_ids: [data.id] 15 | } 16 | }) 17 | 18 | console.log("Product created in Contentful") 19 | } 20 | 21 | export const config: SubscriberConfig = { 22 | event: "product.created" 23 | } 24 | -------------------------------------------------------------------------------- /loyalty-points/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-loyalty-points 9 | POSTGRES_URL= -------------------------------------------------------------------------------- /loyalty-points/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /loyalty-points/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /loyalty-points/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /loyalty-points/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /loyalty-points/medusa-config.ts: -------------------------------------------------------------------------------- 1 | import { loadEnv, defineConfig } from '@medusajs/framework/utils' 2 | 3 | loadEnv(process.env.NODE_ENV || 'development', process.cwd()) 4 | 5 | module.exports = defineConfig({ 6 | projectConfig: { 7 | databaseUrl: process.env.DATABASE_URL, 8 | http: { 9 | storeCors: process.env.STORE_CORS!, 10 | adminCors: process.env.ADMIN_CORS!, 11 | authCors: process.env.AUTH_CORS!, 12 | jwtSecret: process.env.JWT_SECRET || "supersecret", 13 | cookieSecret: process.env.COOKIE_SECRET || "supersecret", 14 | } 15 | }, 16 | modules: [ 17 | { 18 | resolve: "./src/modules/loyalty", 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /loyalty-points/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /loyalty-points/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /loyalty-points/src/api/admin/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ) { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /loyalty-points/src/api/store/customers/me/loyalty-points/route.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AuthenticatedMedusaRequest, 4 | MedusaResponse 5 | } from "@medusajs/framework/http"; 6 | import { LOYALTY_MODULE } from "../../../../../modules/loyalty"; 7 | import LoyaltyModuleService from "../../../../../modules/loyalty/service"; 8 | 9 | export async function GET( 10 | req: AuthenticatedMedusaRequest, 11 | res: MedusaResponse 12 | ) { 13 | const loyaltyModuleService: LoyaltyModuleService = req.scope.resolve( 14 | LOYALTY_MODULE 15 | ) 16 | 17 | const points = await loyaltyModuleService.getPoints( 18 | req.auth_context.actor_id 19 | ) 20 | 21 | res.json({ 22 | points, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /loyalty-points/src/modules/loyalty/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import LoyaltyModuleService from "./service" 3 | 4 | export const LOYALTY_MODULE = "loyalty" 5 | 6 | export default Module(LOYALTY_MODULE, { 7 | service: LoyaltyModuleService, 8 | }) 9 | -------------------------------------------------------------------------------- /loyalty-points/src/modules/loyalty/models/loyalty-point.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | 3 | const LoyaltyPoint = model.define("loyalty_point", { 4 | id: model.id().primaryKey(), 5 | points: model.number().default(0), 6 | customer_id: model.text().unique("IDX_LOYALTY_CUSTOMER_ID"), 7 | }) 8 | 9 | export default LoyaltyPoint 10 | 11 | -------------------------------------------------------------------------------- /loyalty-points/src/subscribers/order-placed.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SubscriberArgs, 3 | SubscriberConfig, 4 | } from "@medusajs/framework" 5 | import { handleOrderPointsWorkflow } from "../workflows/handle-order-points" 6 | 7 | export default async function orderPlacedHandler({ 8 | event: { data }, 9 | container, 10 | }: SubscriberArgs<{ id: string }>) { 11 | await handleOrderPointsWorkflow(container).run({ 12 | input: { 13 | order_id: data.id, 14 | }, 15 | }) 16 | } 17 | 18 | export const config: SubscriberConfig = { 19 | event: "order.placed", 20 | } 21 | -------------------------------------------------------------------------------- /marketplace/.env.template: -------------------------------------------------------------------------------- 1 | MEDUSA_ADMIN_ONBOARDING_TYPE=default 2 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 3 | ADMIN_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 4 | AUTH_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 5 | REDIS_URL=redis://localhost:6379 6 | JWT_SECRET=supersecret 7 | COOKIE_SECRET=supersecret 8 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 9 | DB_NAME=medusa-market -------------------------------------------------------------------------------- /marketplace/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | package-lock.json 16 | medusa-db.sql 17 | build 18 | .cache 19 | 20 | .yarn/* 21 | !.yarn/patches 22 | !.yarn/plugins 23 | !.yarn/releases 24 | !.yarn/sdks 25 | !.yarn/versions 26 | -------------------------------------------------------------------------------- /marketplace/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /marketplace/medusa-config.ts: -------------------------------------------------------------------------------- 1 | import { loadEnv, defineConfig } from '@medusajs/framework/utils' 2 | 3 | loadEnv(process.env.NODE_ENV || "development", process.cwd()) 4 | 5 | module.exports = defineConfig({ 6 | projectConfig: { 7 | databaseUrl: process.env.DATABASE_URL, 8 | http: { 9 | storeCors: process.env.STORE_CORS!, 10 | adminCors: process.env.ADMIN_CORS!, 11 | authCors: process.env.AUTH_CORS!, 12 | jwtSecret: process.env.JWT_SECRET || "supersecret", 13 | cookieSecret: process.env.COOKIE_SECRET || "supersecret", 14 | } 15 | }, 16 | modules: [ 17 | { 18 | resolve: "./src/modules/marketplace", 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /marketplace/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /marketplace/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /marketplace/src/api/store/carts/[id]/complete-vendor/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthenticatedMedusaRequest, 3 | MedusaResponse 4 | } from "@medusajs/framework"; 5 | import createVendorOrdersWorkflow from "../../../../../workflows/marketplace/create-vendor-orders"; 6 | 7 | export const POST = async ( 8 | req: AuthenticatedMedusaRequest, 9 | res: MedusaResponse 10 | ) => { 11 | const cartId = req.params.id 12 | 13 | const { result } = await createVendorOrdersWorkflow(req.scope) 14 | .run({ 15 | input: { 16 | cart_id: cartId 17 | } 18 | }) 19 | 20 | res.json({ 21 | type: "order", 22 | order: result.parent_order 23 | }) 24 | } -------------------------------------------------------------------------------- /marketplace/src/api/vendors/admins/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthenticatedMedusaRequest, 3 | MedusaResponse 4 | } from "@medusajs/framework/http" 5 | import { deleteVendorAdminWorkflow } from "../../../../workflows/marketplace/delete-vendor-admin" 6 | 7 | export const DELETE = async ( 8 | req: AuthenticatedMedusaRequest, 9 | res: MedusaResponse 10 | ) => { 11 | await deleteVendorAdminWorkflow(req.scope).run({ 12 | input: { 13 | id: req.params.id 14 | } 15 | }) 16 | 17 | res.json({ message: "success" }) 18 | } -------------------------------------------------------------------------------- /marketplace/src/links/vendor-order.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import MarketplaceModule from "../modules/marketplace" 3 | import OrderModule from "@medusajs/medusa/order" 4 | 5 | export default defineLink( 6 | MarketplaceModule.linkable.vendor, 7 | { 8 | linkable: OrderModule.linkable.order.id, 9 | isList: true 10 | } 11 | ) -------------------------------------------------------------------------------- /marketplace/src/links/vendor-product.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import MarketplaceModule from "../modules/marketplace" 3 | import ProductModule from "@medusajs/medusa/product" 4 | 5 | export default defineLink( 6 | MarketplaceModule.linkable.vendor, 7 | { 8 | linkable: ProductModule.linkable.product.id, 9 | isList: true 10 | } 11 | ) -------------------------------------------------------------------------------- /marketplace/src/modules/marketplace/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import MarketplaceModuleService from "./service" 3 | 4 | export const MARKETPLACE_MODULE = "marketplace" 5 | 6 | export default Module(MARKETPLACE_MODULE, { 7 | service: MarketplaceModuleService 8 | }) -------------------------------------------------------------------------------- /marketplace/src/modules/marketplace/migrations/Migration20250311091542.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '@mikro-orm/migrations'; 2 | 3 | export class Migration20250311091542 extends Migration { 4 | 5 | override async up(): Promise { 6 | this.addSql(`alter table if exists "vendor" drop constraint if exists "vendor_handle_unique";`); 7 | this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_vendor_handle_unique" ON "vendor" (handle) WHERE deleted_at IS NULL;`); 8 | } 9 | 10 | override async down(): Promise { 11 | this.addSql(`drop index if exists "IDX_vendor_handle_unique";`); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /marketplace/src/modules/marketplace/models/vendor-admin.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import Vendor from "./vendor" 3 | 4 | const VendorAdmin = model.define("vendor_admin", { 5 | id: model.id().primaryKey(), 6 | first_name: model.text().nullable(), 7 | last_name: model.text().nullable(), 8 | email: model.text().unique(), 9 | vendor: model.belongsTo(() => Vendor, { 10 | mappedBy: "admins" 11 | }) 12 | }) 13 | 14 | export default VendorAdmin -------------------------------------------------------------------------------- /marketplace/src/modules/marketplace/models/vendor.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import VendorAdmin from "./vendor-admin" 3 | 4 | const Vendor = model.define("vendor", { 5 | id: model.id().primaryKey(), 6 | handle: model.text().unique(), 7 | name: model.text(), 8 | logo: model.text().nullable(), 9 | admins: model.hasMany(() => VendorAdmin) 10 | }) 11 | 12 | export default Vendor -------------------------------------------------------------------------------- /marketplace/src/modules/marketplace/service.ts: -------------------------------------------------------------------------------- 1 | import { MedusaService } from "@medusajs/framework/utils" 2 | import Vendor from "./models/vendor" 3 | import VendorAdmin from "./models/vendor-admin" 4 | 5 | class MarketplaceModuleService extends MedusaService({ 6 | Vendor, 7 | VendorAdmin 8 | }) { 9 | } 10 | 11 | export default MarketplaceModuleService -------------------------------------------------------------------------------- /migrate-from-magento/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /migrate-from-magento/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: import.meta.env.VITE_BACKEND_URL || "/", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) -------------------------------------------------------------------------------- /migrate-from-magento/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /migrate-from-magento/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /migrate-from-magento/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { defineMiddlewares, validateAndTransformBody } from "@medusajs/framework/http"; 2 | import { z } from "zod" 3 | 4 | export const AdminMagentoMigrationsPost = z.object({ 5 | type: z.enum(["category", "product"]).array() 6 | }) 7 | 8 | export default defineMiddlewares({ 9 | routes: [ 10 | { 11 | matcher: "/admin/magento/migrations", 12 | method: "POST", 13 | middlewares: [ 14 | validateAndTransformBody(AdminMagentoMigrationsPost) 15 | ] 16 | } 17 | ] 18 | }) -------------------------------------------------------------------------------- /migrate-from-magento/src/api/store/plugin/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ) { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /migrate-from-magento/src/jobs/migrate-magento.ts: -------------------------------------------------------------------------------- 1 | import { MedusaContainer } from "@medusajs/framework/types" 2 | 3 | export default async function migrateMagentoJob( 4 | container: MedusaContainer 5 | ) { 6 | const eventBusService = container.resolve("event_bus") 7 | 8 | eventBusService.emit({ 9 | name: "migrate.magento", 10 | data: { 11 | type: ["product", "category"], 12 | } 13 | }) 14 | } 15 | 16 | export const config = { 17 | name: "migrate-magento-job", 18 | schedule: "0 0 * * *" 19 | } 20 | -------------------------------------------------------------------------------- /migrate-from-magento/src/modules/magento/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import MagentoModuleService from "./service" 3 | 4 | export const MAGENTO_MODULE = "magento" 5 | 6 | export default Module(MAGENTO_MODULE, { 7 | service: MagentoModuleService, 8 | }) 9 | 10 | -------------------------------------------------------------------------------- /migrate-from-magento/src/workflows/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./migrate-categories-from-magento" 2 | export * from "./migrate-products-from-magento" -------------------------------------------------------------------------------- /migrate-from-magento/src/workflows/steps/get-magento-categories.ts: -------------------------------------------------------------------------------- 1 | import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"; 2 | import MagentoModuleService from "../../modules/magento/service"; 3 | import { MAGENTO_MODULE } from "../../modules/magento"; 4 | 5 | export const getMagentoCategoriesStep = createStep( 6 | { 7 | name: "get-magento-categories", 8 | async: true, 9 | }, 10 | async ({}, { container }) => { 11 | const magentoModuleService: MagentoModuleService = container.resolve(MAGENTO_MODULE) 12 | 13 | const magentoCategories = await magentoModuleService.getCategories() 14 | 15 | return new StepResponse(magentoCategories) 16 | } 17 | ) -------------------------------------------------------------------------------- /phone-auth/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-phone-auth 9 | POSTGRES_URL= 10 | 11 | TWILIO_ACCOUNT_SID= 12 | TWILIO_AUTH_TOKEN= 13 | TWILIO_FROM= -------------------------------------------------------------------------------- /phone-auth/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /phone-auth/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /phone-auth/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /phone-auth/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /phone-auth/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /phone-auth/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /phone-auth/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { defineMiddlewares } from "@medusajs/framework"; 2 | 3 | export default defineMiddlewares({ 4 | routes: [ 5 | { 6 | matcher: "/store/customers/me", 7 | method: ["POST"], 8 | middlewares: [ 9 | async (req, res, next) => { 10 | const { phone } = req.body as Record 11 | 12 | if (phone) { 13 | return res.status(400).json({ 14 | error: "Phone number is not allowed to be updated" 15 | }) 16 | } 17 | 18 | next() 19 | } 20 | ] 21 | } 22 | ] 23 | }) -------------------------------------------------------------------------------- /phone-auth/src/modules/phone-auth/index.ts: -------------------------------------------------------------------------------- 1 | import PhoneAuthService from "./service" 2 | import { 3 | ModuleProvider, 4 | Modules 5 | } from "@medusajs/framework/utils" 6 | 7 | export default ModuleProvider(Modules.AUTH, { 8 | services: [PhoneAuthService], 9 | }) -------------------------------------------------------------------------------- /phone-auth/src/modules/twilio-sms/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModuleProvider, 3 | Modules, 4 | } from "@medusajs/framework/utils" 5 | import TwilioSMSNotificationService from "./service" 6 | 7 | export default ModuleProvider(Modules.NOTIFICATION, { 8 | services: [TwilioSMSNotificationService], 9 | }) 10 | -------------------------------------------------------------------------------- /product-reviews/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-product-reviews 9 | POSTGRES_URL= -------------------------------------------------------------------------------- /product-reviews/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /product-reviews/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /product-reviews/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /product-reviews/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /product-reviews/medusa-config.ts: -------------------------------------------------------------------------------- 1 | import { loadEnv, defineConfig } from '@medusajs/framework/utils' 2 | 3 | loadEnv(process.env.NODE_ENV || 'development', process.cwd()) 4 | 5 | module.exports = defineConfig({ 6 | projectConfig: { 7 | databaseUrl: process.env.DATABASE_URL, 8 | http: { 9 | storeCors: process.env.STORE_CORS!, 10 | adminCors: process.env.ADMIN_CORS!, 11 | authCors: process.env.AUTH_CORS!, 12 | jwtSecret: process.env.JWT_SECRET || "supersecret", 13 | cookieSecret: process.env.COOKIE_SECRET || "supersecret", 14 | } 15 | }, 16 | modules: [ 17 | { 18 | resolve: "./src/modules/product-review" 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /product-reviews/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: "http://localhost:9000", 5 | debug: process.env.NODE_ENV === "development", 6 | auth: { 7 | type: "session", 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /product-reviews/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /product-reviews/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /product-reviews/src/links/review-product.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import ProductReviewModule from "../modules/product-review" 3 | import ProductModule from "@medusajs/medusa/product" 4 | 5 | export default defineLink( 6 | { 7 | linkable: ProductReviewModule.linkable.review, 8 | field: "product_id", 9 | isList: false, 10 | }, 11 | ProductModule.linkable.product, 12 | { 13 | readOnly: true 14 | } 15 | ) -------------------------------------------------------------------------------- /product-reviews/src/modules/product-review/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import ProductReviewModuleService from "./service" 3 | 4 | export const PRODUCT_REVIEW_MODULE = "productReview" 5 | 6 | export default Module(PRODUCT_REVIEW_MODULE, { 7 | service: ProductReviewModuleService, 8 | }) 9 | -------------------------------------------------------------------------------- /product-reviews/src/modules/product-review/models/review.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | 3 | const Review = model.define("review", { 4 | id: model.id().primaryKey(), 5 | title: model.text().nullable(), 6 | content: model.text(), 7 | rating: model.float(), 8 | first_name: model.text(), 9 | last_name: model.text(), 10 | status: model.enum(["pending", "approved", "rejected"]).default("pending"), 11 | product_id: model.text().index("IDX_REVIEW_PRODUCT_ID"), 12 | customer_id: model.text().nullable() 13 | }) 14 | .checks([ 15 | { 16 | name: "rating_range", 17 | expression: (columns) => `${columns.rating} >= 1 AND ${columns.rating} <= 5` 18 | } 19 | ]) 20 | 21 | export default Review 22 | -------------------------------------------------------------------------------- /product-reviews/src/workflows/update-review.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createWorkflow, 3 | WorkflowResponse, 4 | } from "@medusajs/framework/workflows-sdk" 5 | import { updateReviewsStep } from "./steps/update-review" 6 | 7 | export type UpdateReviewInput = { 8 | id: string 9 | status: "pending" | "approved" | "rejected" 10 | }[] 11 | 12 | export const updateReviewWorkflow = createWorkflow( 13 | "update-review", 14 | (input: UpdateReviewInput) => { 15 | const reviews = updateReviewsStep(input) 16 | 17 | return new WorkflowResponse({ 18 | reviews 19 | }) 20 | } 21 | ) 22 | 23 | -------------------------------------------------------------------------------- /quotes-management/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-quotes-management 9 | POSTGRES_URL= -------------------------------------------------------------------------------- /quotes-management/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /quotes-management/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /quotes-management/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /quotes-management/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /quotes-management/medusa-config.ts: -------------------------------------------------------------------------------- 1 | import { loadEnv, defineConfig } from '@medusajs/framework/utils' 2 | 3 | loadEnv(process.env.NODE_ENV || 'development', process.cwd()) 4 | 5 | module.exports = defineConfig({ 6 | projectConfig: { 7 | databaseUrl: process.env.DATABASE_URL, 8 | http: { 9 | storeCors: process.env.STORE_CORS!, 10 | adminCors: process.env.ADMIN_CORS!, 11 | authCors: process.env.AUTH_CORS!, 12 | jwtSecret: process.env.JWT_SECRET || "supersecret", 13 | cookieSecret: process.env.COOKIE_SECRET || "supersecret", 14 | } 15 | }, 16 | modules: [ 17 | { 18 | resolve: "./src/modules/quote", 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /quotes-management/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: import.meta.env.VITE_BACKEND_URL || "/", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) -------------------------------------------------------------------------------- /quotes-management/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /quotes-management/src/admin/utils/format-amount.ts: -------------------------------------------------------------------------------- 1 | export const formatAmount = (amount: number, currency_code: string) => { 2 | return new Intl.NumberFormat("en-US", { 3 | style: "currency", 4 | currency: currency_code, 5 | }).format(amount); 6 | }; 7 | -------------------------------------------------------------------------------- /quotes-management/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /quotes-management/src/api/admin/quotes/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AuthenticatedMedusaRequest, 3 | MedusaResponse, 4 | } from "@medusajs/framework/http"; 5 | import { ContainerRegistrationKeys } from "@medusajs/framework/utils"; 6 | 7 | export const GET = async ( 8 | req: AuthenticatedMedusaRequest, 9 | res: MedusaResponse 10 | ) => { 11 | const query = req.scope.resolve(ContainerRegistrationKeys.QUERY); 12 | const { id } = req.params; 13 | 14 | const { 15 | data: [quote], 16 | } = await query.graph( 17 | { 18 | entity: "quote", 19 | filters: { id }, 20 | fields: req.queryConfig.fields, 21 | }, 22 | { throwIfKeyNotFound: true } 23 | ); 24 | 25 | res.json({ quote }); 26 | }; 27 | -------------------------------------------------------------------------------- /quotes-management/src/api/admin/quotes/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | import { ContainerRegistrationKeys } from "@medusajs/framework/utils"; 3 | 4 | export const GET = async ( 5 | req: MedusaRequest, 6 | res: MedusaResponse 7 | ) => { 8 | const query = req.scope.resolve(ContainerRegistrationKeys.QUERY); 9 | 10 | const { data: quotes, metadata } = await query.graph({ 11 | entity: "quote", 12 | ...req.queryConfig, 13 | }); 14 | 15 | res.json({ 16 | quotes, 17 | count: metadata!.count, 18 | offset: metadata!.skip, 19 | limit: metadata!.take, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /quotes-management/src/api/admin/quotes/validators.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createFindParams, 3 | } from "@medusajs/medusa/api/utils/validators"; 4 | 5 | export const AdminGetQuoteParams = createFindParams({ 6 | limit: 15, 7 | offset: 0, 8 | }) 9 | .strict(); -------------------------------------------------------------------------------- /quotes-management/src/api/store/validators.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createFindParams, 3 | } from "@medusajs/medusa/api/utils/validators"; 4 | import { z } from "zod"; 5 | 6 | export type GetQuoteParamsType = z.infer; 7 | export const GetQuoteParams = createFindParams({ 8 | limit: 15, 9 | offset: 0, 10 | }) 11 | 12 | export type CreateQuoteType = z.infer; 13 | export const CreateQuote = z 14 | .object({ 15 | cart_id: z.string().min(1), 16 | }) 17 | .strict(); -------------------------------------------------------------------------------- /quotes-management/src/links/quote-cart.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import QuoteModule from "../modules/quote" 3 | import CartModule from "@medusajs/medusa/cart" 4 | 5 | export default defineLink( 6 | { 7 | linkable: QuoteModule.linkable.quote.id, 8 | field: "cart_id" 9 | }, 10 | CartModule.linkable.cart, 11 | { 12 | readOnly: true 13 | } 14 | ) -------------------------------------------------------------------------------- /quotes-management/src/links/quote-customer.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import QuoteModule from "../modules/quote" 3 | import CustomerModule from "@medusajs/medusa/customer" 4 | 5 | export default defineLink( 6 | { 7 | linkable: QuoteModule.linkable.quote.id, 8 | field: "customer_id" 9 | }, 10 | CustomerModule.linkable.customer, 11 | { 12 | readOnly: true 13 | } 14 | ) -------------------------------------------------------------------------------- /quotes-management/src/links/quote-order-change.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import QuoteModule from "../modules/quote" 3 | import OrderModule from "@medusajs/medusa/order" 4 | 5 | export default defineLink( 6 | { 7 | linkable: QuoteModule.linkable.quote.id, 8 | field: "order_change_id" 9 | }, 10 | OrderModule.linkable.orderChange, 11 | { 12 | readOnly: true 13 | } 14 | ) -------------------------------------------------------------------------------- /quotes-management/src/links/quote-order.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import QuoteModule from "../modules/quote" 3 | import OrderModule from "@medusajs/medusa/order" 4 | 5 | export default defineLink( 6 | { 7 | linkable: QuoteModule.linkable.quote.id, 8 | field: "draft_order_id" 9 | }, 10 | { 11 | linkable: OrderModule.linkable.order.id, 12 | alias: "draft_order", 13 | }, 14 | { 15 | readOnly: true 16 | } 17 | ) -------------------------------------------------------------------------------- /quotes-management/src/modules/quote/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils"; 2 | import QuoteModuleService from "./service"; 3 | 4 | export const QUOTE_MODULE = "quote"; 5 | 6 | export default Module(QUOTE_MODULE, { 7 | service: QuoteModuleService 8 | }); 9 | -------------------------------------------------------------------------------- /quotes-management/src/modules/quote/models/quote.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils"; 2 | 3 | export enum QuoteStatus { 4 | PENDING_MERCHANT = "pending_merchant", 5 | PENDING_CUSTOMER = "pending_customer", 6 | ACCEPTED = "accepted", 7 | CUSTOMER_REJECTED = "customer_rejected", 8 | MERCHANT_REJECTED = "merchant_rejected", 9 | } 10 | 11 | export const Quote = model.define("quote", { 12 | id: model.id().primaryKey(), 13 | status: model 14 | .enum(Object.values(QuoteStatus)) 15 | .default(QuoteStatus.PENDING_MERCHANT), 16 | customer_id: model.text(), 17 | draft_order_id: model.text(), 18 | order_change_id: model.text(), 19 | cart_id: model.text(), 20 | }); 21 | -------------------------------------------------------------------------------- /quotes-management/src/modules/quote/service.ts: -------------------------------------------------------------------------------- 1 | import { MedusaService } from "@medusajs/framework/utils"; 2 | import { Quote } from "./models/quote"; 3 | 4 | class QuoteModuleService extends MedusaService({ 5 | Quote, 6 | }) {} 7 | 8 | export default QuoteModuleService; 9 | -------------------------------------------------------------------------------- /re-order/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-re-order 9 | POSTGRES_URL= -------------------------------------------------------------------------------- /re-order/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /re-order/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /re-order/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /re-order/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /re-order/medusa-config.ts: -------------------------------------------------------------------------------- 1 | import { loadEnv, defineConfig } from '@medusajs/framework/utils' 2 | 3 | loadEnv(process.env.NODE_ENV || 'development', process.cwd()) 4 | 5 | module.exports = defineConfig({ 6 | projectConfig: { 7 | databaseUrl: process.env.DATABASE_URL, 8 | http: { 9 | storeCors: process.env.STORE_CORS!, 10 | adminCors: process.env.ADMIN_CORS!, 11 | authCors: process.env.AUTH_CORS!, 12 | jwtSecret: process.env.JWT_SECRET || "supersecret", 13 | cookieSecret: process.env.COOKIE_SECRET || "supersecret", 14 | } 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /re-order/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /re-order/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /re-order/src/api/admin/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ) { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /re-order/src/api/store/customers/me/orders/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | import { reorderWorkflow } from "../../../../../../workflows/reorder"; 3 | 4 | export async function POST( 5 | req: AuthenticatedMedusaRequest, 6 | res: MedusaResponse 7 | ) { 8 | const { id } = req.params 9 | 10 | const { result } = await reorderWorkflow(req.scope).run({ 11 | input: { 12 | order_id: id, 13 | }, 14 | }) 15 | 16 | return res.json({ 17 | cart: result, 18 | }) 19 | } -------------------------------------------------------------------------------- /resend-integration/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-v2 9 | POSTGRES_URL= 10 | RESEND_API_KEY= 11 | RESEND_FROM_EMAIL=onboarding@resend.dev -------------------------------------------------------------------------------- /resend-integration/.env.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/resend-integration/.env.test -------------------------------------------------------------------------------- /resend-integration/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /resend-integration/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /resend-integration/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /resend-integration/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /resend-integration/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /resend-integration/src/api/admin/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ): Promise { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /resend-integration/src/api/store/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ): Promise { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /resend-integration/src/modules/resend/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModuleProvider, 3 | Modules 4 | } from "@medusajs/framework/utils" 5 | import ResendNotificationProviderService from "./service" 6 | 7 | export default ModuleProvider(Modules.NOTIFICATION, { 8 | services: [ResendNotificationProviderService], 9 | }) -------------------------------------------------------------------------------- /resend-integration/src/subscribers/order-placed.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SubscriberArgs, 3 | SubscriberConfig, 4 | } from "@medusajs/framework" 5 | import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation" 6 | 7 | export default async function orderPlacedHandler({ 8 | event: { data }, 9 | container, 10 | }: SubscriberArgs<{ id: string }>) { 11 | await sendOrderConfirmationWorkflow(container) 12 | .run({ 13 | input: { 14 | id: data.id 15 | } 16 | }) 17 | } 18 | 19 | export const config: SubscriberConfig = { 20 | event: "order.placed", 21 | } -------------------------------------------------------------------------------- /resend-integration/src/workflows/steps/send-notification.ts: -------------------------------------------------------------------------------- 1 | import { Modules } from "@medusajs/framework/utils" 2 | import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" 3 | import { CreateNotificationDTO } from "@medusajs/framework/types" 4 | 5 | export const sendNotificationStep = createStep( 6 | "send-notification", 7 | async (data: CreateNotificationDTO[], { container }) => { 8 | const notificationModuleService = container.resolve( 9 | Modules.NOTIFICATION 10 | ) 11 | const notification = await notificationModuleService.createNotifications(data) 12 | return new StepResponse(notification) 13 | } 14 | ) -------------------------------------------------------------------------------- /restaurant-marketplace/.env.template: -------------------------------------------------------------------------------- 1 | MEDUSA_ADMIN_ONBOARDING_TYPE=default 2 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 3 | ADMIN_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 4 | AUTH_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 5 | REDIS_URL=redis://localhost:6379 6 | JWT_SECRET=supersecret 7 | COOKIE_SECRET=supersecret 8 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 9 | DB_NAME= -------------------------------------------------------------------------------- /restaurant-marketplace/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | package-lock.json 16 | medusa-db.sql 17 | build 18 | .cache 19 | 20 | .yarn/* 21 | !.yarn/patches 22 | !.yarn/plugins 23 | !.yarn/releases 24 | !.yarn/sdks 25 | !.yarn/versions 26 | -------------------------------------------------------------------------------- /restaurant-marketplace/.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted -------------------------------------------------------------------------------- /restaurant-marketplace/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /restaurant-marketplace/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /restaurant-marketplace/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /restaurant-marketplace/src/api/deliveries/[id]/claim/route.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework"; 2 | import { claimDeliveryWorkflow } from "../../../../workflows/delivery/workflows/claim-delivery"; 3 | 4 | export async function POST(req: AuthenticatedMedusaRequest, res: MedusaResponse) { 5 | const deliveryId = req.params.id; 6 | 7 | const claimedDelivery = await claimDeliveryWorkflow(req.scope).run({ 8 | input: { 9 | driver_id: req.auth_context.actor_id, 10 | delivery_id: deliveryId, 11 | }, 12 | }); 13 | 14 | return res.status(200).json({ delivery: claimedDelivery }); 15 | } 16 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { authenticate, defineMiddlewares } from "@medusajs/medusa"; 2 | import deliveriesMiddlewares from "./deliveries/[id]/middlewares" 3 | 4 | export default defineMiddlewares({ 5 | routes: [ 6 | { 7 | method: ["POST"], 8 | matcher: "/users", 9 | middlewares: [ 10 | authenticate(["driver", "restaurant"], "bearer", { 11 | allowUnregistered: true, 12 | }), 13 | ], 14 | }, 15 | { 16 | method: ["POST", "DELETE"], 17 | matcher: "/restaurants/:id/**", 18 | middlewares: [ 19 | authenticate(["restaurant", "user"], "bearer"), 20 | ], 21 | }, 22 | ...deliveriesMiddlewares.routes 23 | ], 24 | }) -------------------------------------------------------------------------------- /restaurant-marketplace/src/api/restaurants/[id]/admins/[admin_id]/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthenticatedMedusaRequest, 3 | MedusaResponse 4 | } from "@medusajs/framework" 5 | import { 6 | deleteRestaurantAdminWorkflow 7 | } from "../../../../../workflows/restaurant/workflows/delete-restaurant-admin" 8 | 9 | export const DELETE = async ( 10 | req: AuthenticatedMedusaRequest, 11 | res: MedusaResponse 12 | ) => { 13 | await deleteRestaurantAdminWorkflow(req.scope).run({ 14 | input: { 15 | id: req.params.admin_id 16 | } 17 | }) 18 | 19 | res.json({ message: "success" }) 20 | } -------------------------------------------------------------------------------- /restaurant-marketplace/src/api/restaurants/validation-schemas.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const restaurantSchema = z.object({ 4 | name: z.string(), 5 | handle: z.string(), 6 | address: z.string(), 7 | phone: z.string(), 8 | email: z.string(), 9 | image_url: z.string().optional(), 10 | }); -------------------------------------------------------------------------------- /restaurant-marketplace/src/api/store/deliveries/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework" 2 | import { DELIVERY_MODULE } from "../../../../modules/delivery" 3 | import DeliveryModuleService from "../../../../modules/delivery/service" 4 | 5 | export async function GET(req: MedusaRequest, res: MedusaResponse) { 6 | const deliveryModuleService: DeliveryModuleService = req.scope.resolve(DELIVERY_MODULE) 7 | 8 | const delivery = await deliveryModuleService.retrieveDelivery( 9 | req.params.id 10 | ) 11 | 12 | res.json({ 13 | delivery 14 | }) 15 | } -------------------------------------------------------------------------------- /restaurant-marketplace/src/api/users/validation-schemas.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const createUserSchema = z 4 | .object({ 5 | email: z.string().email(), 6 | first_name: z.string(), 7 | last_name: z.string(), 8 | phone: z.string(), 9 | avatar_url: z.string().optional(), 10 | restaurant_id: z.string().optional(), 11 | actor_type: z.ZodEnum.create(["restaurant", "driver"]), 12 | }) -------------------------------------------------------------------------------- /restaurant-marketplace/src/links/delivery-cart.ts: -------------------------------------------------------------------------------- 1 | import DeliveryModule from "../modules/delivery"; 2 | import CartModule from "@medusajs/cart"; 3 | import { defineLink } from "@medusajs/framework/utils"; 4 | 5 | export default defineLink( 6 | DeliveryModule.linkable.delivery, 7 | CartModule.linkable.cart 8 | ); 9 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/links/delivery-order.ts: -------------------------------------------------------------------------------- 1 | import DeliveryModule from "../modules/delivery"; 2 | import OrderModule from "@medusajs/order"; 3 | import { defineLink } from "@medusajs/framework/utils"; 4 | 5 | export default defineLink( 6 | DeliveryModule.linkable.delivery, 7 | OrderModule.linkable.order 8 | ); 9 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/links/restaurant-delivery.ts: -------------------------------------------------------------------------------- 1 | import RestaurantModule from "../modules/restaurant"; 2 | import DeliveryModule from "../modules/delivery"; 3 | import { defineLink } from "@medusajs/framework/utils"; 4 | 5 | export default defineLink( 6 | RestaurantModule.linkable.restaurant, 7 | { 8 | linkable: DeliveryModule.linkable.delivery.id, 9 | isList: true, 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/links/restaurant-products.ts: -------------------------------------------------------------------------------- 1 | import RestaurantModule from "../modules/restaurant"; 2 | import ProductModule from "@medusajs/product"; 3 | import { defineLink } from "@medusajs/framework/utils"; 4 | 5 | export default defineLink( 6 | RestaurantModule.linkable.restaurant, 7 | { 8 | linkable: ProductModule.linkable.product.id, 9 | isList: true, 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/delivery/index.ts: -------------------------------------------------------------------------------- 1 | import Service from "./service"; 2 | import { Module } from "@medusajs/framework/utils"; 3 | 4 | export const DELIVERY_MODULE = "delivery"; 5 | 6 | export default Module(DELIVERY_MODULE, { 7 | service: Service, 8 | }); 9 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/delivery/migrations/Migration20250310081142.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '@mikro-orm/migrations'; 2 | 3 | export class Migration20250310081142 extends Migration { 4 | 5 | override async up(): Promise { 6 | this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_driver_deleted_at" ON "driver" (deleted_at) WHERE deleted_at IS NULL;`); 7 | 8 | this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_delivery_deleted_at" ON "delivery" (deleted_at) WHERE deleted_at IS NULL;`); 9 | } 10 | 11 | override async down(): Promise { 12 | this.addSql(`drop index if exists "IDX_driver_deleted_at";`); 13 | 14 | this.addSql(`drop index if exists "IDX_delivery_deleted_at";`); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/delivery/models/delivery.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils"; 2 | import { DeliveryStatus } from "../types"; 3 | import { Driver } from "./driver"; 4 | 5 | export const Delivery = model.define("delivery", { 6 | id: model 7 | .id() 8 | .primaryKey(), 9 | transaction_id: model.text().nullable(), 10 | delivery_status: model.enum(DeliveryStatus).default(DeliveryStatus.PENDING), 11 | eta: model.dateTime().nullable(), 12 | delivered_at: model.dateTime().nullable(), 13 | driver: model.belongsTo(() => Driver, { 14 | mappedBy: "deliveries" 15 | }).nullable() 16 | }); 17 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/delivery/models/driver.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils"; 2 | import { Delivery } from "./delivery"; 3 | 4 | export const Driver = model.define("driver", { 5 | id: model 6 | .id() 7 | .primaryKey(), 8 | first_name: model.text(), 9 | last_name: model.text(), 10 | email: model.text(), 11 | phone: model.text(), 12 | avatar_url: model.text().nullable(), 13 | deliveries: model.hasMany(() => Delivery, { 14 | mappedBy: "driver" 15 | }) 16 | }); 17 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/delivery/service.ts: -------------------------------------------------------------------------------- 1 | import { MedusaService } from "@medusajs/framework/utils"; 2 | import { Delivery } from "./models/delivery"; 3 | import { Driver } from "./models/driver"; 4 | 5 | class DeliveryModuleService extends MedusaService({ 6 | Delivery, 7 | Driver, 8 | }) {} 9 | 10 | export default DeliveryModuleService; 11 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/restaurant/index.ts: -------------------------------------------------------------------------------- 1 | import Service from "./service"; 2 | import { Module } from "@medusajs/framework/utils"; 3 | 4 | export const RESTAURANT_MODULE = "restaurant"; 5 | 6 | export default Module(RESTAURANT_MODULE, { 7 | service: Service, 8 | }); 9 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/restaurant/models/restaurant-admin.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils"; 2 | import { Restaurant } from "./restaurant"; 3 | 4 | export const RestaurantAdmin = model.define("restaurant_admin", { 5 | id: model 6 | .id() 7 | .primaryKey(), 8 | first_name: model.text(), 9 | last_name: model.text(), 10 | email: model.text(), 11 | avatar_url: model.text().nullable(), 12 | restaurant: model.belongsTo(() => Restaurant, { 13 | mappedBy: "admins" 14 | }), 15 | }); 16 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/restaurant/models/restaurant.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils"; 2 | import { RestaurantAdmin } from "./restaurant-admin"; 3 | 4 | export const Restaurant = model.define("restaurant", { 5 | id: model 6 | .id() 7 | .primaryKey(), 8 | handle: model.text(), 9 | is_open: model.boolean().default(false), 10 | name: model.text(), 11 | description: model.text().nullable(), 12 | phone: model.text(), 13 | email: model.text(), 14 | address: model.text(), 15 | image_url: model.text().nullable(), 16 | admins: model.hasMany(() => RestaurantAdmin) 17 | }); 18 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/restaurant/service.ts: -------------------------------------------------------------------------------- 1 | import { MedusaService } from "@medusajs/framework/utils"; 2 | import { Restaurant } from "./models/restaurant"; 3 | import { RestaurantAdmin } from "./models/restaurant-admin"; 4 | 5 | class RestaurantModuleService extends MedusaService({ 6 | Restaurant, 7 | RestaurantAdmin, 8 | }) {} 9 | 10 | export default RestaurantModuleService; 11 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/modules/restaurant/types/index.ts: -------------------------------------------------------------------------------- 1 | import { InferTypeOf } from "@medusajs/framework/types"; 2 | import RestaurantModuleService from "../service"; 3 | import { Restaurant } from "../models/restaurant"; 4 | 5 | export type CreateRestaurant = Omit< 6 | InferTypeOf, "id" | "admins" 7 | > 8 | 9 | declare module "@medusajs/framework/types" { 10 | export interface ModuleImplementations { 11 | restaurantModuleService: RestaurantModuleService; 12 | } 13 | } -------------------------------------------------------------------------------- /restaurant-marketplace/src/workflows/delivery/steps/await-delivery.ts: -------------------------------------------------------------------------------- 1 | import { createStep } from "@medusajs/framework/workflows-sdk"; 2 | 3 | export const awaitDeliveryStepId = "await-delivery-step"; 4 | export const awaitDeliveryStep = createStep( 5 | { name: awaitDeliveryStepId, async: true, timeout: 60 * 15 }, 6 | async function (_, { container }) { 7 | const logger = container.resolve("logger"); 8 | logger.info("Awaiting delivery by driver..."); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/workflows/delivery/steps/await-driver-claim.ts: -------------------------------------------------------------------------------- 1 | import { createStep } from "@medusajs/framework/workflows-sdk"; 2 | 3 | export const awaitDriverClaimStepId = "await-driver-claim-step"; 4 | export const awaitDriverClaimStep = createStep( 5 | { 6 | name: awaitDriverClaimStepId, 7 | async: true, 8 | timeout: 60 * 15, 9 | maxRetries: 2 10 | }, 11 | async function (_, { container }) { 12 | const logger = container.resolve("logger"); 13 | logger.info("Awaiting driver to claim..."); 14 | } 15 | ); 16 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/workflows/delivery/steps/await-pick-up.ts: -------------------------------------------------------------------------------- 1 | import { createStep } from "@medusajs/framework/workflows-sdk"; 2 | 3 | export const awaitPickUpStepId = "await-pick-up-step"; 4 | export const awaitPickUpStep = createStep( 5 | { name: awaitPickUpStepId, async: true, timeout: 60 * 15 }, 6 | async function (_, { container }) { 7 | const logger = container.resolve("logger"); 8 | logger.info("Awaiting pick up by driver..."); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/workflows/delivery/steps/await-preparation.ts: -------------------------------------------------------------------------------- 1 | import { createStep } from "@medusajs/framework/workflows-sdk"; 2 | 3 | export const awaitPreparationStepId = "await-preparation-step"; 4 | export const awaitPreparationStep = createStep( 5 | { name: awaitPreparationStepId, async: true, timeout: 60 * 15 }, 6 | async function (_, { container }) { 7 | const logger = container.resolve("logger"); 8 | logger.info("Awaiting preparation..."); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/workflows/delivery/steps/await-start-preparation.ts: -------------------------------------------------------------------------------- 1 | import { createStep } from "@medusajs/framework/workflows-sdk"; 2 | 3 | export const awaitStartPreparationStepId = "await-start-preparation-step"; 4 | export const awaitStartPreparationStep = createStep( 5 | { name: awaitStartPreparationStepId, async: true, timeout: 60 * 15 }, 6 | async function (_, { container }) { 7 | const logger = container.resolve("logger"); 8 | logger.info("Awaiting start of preparation..."); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /restaurant-marketplace/src/workflows/restaurant/workflows/create-restaurant.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createWorkflow, 3 | WorkflowResponse, 4 | } from "@medusajs/framework/workflows-sdk"; 5 | import { createRestaurantStep } from "../steps/create-restaurant"; 6 | import { CreateRestaurant } from "../../../modules/restaurant/types"; 7 | 8 | type WorkflowInput = { 9 | restaurant: CreateRestaurant; 10 | }; 11 | 12 | export const createRestaurantWorkflow = createWorkflow( 13 | "create-restaurant-workflow", 14 | function (input: WorkflowInput) { 15 | const restaurant = createRestaurantStep(input.restaurant); 16 | 17 | return new WorkflowResponse(restaurant); 18 | } 19 | ); 20 | -------------------------------------------------------------------------------- /restock-notification/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-restock-notification-2 9 | POSTGRES_URL= -------------------------------------------------------------------------------- /restock-notification/.env.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/restock-notification/.env.test -------------------------------------------------------------------------------- /restock-notification/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /restock-notification/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /restock-notification/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /restock-notification/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /restock-notification/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /restock-notification/src/api/admin/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ): Promise { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /restock-notification/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { authenticate, defineMiddlewares, validateAndTransformBody } from "@medusajs/framework/http"; 2 | import { PostStoreCreateRestockSubscription } from "./store/restock-subscriptions/validators"; 3 | 4 | export default defineMiddlewares({ 5 | routes: [ 6 | { 7 | matcher: "/store/restock-subscriptions", 8 | method: "POST", 9 | middlewares: [ 10 | authenticate("customer", ["bearer", "session"], { 11 | allowUnauthenticated: true 12 | }), 13 | validateAndTransformBody(PostStoreCreateRestockSubscription) 14 | ] 15 | } 16 | ] 17 | }) -------------------------------------------------------------------------------- /restock-notification/src/api/store/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ): Promise { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /restock-notification/src/api/store/restock-subscriptions/validators.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod" 2 | 3 | export const PostStoreCreateRestockSubscription = z.object({ 4 | variant_id: z.string(), 5 | email: z.string().optional(), 6 | sales_channel_id: z.string().optional() 7 | }) -------------------------------------------------------------------------------- /restock-notification/src/jobs/check-restock.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MedusaContainer 3 | } from "@medusajs/framework/types"; 4 | import { sendRestockNotificationsWorkflow } from "../workflows/send-restock-notifications"; 5 | 6 | export default async function myCustomJob(container: MedusaContainer) { 7 | await sendRestockNotificationsWorkflow(container) 8 | .run() 9 | } 10 | 11 | export const config = { 12 | name: "check-restock", 13 | schedule: "0 0 * * *", // For debugging, change to `* * * * *` 14 | }; -------------------------------------------------------------------------------- /restock-notification/src/links/restock-variant.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils"; 2 | import RestockModule from "../modules/restock" 3 | import ProductModule from "@medusajs/medusa/product"; 4 | 5 | export default defineLink( 6 | { 7 | linkable: RestockModule.linkable.restockSubscription.id, 8 | field: "variant_id" 9 | }, 10 | ProductModule.linkable.productVariant, 11 | { 12 | readOnly: true 13 | } 14 | ) -------------------------------------------------------------------------------- /restock-notification/src/modules/restock/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import RestockModuleService from "./service" 3 | 4 | export const RESTOCK_MODULE = "restock" 5 | 6 | export default Module(RESTOCK_MODULE, { 7 | service: RestockModuleService 8 | }) -------------------------------------------------------------------------------- /restock-notification/src/modules/restock/models/restock-subscription.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | 3 | const RestockSubscription = model.define("restock_subscription", { 4 | id: model.id().primaryKey(), 5 | variant_id: model.text(), 6 | sales_channel_id: model.text(), 7 | email: model.text(), 8 | customer_id: model.text().nullable() 9 | }) 10 | .indexes([ 11 | { 12 | on: ["variant_id", "sales_channel_id", "email"], 13 | unique: true 14 | } 15 | ]) 16 | 17 | export default RestockSubscription -------------------------------------------------------------------------------- /restock-notification/src/workflows/send-restock-notifications/steps/get-distinct-subscriptions.ts: -------------------------------------------------------------------------------- 1 | import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"; 2 | import RestockModuleService from "../../../modules/restock/service"; 3 | import { RESTOCK_MODULE } from "../../../modules/restock"; 4 | 5 | export const getDistinctSubscriptionsStep = createStep( 6 | "get-distinct-subscriptions", 7 | async (_, { container }) => { 8 | const restockModuleService: RestockModuleService = container.resolve( 9 | RESTOCK_MODULE 10 | ) 11 | 12 | const distinctSubscriptions = await restockModuleService.getUniqueSubscriptions() 13 | 14 | return new StepResponse(distinctSubscriptions) 15 | } 16 | ) -------------------------------------------------------------------------------- /sanity-integration/medusa/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME= 9 | SANITY_API_TOKEN= 10 | SANITY_PROJECT_ID= -------------------------------------------------------------------------------- /sanity-integration/medusa/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /sanity-integration/medusa/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /sanity-integration/medusa/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /sanity-integration/medusa/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: "http://localhost:9000", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) -------------------------------------------------------------------------------- /sanity-integration/medusa/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /sanity-integration/medusa/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /sanity-integration/medusa/src/api/admin/sanity/documents/[id]/sync/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MedusaRequest, 3 | MedusaResponse 4 | } from "@medusajs/framework/http"; 5 | import { 6 | sanitySyncProductsWorkflow 7 | } from "../../../../../../workflows/sanity-sync-products"; 8 | 9 | export const POST = async (req: MedusaRequest, res: MedusaResponse) => { 10 | const { transaction } = await sanitySyncProductsWorkflow(req.scope) 11 | .run({ 12 | input: { product_ids: [req.params.id] }, 13 | }); 14 | 15 | res.json({ transaction_id: transaction.transactionId }); 16 | }; 17 | -------------------------------------------------------------------------------- /sanity-integration/medusa/src/api/store/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ): Promise { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /sanity-integration/medusa/src/links/product-sanity.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils"; 2 | import ProductModule from "@medusajs/medusa/product"; 3 | import { SANITY_MODULE } from "../modules/sanity"; 4 | 5 | defineLink( 6 | { 7 | linkable: ProductModule.linkable.product.id, 8 | field: "id", 9 | }, 10 | { 11 | linkable: { 12 | serviceName: SANITY_MODULE, 13 | alias: "sanity_product", 14 | primaryKey: "id", 15 | }, 16 | }, 17 | { 18 | readOnly: true, 19 | } 20 | ); -------------------------------------------------------------------------------- /sanity-integration/medusa/src/modules/sanity/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils"; 2 | import SanityModuleService from "./service"; 3 | 4 | export const SANITY_MODULE = "sanity"; 5 | 6 | export default Module(SANITY_MODULE, { 7 | service: SanityModuleService, 8 | }); 9 | -------------------------------------------------------------------------------- /sanity-integration/medusa/src/subscribers/sanity-product-sync.ts: -------------------------------------------------------------------------------- 1 | import type { SubscriberArgs, SubscriberConfig } from "@medusajs/medusa"; 2 | import { sanitySyncProductsWorkflow } from "../workflows/sanity-sync-products"; 3 | 4 | export default async function upsertSanityProduct({ 5 | event: { data }, 6 | container, 7 | }: SubscriberArgs<{ id: string }>) { 8 | await sanitySyncProductsWorkflow(container).run({ 9 | input: { 10 | product_ids: [data.id], 11 | }, 12 | }); 13 | } 14 | 15 | export const config: SubscriberConfig = { 16 | event: ["product.created", "product.updated"], 17 | }; 18 | -------------------------------------------------------------------------------- /sanity-integration/medusa/src/workflows/sanity-sync-products/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createWorkflow, 3 | WorkflowResponse, 4 | } from "@medusajs/framework/workflows-sdk"; 5 | import { syncStep } from "./steps/sync"; 6 | 7 | export type SanitySyncProductsWorkflowInput = { 8 | product_ids?: string[]; 9 | }; 10 | 11 | export const sanitySyncProductsWorkflow = createWorkflow( 12 | { name: "sanity-sync-products", retentionTime: 10000 }, 13 | function (input: SanitySyncProductsWorkflowInput) { 14 | const result = syncStep(input); 15 | 16 | return new WorkflowResponse(result); 17 | }, 18 | ); 19 | -------------------------------------------------------------------------------- /sanity-integration/storefront/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["next/core-web-vitals"] 3 | }; -------------------------------------------------------------------------------- /sanity-integration/storefront/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "semi": false, 4 | "endOfLine": "auto", 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "es5" 8 | } 9 | -------------------------------------------------------------------------------- /sanity-integration/storefront/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/.env.example: -------------------------------------------------------------------------------- 1 | # Need a superuser to reset database 2 | PGHOST=localhost 3 | PGPORT=5432 4 | PGUSER=postgres 5 | PGPASSWORD=password 6 | PGDATABASE=postgres 7 | 8 | # Test database config 9 | TEST_POSTGRES_USER=test_medusa_user 10 | TEST_POSTGRES_DATABASE=test_medusa_db 11 | TEST_POSTGRES_DATABASE_TEMPLATE=test_medusa_db_template 12 | TEST_POSTGRES_HOST=localhost 13 | TEST_POSTGREST_PORT=5432 14 | PRODUCTION_POSTGRES_DATABASE=medusa_db 15 | 16 | # Backend server API 17 | CLIENT_SERVER=http://localhost:9000 18 | MEDUSA_ADMIN_EMAIL=admin@medusa-test.com 19 | MEDUSA_ADMIN_PASSWORD=supersecret -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/fixtures/base/base-modal.ts: -------------------------------------------------------------------------------- 1 | import { Page, Locator } from "@playwright/test" 2 | 3 | export class BaseModal { 4 | page: Page 5 | container: Locator 6 | closeButton: Locator 7 | 8 | constructor(page: Page, container: Locator) { 9 | this.page = page 10 | this.container = container 11 | this.closeButton = this.container.getByTestId("close-modal-button") 12 | } 13 | 14 | async close() { 15 | const button = this.container.getByTestId("close-modal-button") 16 | await button.click() 17 | } 18 | 19 | async isOpen() { 20 | return await this.container.isVisible() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/fixtures/modals/mobile-actions-modal.ts: -------------------------------------------------------------------------------- 1 | import { Page, Locator } from "@playwright/test" 2 | import { BaseModal } from "../base/base-modal" 3 | 4 | export class MobileActionsModal extends BaseModal { 5 | optionButton: Locator 6 | 7 | constructor(page: Page) { 8 | super(page, page.getByTestId("mobile-actions-modal")) 9 | this.optionButton = this.container.getByTestId("option-button") 10 | } 11 | 12 | getOption(option: string) { 13 | return this.optionButton.filter({ 14 | hasText: option, 15 | }) 16 | } 17 | 18 | async selectOption(option: string) { 19 | const optionButton = this.getOption(option) 20 | await optionButton.click() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/fixtures/store-page.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "@playwright/test" 2 | import { CategoryPage } from "./category-page" 3 | 4 | export class StorePage extends CategoryPage { 5 | pageTitle: Locator 6 | 7 | constructor(page: Page) { 8 | super(page) 9 | this.pageTitle = page.getByTestId("store-page-title") 10 | } 11 | 12 | async goto() { 13 | await this.navMenu.open() 14 | await this.navMenu.storeLink.click() 15 | await this.pageTitle.waitFor({ state: "visible" }) 16 | await this.productsListLoader.waitFor({ state: "hidden" }) 17 | } 18 | } -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/index.ts: -------------------------------------------------------------------------------- 1 | import { mergeTests } from "@playwright/test" 2 | import { fixtures } from "./fixtures" 3 | import { accountFixtures } from "./fixtures/account" 4 | 5 | export const test = mergeTests(fixtures, accountFixtures) 6 | export { expect } from "@playwright/test" 7 | -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/tests/global/public-setup.ts: -------------------------------------------------------------------------------- 1 | import { test as setup } from "@playwright/test" 2 | import { seedData } from "../../data/seed" 3 | 4 | setup("Seed data", async () => { 5 | await seedData() 6 | }) 7 | -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/tests/global/teardown.ts: -------------------------------------------------------------------------------- 1 | import { test as teardown } from "@playwright/test" 2 | import { dropTemplate, resetDatabase } from "../../data/reset" 3 | 4 | teardown("Reset the database and the drop the template database", async () => { 5 | await resetDatabase() 6 | await dropTemplate() 7 | }) 8 | -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function getFloatValue(s: string) { 2 | return parseFloat(parseFloat(s).toFixed(2)) 3 | } 4 | 5 | export function compareFloats(f1: number, f2: number) { 6 | const diff = f1 - f2 7 | if (Math.abs(diff) < 0.01) { 8 | return 0 9 | } else if (diff < 0) { 10 | return -1 11 | } else { 12 | return 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sanity-integration/storefront/e2e/utils/locators.ts: -------------------------------------------------------------------------------- 1 | import { Page, Locator} from '@playwright/test' 2 | 3 | export async function getSelectedOptionText(page: Page, select: Locator) { 4 | const handle = await select.elementHandle() 5 | return await page.evaluate( 6 | (opts) => { 7 | if (!opts || !opts[0]) { return "" } 8 | const select = opts[0] as HTMLSelectElement 9 | return select.options[select.selectedIndex].textContent 10 | }, 11 | [handle] 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /sanity-integration/storefront/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /sanity-integration/storefront/next-sitemap.js: -------------------------------------------------------------------------------- 1 | const excludedPaths = ["/checkout", "/account/*"] 2 | 3 | module.exports = { 4 | siteUrl: process.env.NEXT_PUBLIC_VERCEL_URL, 5 | generateRobotsTxt: true, 6 | exclude: excludedPaths + ["/[sitemap]"], 7 | robotsTxtOptions: { 8 | policies: [ 9 | { 10 | userAgent: "*", 11 | allow: "/", 12 | }, 13 | { 14 | userAgent: "*", 15 | disallow: excludedPaths, 16 | }, 17 | ], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /sanity-integration/storefront/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /sanity-integration/storefront/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/sanity-integration/storefront/public/favicon.ico -------------------------------------------------------------------------------- /sanity-integration/storefront/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This configuration file lets you run `$ sanity [command]` in this folder 3 | * Go to https://www.sanity.io/docs/cli to learn more. 4 | **/ 5 | import { defineCliConfig } from 'sanity/cli' 6 | 7 | const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID 8 | const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET 9 | 10 | export default defineCliConfig({ api: { projectId, dataset } }) 11 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/[countryCode]/(main)/account/@dashboard/loading.tsx: -------------------------------------------------------------------------------- 1 | import Spinner from "@modules/common/icons/spinner" 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/[countryCode]/(main)/account/@login/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next" 2 | 3 | import LoginTemplate from "@modules/account/templates/login-template" 4 | 5 | export const metadata: Metadata = { 6 | title: "Sign in", 7 | description: "Sign in to your Medusa Store account.", 8 | } 9 | 10 | export default function Login() { 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/[countryCode]/(main)/account/layout.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveCustomer } from "@lib/data/customer" 2 | import { Toaster } from "@medusajs/ui" 3 | import AccountLayout from "@modules/account/templates/account-layout" 4 | 5 | export default async function AccountPageLayout({ 6 | dashboard, 7 | login, 8 | }: { 9 | dashboard?: React.ReactNode 10 | login?: React.ReactNode 11 | }) { 12 | const customer = await retrieveCustomer().catch(() => null) 13 | 14 | return ( 15 | 16 | {customer ? dashboard : login} 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/[countryCode]/(main)/account/loading.tsx: -------------------------------------------------------------------------------- 1 | import Spinner from "@modules/common/icons/spinner" 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/[countryCode]/(main)/cart/loading.tsx: -------------------------------------------------------------------------------- 1 | import SkeletonCartPage from "@modules/skeletons/templates/skeleton-cart-page" 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/[countryCode]/(main)/cart/page.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveCart } from "@lib/data/cart" 2 | import { retrieveCustomer } from "@lib/data/customer" 3 | import CartTemplate from "@modules/cart/templates" 4 | import { Metadata } from "next" 5 | import { notFound } from "next/navigation" 6 | 7 | export const metadata: Metadata = { 8 | title: "Cart", 9 | description: "View your cart", 10 | } 11 | 12 | export default async function Cart() { 13 | const cart = await retrieveCart() 14 | const customer = await retrieveCustomer() 15 | 16 | if (!cart) { 17 | return notFound() 18 | } 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/[countryCode]/(main)/order/[id]/confirmed/loading.tsx: -------------------------------------------------------------------------------- 1 | import SkeletonOrderConfirmed from "@modules/skeletons/templates/skeleton-order-confirmed" 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { getBaseURL } from "@lib/util/env" 2 | import { Metadata } from "next" 3 | import "styles/globals.css" 4 | 5 | export const metadata: Metadata = { 6 | metadataBase: new URL(getBaseURL()), 7 | } 8 | 9 | export default function RootLayout(props: { children: React.ReactNode }) { 10 | return ( 11 | 12 | 13 |
{props.children}
14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/opengraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/sanity-integration/storefront/src/app/opengraph-image.jpg -------------------------------------------------------------------------------- /sanity-integration/storefront/src/app/twitter-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/sanity-integration/storefront/src/app/twitter-image.jpg -------------------------------------------------------------------------------- /sanity-integration/storefront/src/lib/config.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | // Defaults to standard port for Medusa server 4 | let MEDUSA_BACKEND_URL = "http://localhost:9000" 5 | 6 | if (process.env.MEDUSA_BACKEND_URL) { 7 | MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL 8 | } 9 | 10 | export const sdk = new Medusa({ 11 | baseUrl: MEDUSA_BACKEND_URL, 12 | debug: process.env.NODE_ENV === "development", 13 | publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY, 14 | }) 15 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/lib/data/onboarding.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | import { cookies as nextCookies } from "next/headers" 3 | import { redirect } from "next/navigation" 4 | 5 | export async function resetOnboardingState(orderId: string) { 6 | const cookies = await nextCookies() 7 | cookies.set("_medusa_onboarding", "false", { maxAge: -1 }) 8 | redirect(`http://localhost:7001/a/orders/${orderId}`) 9 | } 10 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/lib/util/compare-addresses.ts: -------------------------------------------------------------------------------- 1 | import { isEqual, pick } from "lodash" 2 | 3 | export default function compareAddresses(address1: any, address2: any) { 4 | return isEqual( 5 | pick(address1, [ 6 | "first_name", 7 | "last_name", 8 | "address_1", 9 | "company", 10 | "postal_code", 11 | "city", 12 | "country_code", 13 | "province", 14 | "phone", 15 | ]), 16 | pick(address2, [ 17 | "first_name", 18 | "last_name", 19 | "address_1", 20 | "company", 21 | "postal_code", 22 | "city", 23 | "country_code", 24 | "province", 25 | "phone", 26 | ]) 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/lib/util/env.ts: -------------------------------------------------------------------------------- 1 | export const getBaseURL = () => { 2 | return process.env.NEXT_PUBLIC_BASE_URL || "https://localhost:8000" 3 | } 4 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/lib/util/get-precentage-diff.ts: -------------------------------------------------------------------------------- 1 | export const getPercentageDiff = (original: number, calculated: number) => { 2 | const diff = original - calculated 3 | const decrease = (diff / original) * 100 4 | 5 | return decrease.toFixed() 6 | } 7 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/lib/util/isEmpty.ts: -------------------------------------------------------------------------------- 1 | export const isObject = (input: any) => input instanceof Object 2 | export const isArray = (input: any) => Array.isArray(input) 3 | export const isEmpty = (input: any) => { 4 | return ( 5 | input === null || 6 | input === undefined || 7 | (isObject(input) && Object.keys(input).length === 0) || 8 | (isArray(input) && (input as any[]).length === 0) || 9 | (typeof input === "string" && input.trim().length === 0) 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/lib/util/repeat.ts: -------------------------------------------------------------------------------- 1 | const repeat = (times: number) => { 2 | return Array.from(Array(times).keys()) 3 | } 4 | 5 | export default repeat 6 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/checkout/components/error-message/index.tsx: -------------------------------------------------------------------------------- 1 | const ErrorMessage = ({ error, 'data-testid': dataTestid }: { error?: string | null, 'data-testid'?: string }) => { 2 | if (!error) { 3 | return null 4 | } 5 | 6 | return ( 7 |
8 | {error} 9 |
10 | ) 11 | } 12 | 13 | export default ErrorMessage 14 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/checkout/components/payment-test/index.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@medusajs/ui" 2 | 3 | const PaymentTest = ({ className }: { className?: string }) => { 4 | return ( 5 | 6 | Attention: For testing purposes 7 | only. 8 | 9 | ) 10 | } 11 | 12 | export default PaymentTest 13 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/common/components/divider/index.tsx: -------------------------------------------------------------------------------- 1 | import { clx } from "@medusajs/ui" 2 | 3 | const Divider = ({ className }: { className?: string }) => ( 4 |
7 | ) 8 | 9 | export default Divider 10 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/common/icons/chevron-down.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { IconProps } from "types/icon" 4 | 5 | const ChevronDown: React.FC = ({ 6 | size = "16", 7 | color = "currentColor", 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 27 | ) 28 | } 29 | 30 | export default ChevronDown 31 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/home/components/featured-products/index.tsx: -------------------------------------------------------------------------------- 1 | import { HttpTypes } from "@medusajs/types" 2 | import ProductRail from "@modules/home/components/featured-products/product-rail" 3 | 4 | export default async function FeaturedProducts({ 5 | collections, 6 | region, 7 | }: { 8 | collections: HttpTypes.StoreCollection[] 9 | region: HttpTypes.StoreRegion 10 | }) { 11 | return collections.map((collection) => ( 12 |
  • 13 | 14 |
  • 15 | )) 16 | } 17 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/layout/components/cart-button/index.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveCart } from "@lib/data/cart" 2 | import CartDropdown from "../cart-dropdown" 3 | 4 | export default async function CartButton() { 5 | const cart = await retrieveCart().catch(() => null) 6 | 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/layout/components/medusa-cta/index.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from "@medusajs/ui" 2 | 3 | import Medusa from "../../../common/icons/medusa" 4 | import NextJs from "../../../common/icons/nextjs" 5 | 6 | const MedusaCTA = () => { 7 | return ( 8 | 9 | Powered by 10 | 11 | 12 | 13 | & 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default MedusaCTA 22 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/layout/templates/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Footer from "@modules/layout/templates/footer" 4 | import Nav from "@modules/layout/templates/nav" 5 | 6 | const Layout: React.FC<{ 7 | children: React.ReactNode 8 | }> = ({ children }) => { 9 | return ( 10 |
    11 |
    15 | ) 16 | } 17 | 18 | export default Layout 19 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/skeletons/components/skeleton-button/index.tsx: -------------------------------------------------------------------------------- 1 | const SkeletonButton = () => { 2 | return
    3 | } 4 | 5 | export default SkeletonButton 6 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/skeletons/components/skeleton-code-form/index.tsx: -------------------------------------------------------------------------------- 1 | const SkeletonCodeForm = () => { 2 | return ( 3 |
    4 |
    5 |
    6 |
    7 |
    8 |
    9 |
    10 | ) 11 | } 12 | 13 | export default SkeletonCodeForm 14 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/skeletons/components/skeleton-order-confirmed-header/index.tsx: -------------------------------------------------------------------------------- 1 | const SkeletonOrderConfirmedHeader = () => { 2 | return ( 3 |
    4 |
    5 |
    6 |
    7 |
    8 |
    9 |
    10 |
    11 | ) 12 | } 13 | 14 | export default SkeletonOrderConfirmedHeader 15 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/skeletons/components/skeleton-order-summary/index.tsx: -------------------------------------------------------------------------------- 1 | import SkeletonButton from "@modules/skeletons/components/skeleton-button" 2 | import SkeletonCartTotals from "@modules/skeletons/components/skeleton-cart-totals" 3 | 4 | const SkeletonOrderSummary = () => { 5 | return ( 6 |
    7 | 8 |
    9 | 10 |
    11 |
    12 | ) 13 | } 14 | 15 | export default SkeletonOrderSummary 16 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/modules/skeletons/components/skeleton-product-preview/index.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from "@medusajs/ui" 2 | 3 | const SkeletonProductPreview = () => { 4 | return ( 5 |
    6 | 7 |
    8 |
    9 |
    10 |
    11 |
    12 | ) 13 | } 14 | 15 | export default SkeletonProductPreview 16 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/sanity/env.ts: -------------------------------------------------------------------------------- 1 | export const apiVersion = 2 | process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-11-18' 3 | 4 | export const dataset = assertValue( 5 | process.env.NEXT_PUBLIC_SANITY_DATASET, 6 | 'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET' 7 | ) 8 | 9 | export const projectId = assertValue( 10 | process.env.NEXT_PUBLIC_SANITY_PROJECT_ID, 11 | 'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID' 12 | ) 13 | 14 | function assertValue(v: T | undefined, errorMessage: string): T { 15 | if (v === undefined) { 16 | throw new Error(errorMessage) 17 | } 18 | 19 | return v 20 | } 21 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/sanity/lib/client.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'next-sanity' 2 | 3 | import { apiVersion, dataset, projectId } from '../env' 4 | 5 | export const client = createClient({ 6 | projectId, 7 | dataset, 8 | apiVersion, 9 | useCdn: true, // Set to false if statically generating pages, using ISR or tag-based revalidation 10 | }) 11 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/sanity/lib/image.ts: -------------------------------------------------------------------------------- 1 | import createImageUrlBuilder from '@sanity/image-url' 2 | import { SanityImageSource } from "@sanity/image-url/lib/types/types"; 3 | 4 | import { dataset, projectId } from '../env' 5 | 6 | // https://www.sanity.io/docs/image-url 7 | const builder = createImageUrlBuilder({ projectId, dataset }) 8 | 9 | export const urlFor = (source: SanityImageSource) => { 10 | return builder.image(source) 11 | } 12 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/sanity/lib/live.ts: -------------------------------------------------------------------------------- 1 | // Querying with "sanityFetch" will keep content automatically updated 2 | // Before using it, import and render "" in your layout, see 3 | // https://github.com/sanity-io/next-sanity#live-content-api for more information. 4 | import { defineLive } from "next-sanity"; 5 | import { client } from './client' 6 | 7 | export const { sanityFetch, SanityLive } = defineLive({ 8 | client: client.withConfig({ 9 | // Live content is currently only available on the experimental API 10 | // https://www.sanity.io/docs/api-versioning 11 | apiVersion: 'vX' 12 | }) 13 | }); 14 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/sanity/schemaTypes/index.ts: -------------------------------------------------------------------------------- 1 | import { SchemaPluginOptions } from 'sanity' 2 | import productSchema from './documents/product' 3 | 4 | export const schema: SchemaPluginOptions = { 5 | types: [productSchema], 6 | templates: (templates) => templates.filter( 7 | (template) => template.schemaType !== "product" 8 | ), 9 | } 10 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/sanity/structure.ts: -------------------------------------------------------------------------------- 1 | import type {StructureResolver} from 'sanity/structure' 2 | 3 | // https://www.sanity.io/docs/structure-builder-cheat-sheet 4 | export const structure: StructureResolver = (S) => 5 | S.list() 6 | .title('Content') 7 | .items(S.documentTypeListItems()) 8 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/types/global.ts: -------------------------------------------------------------------------------- 1 | import { StorePrice } from "@medusajs/types" 2 | 3 | export type FeaturedProduct = { 4 | id: string 5 | title: string 6 | handle: string 7 | thumbnail?: string 8 | } 9 | 10 | export type VariantPrice = { 11 | calculated_price_number: number 12 | calculated_price: string 13 | original_price_number: number 14 | original_price: string 15 | currency_code: string 16 | price_type: string 17 | percentage_diff: string 18 | } 19 | 20 | export type StoreFreeShippingPrice = StorePrice & { 21 | target_reached: boolean 22 | target_remaining: number 23 | remaining_percentage: number 24 | } 25 | -------------------------------------------------------------------------------- /sanity-integration/storefront/src/types/icon.ts: -------------------------------------------------------------------------------- 1 | export type IconProps = { 2 | color?: string 3 | size?: string | number 4 | } & React.SVGAttributes 5 | -------------------------------------------------------------------------------- /segment-integration/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-segment-integration 9 | POSTGRES_URL= 10 | 11 | SEGMENT_WRITE_KEY= -------------------------------------------------------------------------------- /segment-integration/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /segment-integration/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /segment-integration/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /segment-integration/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /segment-integration/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /segment-integration/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /segment-integration/src/api/admin/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ) { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /segment-integration/src/modules/segment/index.ts: -------------------------------------------------------------------------------- 1 | import SegmentAnalyticsProviderService from "./service" 2 | import { 3 | ModuleProvider, 4 | Modules 5 | } from "@medusajs/framework/utils" 6 | 7 | export default ModuleProvider(Modules.ANALYTICS, { 8 | services: [SegmentAnalyticsProviderService], 9 | }) -------------------------------------------------------------------------------- /segment-integration/src/subscribers/order-placed.ts: -------------------------------------------------------------------------------- 1 | import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" 2 | import { trackOrderPlacedWorkflow } from "../workflows/track-order-placed" 3 | 4 | export default async function orderPlacedHandler({ 5 | event: { data }, 6 | container, 7 | }: SubscriberArgs<{ id: string }>) { 8 | await trackOrderPlacedWorkflow(container) 9 | .run({ 10 | input: { 11 | id: data.id, 12 | }, 13 | }) 14 | } 15 | 16 | export const config: SubscriberConfig = { 17 | event: "order.placed", 18 | } 19 | -------------------------------------------------------------------------------- /shipstation-integration/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-shipstation 9 | POSTGRES_URL= 10 | SHIPSTATION_API_KEY= -------------------------------------------------------------------------------- /shipstation-integration/.env.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/shipstation-integration/.env.test -------------------------------------------------------------------------------- /shipstation-integration/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /shipstation-integration/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /shipstation-integration/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /shipstation-integration/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /shipstation-integration/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /shipstation-integration/src/api/admin/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ): Promise { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /shipstation-integration/src/api/store/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"; 2 | 3 | export async function GET( 4 | req: MedusaRequest, 5 | res: MedusaResponse 6 | ): Promise { 7 | res.sendStatus(200); 8 | } 9 | -------------------------------------------------------------------------------- /shipstation-integration/src/modules/shipstation/index.ts: -------------------------------------------------------------------------------- 1 | import ShipStationProviderService from "./service" 2 | import { 3 | ModuleProvider, 4 | Modules 5 | } from "@medusajs/framework/utils" 6 | 7 | export default ModuleProvider(Modules.FULFILLMENT, { 8 | services: [ShipStationProviderService], 9 | }) -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/.env.template: -------------------------------------------------------------------------------- 1 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 2 | ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 3 | AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com 4 | REDIS_URL=redis://localhost:6379 5 | JWT_SECRET=supersecret 6 | COOKIE_SECRET=supersecret 7 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 8 | DB_NAME=medusa-stripe-saved-payment 9 | POSTGRES_URL= 10 | 11 | STRIPE_API_KEY= -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/integration-tests/http/health.spec.ts: -------------------------------------------------------------------------------- 1 | import { medusaIntegrationTestRunner } from "@medusajs/test-utils" 2 | jest.setTimeout(60 * 1000) 3 | 4 | medusaIntegrationTestRunner({ 5 | inApp: true, 6 | env: {}, 7 | testSuite: ({ api }) => { 8 | describe("Ping", () => { 9 | it("ping the server health endpoint", async () => { 10 | const response = await api.get('/health') 11 | expect(response.status).toEqual(200) 12 | }) 13 | }) 14 | }, 15 | }) -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/integration-tests/setup.js: -------------------------------------------------------------------------------- 1 | const { MetadataStorage } = require("@mikro-orm/core") 2 | 3 | MetadataStorage.clear() -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /stripe-saved-payment/medusa/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { authenticate, defineMiddlewares } from "@medusajs/framework/http"; 2 | 3 | export default defineMiddlewares({ 4 | routes: [ 5 | { 6 | matcher: "/store/payment-methods/:provider_id/:account_holder_id", 7 | method: "GET", 8 | middlewares: [ 9 | authenticate("customer", ["bearer", "session"]) 10 | ] 11 | } 12 | ] 13 | }) -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["next/core-web-vitals"] 3 | }; -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "semi": false, 4 | "endOfLine": "auto", 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "es5" 8 | } 9 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | nodeLinker: node-modules 6 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/next-sitemap.js: -------------------------------------------------------------------------------- 1 | const excludedPaths = ["/checkout", "/account/*"] 2 | 3 | module.exports = { 4 | siteUrl: process.env.NEXT_PUBLIC_VERCEL_URL, 5 | generateRobotsTxt: true, 6 | exclude: excludedPaths + ["/[sitemap]"], 7 | robotsTxtOptions: { 8 | policies: [ 9 | { 10 | userAgent: "*", 11 | allow: "/", 12 | }, 13 | { 14 | userAgent: "*", 15 | disallow: excludedPaths, 16 | }, 17 | ], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/stripe-saved-payment/storefront/public/favicon.ico -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/[countryCode]/(main)/account/@dashboard/loading.tsx: -------------------------------------------------------------------------------- 1 | import Spinner from "@modules/common/icons/spinner" 2 | 3 | export default function Loading() { 4 | return ( 5 |
    6 | 7 |
    8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/[countryCode]/(main)/account/@login/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next" 2 | 3 | import LoginTemplate from "@modules/account/templates/login-template" 4 | 5 | export const metadata: Metadata = { 6 | title: "Sign in", 7 | description: "Sign in to your Medusa Store account.", 8 | } 9 | 10 | export default function Login() { 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/[countryCode]/(main)/account/layout.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveCustomer } from "@lib/data/customer" 2 | import { Toaster } from "@medusajs/ui" 3 | import AccountLayout from "@modules/account/templates/account-layout" 4 | 5 | export default async function AccountPageLayout({ 6 | dashboard, 7 | login, 8 | }: { 9 | dashboard?: React.ReactNode 10 | login?: React.ReactNode 11 | }) { 12 | const customer = await retrieveCustomer().catch(() => null) 13 | 14 | return ( 15 | 16 | {customer ? dashboard : login} 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/[countryCode]/(main)/account/loading.tsx: -------------------------------------------------------------------------------- 1 | import Spinner from "@modules/common/icons/spinner" 2 | 3 | export default function Loading() { 4 | return ( 5 |
    6 | 7 |
    8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/[countryCode]/(main)/cart/loading.tsx: -------------------------------------------------------------------------------- 1 | import SkeletonCartPage from "@modules/skeletons/templates/skeleton-cart-page" 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/[countryCode]/(main)/cart/page.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveCart } from "@lib/data/cart" 2 | import { retrieveCustomer } from "@lib/data/customer" 3 | import CartTemplate from "@modules/cart/templates" 4 | import { Metadata } from "next" 5 | import { notFound } from "next/navigation" 6 | 7 | export const metadata: Metadata = { 8 | title: "Cart", 9 | description: "View your cart", 10 | } 11 | 12 | export default async function Cart() { 13 | const cart = await retrieveCart().catch((error) => { 14 | console.error(error) 15 | return notFound() 16 | }) 17 | 18 | const customer = await retrieveCustomer() 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/[countryCode]/(main)/order/[id]/confirmed/loading.tsx: -------------------------------------------------------------------------------- 1 | import SkeletonOrderConfirmed from "@modules/skeletons/templates/skeleton-order-confirmed" 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { getBaseURL } from "@lib/util/env" 2 | import { Metadata } from "next" 3 | import "styles/globals.css" 4 | 5 | export const metadata: Metadata = { 6 | metadataBase: new URL(getBaseURL()), 7 | } 8 | 9 | export default function RootLayout(props: { children: React.ReactNode }) { 10 | return ( 11 | 12 | 13 |
    {props.children}
    14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/opengraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/stripe-saved-payment/storefront/src/app/opengraph-image.jpg -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/app/twitter-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medusajs/examples/7a3102ddf3e17f9f833f1843d2f0ba77d910c468/stripe-saved-payment/storefront/src/app/twitter-image.jpg -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/config.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | // Defaults to standard port for Medusa server 4 | let MEDUSA_BACKEND_URL = "http://localhost:9000" 5 | 6 | if (process.env.MEDUSA_BACKEND_URL) { 7 | MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL 8 | } 9 | 10 | export const sdk = new Medusa({ 11 | baseUrl: MEDUSA_BACKEND_URL, 12 | debug: process.env.NODE_ENV === "development", 13 | publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY, 14 | }) 15 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/data/onboarding.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | import { cookies as nextCookies } from "next/headers" 3 | import { redirect } from "next/navigation" 4 | 5 | export async function resetOnboardingState(orderId: string) { 6 | const cookies = await nextCookies() 7 | cookies.set("_medusa_onboarding", "false", { maxAge: -1 }) 8 | redirect(`http://localhost:7001/a/orders/${orderId}`) 9 | } 10 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/util/compare-addresses.ts: -------------------------------------------------------------------------------- 1 | import { isEqual, pick } from "lodash" 2 | 3 | export default function compareAddresses(address1: any, address2: any) { 4 | return isEqual( 5 | pick(address1, [ 6 | "first_name", 7 | "last_name", 8 | "address_1", 9 | "company", 10 | "postal_code", 11 | "city", 12 | "country_code", 13 | "province", 14 | "phone", 15 | ]), 16 | pick(address2, [ 17 | "first_name", 18 | "last_name", 19 | "address_1", 20 | "company", 21 | "postal_code", 22 | "city", 23 | "country_code", 24 | "province", 25 | "phone", 26 | ]) 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/util/env.ts: -------------------------------------------------------------------------------- 1 | export const getBaseURL = () => { 2 | return process.env.NEXT_PUBLIC_BASE_URL || "https://localhost:8000" 3 | } 4 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/util/get-precentage-diff.ts: -------------------------------------------------------------------------------- 1 | export const getPercentageDiff = (original: number, calculated: number) => { 2 | const diff = original - calculated 3 | const decrease = (diff / original) * 100 4 | 5 | return decrease.toFixed() 6 | } 7 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/util/isEmpty.ts: -------------------------------------------------------------------------------- 1 | export const isObject = (input: any) => input instanceof Object 2 | export const isArray = (input: any) => Array.isArray(input) 3 | export const isEmpty = (input: any) => { 4 | return ( 5 | input === null || 6 | input === undefined || 7 | (isObject(input) && Object.keys(input).length === 0) || 8 | (isArray(input) && (input as any[]).length === 0) || 9 | (typeof input === "string" && input.trim().length === 0) 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/util/product.ts: -------------------------------------------------------------------------------- 1 | import { HttpTypes } from "@medusajs/types"; 2 | 3 | export const isSimpleProduct = (product: HttpTypes.StoreProduct): boolean => { 4 | return product.options?.length === 1 && product.options[0].values?.length === 1; 5 | } -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/lib/util/repeat.ts: -------------------------------------------------------------------------------- 1 | const repeat = (times: number) => { 2 | return Array.from(Array(times).keys()) 3 | } 4 | 5 | export default repeat 6 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/checkout/components/error-message/index.tsx: -------------------------------------------------------------------------------- 1 | const ErrorMessage = ({ error, 'data-testid': dataTestid }: { error?: string | null, 'data-testid'?: string }) => { 2 | if (!error) { 3 | return null 4 | } 5 | 6 | return ( 7 |
    8 | {error} 9 |
    10 | ) 11 | } 12 | 13 | export default ErrorMessage 14 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/checkout/components/payment-test/index.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@medusajs/ui" 2 | 3 | const PaymentTest = ({ className }: { className?: string }) => { 4 | return ( 5 | 6 | Attention: For testing purposes 7 | only. 8 | 9 | ) 10 | } 11 | 12 | export default PaymentTest 13 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/common/components/divider/index.tsx: -------------------------------------------------------------------------------- 1 | import { clx } from "@medusajs/ui" 2 | 3 | const Divider = ({ className }: { className?: string }) => ( 4 |
    7 | ) 8 | 9 | export default Divider 10 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/home/components/featured-products/index.tsx: -------------------------------------------------------------------------------- 1 | import { HttpTypes } from "@medusajs/types" 2 | import ProductRail from "@modules/home/components/featured-products/product-rail" 3 | 4 | export default async function FeaturedProducts({ 5 | collections, 6 | region, 7 | }: { 8 | collections: HttpTypes.StoreCollection[] 9 | region: HttpTypes.StoreRegion 10 | }) { 11 | return collections.map((collection) => ( 12 |
  • 13 | 14 |
  • 15 | )) 16 | } 17 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/layout/components/cart-button/index.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveCart } from "@lib/data/cart" 2 | import CartDropdown from "../cart-dropdown" 3 | 4 | export default async function CartButton() { 5 | const cart = await retrieveCart().catch(() => null) 6 | 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/layout/components/medusa-cta/index.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from "@medusajs/ui" 2 | 3 | import Medusa from "../../../common/icons/medusa" 4 | import NextJs from "../../../common/icons/nextjs" 5 | 6 | const MedusaCTA = () => { 7 | return ( 8 | 9 | Powered by 10 | 11 | 12 | 13 | & 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default MedusaCTA 22 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/layout/templates/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Footer from "@modules/layout/templates/footer" 4 | import Nav from "@modules/layout/templates/nav" 5 | 6 | const Layout: React.FC<{ 7 | children: React.ReactNode 8 | }> = ({ children }) => { 9 | return ( 10 |
    11 |
    15 | ) 16 | } 17 | 18 | export default Layout 19 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/skeletons/components/skeleton-button/index.tsx: -------------------------------------------------------------------------------- 1 | const SkeletonButton = () => { 2 | return
    3 | } 4 | 5 | export default SkeletonButton 6 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/skeletons/components/skeleton-card-details/index.tsx: -------------------------------------------------------------------------------- 1 | const SkeletonCardDetails = () => { 2 | return ( 3 |
    4 |
    5 |
    6 |
    7 | ) 8 | } 9 | 10 | export default SkeletonCardDetails 11 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/skeletons/components/skeleton-code-form/index.tsx: -------------------------------------------------------------------------------- 1 | const SkeletonCodeForm = () => { 2 | return ( 3 |
    4 |
    5 |
    6 |
    7 |
    8 |
    9 |
    10 | ) 11 | } 12 | 13 | export default SkeletonCodeForm 14 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/skeletons/components/skeleton-order-confirmed-header/index.tsx: -------------------------------------------------------------------------------- 1 | const SkeletonOrderConfirmedHeader = () => { 2 | return ( 3 |
    4 |
    5 |
    6 |
    7 |
    8 |
    9 |
    10 |
    11 | ) 12 | } 13 | 14 | export default SkeletonOrderConfirmedHeader 15 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/skeletons/components/skeleton-order-summary/index.tsx: -------------------------------------------------------------------------------- 1 | import SkeletonButton from "@modules/skeletons/components/skeleton-button" 2 | import SkeletonCartTotals from "@modules/skeletons/components/skeleton-cart-totals" 3 | 4 | const SkeletonOrderSummary = () => { 5 | return ( 6 |
    7 | 8 |
    9 | 10 |
    11 |
    12 | ) 13 | } 14 | 15 | export default SkeletonOrderSummary 16 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/modules/skeletons/components/skeleton-product-preview/index.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from "@medusajs/ui" 2 | 3 | const SkeletonProductPreview = () => { 4 | return ( 5 |
    6 | 7 |
    8 |
    9 |
    10 |
    11 |
    12 | ) 13 | } 14 | 15 | export default SkeletonProductPreview 16 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/types/global.ts: -------------------------------------------------------------------------------- 1 | import { StorePrice } from "@medusajs/types" 2 | 3 | export type FeaturedProduct = { 4 | id: string 5 | title: string 6 | handle: string 7 | thumbnail?: string 8 | } 9 | 10 | export type VariantPrice = { 11 | calculated_price_number: number 12 | calculated_price: string 13 | original_price_number: number 14 | original_price: string 15 | currency_code: string 16 | price_type: string 17 | percentage_diff: string 18 | } 19 | 20 | export type StoreFreeShippingPrice = StorePrice & { 21 | target_reached: boolean 22 | target_remaining: number 23 | remaining_percentage: number 24 | } 25 | -------------------------------------------------------------------------------- /stripe-saved-payment/storefront/src/types/icon.ts: -------------------------------------------------------------------------------- 1 | export type IconProps = { 2 | color?: string 3 | size?: string | number 4 | } & React.SVGAttributes 5 | -------------------------------------------------------------------------------- /subscription/.env.template: -------------------------------------------------------------------------------- 1 | MEDUSA_ADMIN_ONBOARDING_TYPE=default 2 | STORE_CORS=http://localhost:8000,https://docs.medusajs.com 3 | ADMIN_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 4 | AUTH_CORS=http://localhost:7000,http://localhost:7001,https://docs.medusajs.com 5 | REDIS_URL=redis://localhost:6379 6 | JWT_SECRET=supersecret 7 | COOKIE_SECRET=supersecret 8 | DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary 9 | DB_NAME= 10 | STRIPE_API_KEY= -------------------------------------------------------------------------------- /subscription/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | package-lock.json 16 | medusa-db.sql 17 | build 18 | .cache 19 | 20 | .yarn/* 21 | !.yarn/patches 22 | !.yarn/plugins 23 | !.yarn/releases 24 | !.yarn/sdks 25 | !.yarn/versions 26 | -------------------------------------------------------------------------------- /subscription/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /subscription/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: import.meta.env.VITE_BACKEND_URL || "/", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) -------------------------------------------------------------------------------- /subscription/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /subscription/src/admin/types/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OrderDTO, 3 | CustomerDTO 4 | } from "@medusajs/framework/types" 5 | 6 | export enum SubscriptionStatus { 7 | ACTIVE = "active", 8 | CANCELED = "canceled", 9 | EXPIRED = "expired", 10 | FAILED = "failed" 11 | } 12 | 13 | export enum SubscriptionInterval { 14 | MONTHLY = "monthly", 15 | YEARLY = "yearly" 16 | } 17 | 18 | export type SubscriptionData = { 19 | id: string 20 | status: SubscriptionStatus 21 | interval: SubscriptionInterval 22 | subscription_date: string 23 | last_order_date: string 24 | next_order_date: string | null 25 | expiration_date: string 26 | metadata: Record | null 27 | orders?: OrderDTO[] 28 | customer?: CustomerDTO 29 | } -------------------------------------------------------------------------------- /subscription/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /subscription/src/api/admin/subscriptions/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthenticatedMedusaRequest, 3 | MedusaResponse 4 | } from "@medusajs/framework"; 5 | import { ContainerRegistrationKeys } from "@medusajs/framework/utils" 6 | 7 | export const GET = async ( 8 | req: AuthenticatedMedusaRequest, 9 | res: MedusaResponse 10 | ) => { 11 | const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) 12 | 13 | const { data: [subscription] } = await query.graph({ 14 | entity: "subscription", 15 | fields: [ 16 | "*", 17 | "orders.*", 18 | "customer.*", 19 | ], 20 | filters: { 21 | id: [req.params.id] 22 | } 23 | }) 24 | 25 | res.json({ 26 | subscription 27 | }) 28 | } -------------------------------------------------------------------------------- /subscription/src/api/admin/subscriptions/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthenticatedMedusaRequest, 3 | MedusaResponse 4 | } from "@medusajs/framework"; 5 | import { ContainerRegistrationKeys } from "@medusajs/framework/utils" 6 | 7 | export const GET = async ( 8 | req: AuthenticatedMedusaRequest, 9 | res: MedusaResponse 10 | ) => { 11 | const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) 12 | 13 | const { 14 | data: subscriptions, 15 | metadata: { count, take, skip }, 16 | } = await query.graph({ 17 | entity: "subscription", 18 | ...req.queryConfig, 19 | }) 20 | 21 | res.json({ 22 | subscriptions, 23 | count, 24 | limit: take, 25 | offset: skip 26 | }) 27 | } -------------------------------------------------------------------------------- /subscription/src/api/store/customers/me/subscriptions/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthenticatedMedusaRequest, 3 | MedusaResponse 4 | } from "@medusajs/framework"; 5 | import { ContainerRegistrationKeys } from "@medusajs/framework/utils" 6 | 7 | export const GET = async ( 8 | req: AuthenticatedMedusaRequest, 9 | res: MedusaResponse 10 | ) => { 11 | const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) 12 | 13 | const { data: [customer] } = await query.graph({ 14 | entity: "customer", 15 | fields: [ 16 | "subscriptions.*" 17 | ], 18 | filters: { 19 | id: [req.auth_context.actor_id] 20 | } 21 | }) 22 | 23 | res.json({ 24 | subscriptions: customer.subscriptions 25 | }) 26 | } -------------------------------------------------------------------------------- /subscription/src/links/subscription-cart.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import SubscriptionModule from "../modules/subscription" 3 | import CartModule from "@medusajs/medusa/cart" 4 | 5 | export default defineLink( 6 | SubscriptionModule.linkable.subscription, 7 | CartModule.linkable.cart 8 | ) -------------------------------------------------------------------------------- /subscription/src/links/subscription-customer.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import SubscriptionModule from "../modules/subscription" 3 | import CustomerModule from "@medusajs/medusa/customer" 4 | 5 | export default defineLink( 6 | { 7 | linkable: SubscriptionModule.linkable.subscription.id, 8 | isList: true 9 | }, 10 | CustomerModule.linkable.customer 11 | ) -------------------------------------------------------------------------------- /subscription/src/links/subscription-order.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import SubscriptionModule from "../modules/subscription" 3 | import OrderModule from "@medusajs/medusa/order" 4 | 5 | export default defineLink( 6 | SubscriptionModule.linkable.subscription, 7 | { 8 | linkable: OrderModule.linkable.order.id, 9 | isList: true 10 | } 11 | ) -------------------------------------------------------------------------------- /subscription/src/modules/subscription/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@medusajs/framework/utils" 2 | import SubscriptionModuleService from "./service" 3 | 4 | export const SUBSCRIPTION_MODULE = "subscriptionModuleService" 5 | 6 | export default Module(SUBSCRIPTION_MODULE, { 7 | service: SubscriptionModuleService 8 | }) -------------------------------------------------------------------------------- /subscription/src/modules/subscription/models/subscription.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import { SubscriptionInterval, SubscriptionStatus } from "../types" 3 | 4 | const Subscription = model.define("subscription", { 5 | id: model.id().primaryKey(), 6 | status: model.enum(SubscriptionStatus) 7 | .default(SubscriptionStatus.ACTIVE), 8 | interval: model.enum(SubscriptionInterval), 9 | period: model.number(), 10 | subscription_date: model.dateTime(), 11 | last_order_date: model.dateTime(), 12 | next_order_date: model.dateTime().index().nullable(), 13 | expiration_date: model.dateTime().index(), 14 | metadata: model.json().nullable() 15 | }) 16 | 17 | export default Subscription -------------------------------------------------------------------------------- /subscription/src/modules/subscription/types/index.ts: -------------------------------------------------------------------------------- 1 | import { InferTypeOf } from "@medusajs/framework/types" 2 | import Subscription from "../models/subscription" 3 | 4 | export enum SubscriptionStatus { 5 | ACTIVE = "active", 6 | CANCELED = "canceled", 7 | EXPIRED = "expired", 8 | FAILED = "failed" 9 | } 10 | 11 | export enum SubscriptionInterval { 12 | MONTHLY = "monthly", 13 | YEARLY = "yearly" 14 | } 15 | 16 | export type CreateSubscriptionData = { 17 | interval: SubscriptionInterval 18 | period: number 19 | status?: SubscriptionStatus 20 | subscription_date?: Date 21 | metadata?: Record 22 | } 23 | 24 | export type SubscriptionData = InferTypeOf -------------------------------------------------------------------------------- /wishlist-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | medusa-db.sql 16 | build 17 | .cache 18 | 19 | .yarn/* 20 | !.yarn/patches 21 | !.yarn/plugins 22 | !.yarn/releases 23 | !.yarn/sdks 24 | !.yarn/versions 25 | 26 | .medusa -------------------------------------------------------------------------------- /wishlist-plugin/src/admin/lib/sdk.ts: -------------------------------------------------------------------------------- 1 | import Medusa from "@medusajs/js-sdk" 2 | 3 | export const sdk = new Medusa({ 4 | baseUrl: import.meta.env.VITE_BACKEND_URL || "/", 5 | debug: import.meta.env.DEV, 6 | auth: { 7 | type: "session", 8 | }, 9 | }) -------------------------------------------------------------------------------- /wishlist-plugin/src/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["."] 24 | } -------------------------------------------------------------------------------- /wishlist-plugin/src/admin/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /wishlist-plugin/src/api/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineMiddlewares, 3 | validateAndTransformBody, 4 | } from "@medusajs/framework/http" 5 | import { PostStoreCreateWishlistItem } from "./store/customers/me/wishlists/items/validators" 6 | 7 | export default defineMiddlewares({ 8 | routes: [ 9 | { 10 | matcher: "/store/customers/me/wishlists/items", 11 | method: "POST", 12 | middlewares: [ 13 | validateAndTransformBody(PostStoreCreateWishlistItem), 14 | ], 15 | }, 16 | ], 17 | }) -------------------------------------------------------------------------------- /wishlist-plugin/src/api/store/customers/me/wishlists/items/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework"; 2 | import { deleteWishlistItemWorkflow } from "../../../../../../../workflows/delete-wishlist-item"; 3 | 4 | export async function DELETE( 5 | req: AuthenticatedMedusaRequest, 6 | res: MedusaResponse 7 | ) { 8 | const { result } = await deleteWishlistItemWorkflow(req.scope) 9 | .run({ 10 | input: { 11 | wishlist_item_id: req.params.id, 12 | customer_id: req.auth_context.actor_id 13 | } 14 | }) 15 | 16 | res.json({ 17 | wishlist: result.wishlist 18 | }) 19 | } -------------------------------------------------------------------------------- /wishlist-plugin/src/api/store/customers/me/wishlists/items/validators.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod" 2 | 3 | export const PostStoreCreateWishlistItem = z.object({ 4 | variant_id: z.string(), 5 | }) -------------------------------------------------------------------------------- /wishlist-plugin/src/links/wishlist-customer.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import WishlistModule from "../modules/wishlist" 3 | import CustomerModule from "@medusajs/medusa/customer" 4 | 5 | export default defineLink( 6 | { 7 | linkable: WishlistModule.linkable.wishlist.id, 8 | field: "customer_id" 9 | }, 10 | CustomerModule.linkable.customer.id, 11 | { 12 | readOnly: true 13 | } 14 | ) -------------------------------------------------------------------------------- /wishlist-plugin/src/links/wishlist-product.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import WishlistModule from "../modules/wishlist" 3 | import ProductModule from "@medusajs/medusa/product" 4 | 5 | export default defineLink( 6 | { 7 | linkable: WishlistModule.linkable.wishlistItem.id, 8 | field: "product_variant_id" 9 | }, 10 | ProductModule.linkable.productVariant, 11 | { 12 | readOnly: true 13 | } 14 | ) -------------------------------------------------------------------------------- /wishlist-plugin/src/links/wishlist-sales-channel.ts: -------------------------------------------------------------------------------- 1 | import { defineLink } from "@medusajs/framework/utils" 2 | import WishlistModule from "../modules/wishlist" 3 | import SalesChannelModule from "@medusajs/medusa/sales-channel" 4 | 5 | export default defineLink( 6 | { 7 | linkable: WishlistModule.linkable.wishlist.id, 8 | field: "sales_channel_id" 9 | }, 10 | SalesChannelModule.linkable.salesChannel, 11 | { 12 | readOnly: true 13 | } 14 | ) -------------------------------------------------------------------------------- /wishlist-plugin/src/modules/wishlist/index.ts: -------------------------------------------------------------------------------- 1 | import WishlistModuleService from "./service" 2 | import { Module } from "@medusajs/framework/utils" 3 | 4 | export const WISHLIST_MODULE = "wishlist" 5 | 6 | export default Module(WISHLIST_MODULE, { 7 | service: WishlistModuleService, 8 | }) -------------------------------------------------------------------------------- /wishlist-plugin/src/modules/wishlist/models/wishlist-item.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import { Wishlist } from "./wishlist" 3 | 4 | export const WishlistItem = model.define("wishlist_item", { 5 | id: model.id().primaryKey(), 6 | product_variant_id: model.text(), 7 | wishlist: model.belongsTo(() => Wishlist, { 8 | mappedBy: "items" 9 | }) 10 | }) 11 | .indexes([ 12 | { 13 | on: ["product_variant_id", "wishlist_id"], 14 | unique: true 15 | } 16 | ]) -------------------------------------------------------------------------------- /wishlist-plugin/src/modules/wishlist/models/wishlist.ts: -------------------------------------------------------------------------------- 1 | import { model } from "@medusajs/framework/utils" 2 | import { WishlistItem } from "./wishlist-item" 3 | 4 | export const Wishlist = model.define("wishlist", { 5 | id: model.id().primaryKey(), 6 | customer_id: model.text(), 7 | sales_channel_id: model.text(), 8 | items: model.hasMany(() => WishlistItem), 9 | }) 10 | .indexes([ 11 | { 12 | on: ["customer_id", "sales_channel_id"], 13 | unique: true 14 | } 15 | ]) -------------------------------------------------------------------------------- /wishlist-plugin/src/workflows/steps/validate-wishlist-exists.ts: -------------------------------------------------------------------------------- 1 | import { MedusaError } from "@medusajs/framework/utils" 2 | import { createStep } from "@medusajs/framework/workflows-sdk" 3 | import { InferTypeOf } from "@medusajs/framework/types" 4 | import { Wishlist } from "../../modules/wishlist/models/wishlist" 5 | 6 | type Input = { 7 | wishlists?: InferTypeOf[] 8 | } 9 | 10 | export const validateWishlistExistsStep = createStep( 11 | "validate-wishlist-exists", 12 | async (input: Input) => { 13 | if (!input.wishlists?.length) { 14 | throw new MedusaError( 15 | MedusaError.Types.NOT_FOUND, 16 | "No wishlist found for this customer" 17 | ) 18 | } 19 | } 20 | ) --------------------------------------------------------------------------------