├── .eslintrc.js ├── .gitignore ├── .prettierrc.json ├── .yarn └── releases │ └── yarn-3.4.1.cjs ├── .yarnrc.yml ├── LICENSE ├── app.json ├── apps ├── create-universal-medusa-app │ ├── .yarn │ │ └── install-state.gz │ ├── format-connection-string.ts │ ├── index.ts │ ├── package.json │ ├── postgres-client.ts │ ├── readme.md │ ├── run.js │ ├── tsconfig.json │ ├── utils │ │ ├── db │ │ │ └── index.ts │ │ └── get-current-os.ts │ └── yarn.lock ├── docs │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── blog │ │ ├── 2019-05-28-first-blog-post.md │ │ ├── 2019-05-29-long-blog-post.md │ │ ├── 2021-08-01-mdx-blog-post.mdx │ │ ├── 2021-08-26-welcome │ │ │ ├── docusaurus-plushie-banner.jpeg │ │ │ └── index.md │ │ └── authors.yml │ ├── docs │ │ ├── Styling │ │ │ ├── _category_.json │ │ │ ├── breakpoints.md │ │ │ ├── design-system.md │ │ │ ├── layouts.md │ │ │ └── tailwind-with-nativewind.md │ │ ├── headless-ecommerce │ │ │ ├── _category_.json │ │ │ └── about-medusa-js.md │ │ ├── intro.md │ │ ├── navigation │ │ │ ├── _category_.json │ │ │ ├── deep-links.md │ │ │ ├── discoverability │ │ │ │ ├── SEO.md │ │ │ │ ├── _category_.json │ │ │ │ └── app-clips.md │ │ │ ├── linking.md │ │ │ ├── routing.md │ │ │ └── use-universal-pathname.md │ │ └── project-structure │ │ │ ├── _category_.json │ │ │ ├── add-new-dependencies.md │ │ │ ├── apps-workspace.md │ │ │ └── packages-app-workspace.md │ ├── docusaurus.config.js │ ├── package.json │ ├── sidebars.js │ ├── src │ │ ├── components │ │ │ └── HomepageFeatures │ │ │ │ ├── index.js │ │ │ │ └── styles.module.css │ │ ├── css │ │ │ └── custom.css │ │ └── pages │ │ │ ├── _index.js │ │ │ ├── index.module.css │ │ │ └── markdown-page.md │ ├── static │ │ ├── .nojekyll │ │ └── img │ │ │ ├── favicon.ico │ │ │ └── logo.svg │ └── tailwind.config.js ├── expo │ ├── .gitignore │ ├── App.tsx │ ├── app-env.d.ts │ ├── app.json │ ├── app │ │ ├── (tabs) │ │ │ ├── (home) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── products │ │ │ │ │ ├── [handle].tsx │ │ │ │ │ └── __layout.tsx │ │ │ │ └── store.tsx │ │ │ ├── _layout.tsx │ │ │ ├── account │ │ │ │ ├── _layout.tsx │ │ │ │ ├── addresses.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── login.tsx │ │ │ │ ├── orders.tsx │ │ │ │ └── profile.tsx │ │ │ ├── cart.tsx │ │ │ └── my-bag.tsx │ │ ├── [...unmatched].tsx │ │ ├── _layout.tsx │ │ ├── checkout.tsx │ │ └── order │ │ │ ├── confirmed │ │ │ └── [id].tsx │ │ │ └── details │ │ │ └── [id].tsx │ ├── babel.config.js │ ├── eas.json │ ├── index.js │ ├── metro.config.js │ ├── package.json │ ├── patches │ │ └── react-native-reanimated+3.0.2.patch │ ├── tailwind.config.js │ └── tsconfig.json ├── medusa-store │ ├── .babelrc.js │ ├── .env.template │ ├── .gitignore │ ├── README.md │ ├── data │ │ └── seed.json │ ├── index.js │ ├── medusa-config.js │ ├── package.json │ ├── src │ │ ├── api │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ └── routes │ │ │ │ └── store │ │ │ │ ├── custom-route-handler.ts │ │ │ │ └── index.ts │ │ ├── loaders │ │ │ └── README.md │ │ ├── migrations │ │ │ └── README.md │ │ ├── models │ │ │ └── README.md │ │ ├── services │ │ │ ├── README.md │ │ │ └── __tests__ │ │ │ │ └── test-service.spec.ts │ │ └── subscribers │ │ │ └── README.md │ ├── tsconfig.json │ └── tsconfig.spec.json └── next │ ├── .gitignore │ ├── app-env.d.ts │ ├── global.css │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── account │ │ ├── addresses.tsx │ │ ├── index.tsx │ │ ├── login.tsx │ │ ├── orders.tsx │ │ └── profile.tsx │ ├── cart.tsx │ ├── checkout.tsx │ ├── index.tsx │ ├── order │ │ ├── confirmed │ │ │ └── [id].tsx │ │ └── details │ │ │ └── [id].tsx │ ├── products │ │ └── [handle].tsx │ ├── store.tsx │ └── user │ │ └── [id].tsx │ ├── plugins │ └── swc_plugin_reanimated.wasm │ ├── postcss.config.js │ ├── public │ ├── cta_four.jpg │ ├── cta_one.jpg │ ├── cta_three.jpg │ ├── cta_two.jpg │ ├── favicon.ico │ ├── hero.jpg │ └── vercel.svg │ ├── tailwind.config.js │ └── tsconfig.json ├── package.json ├── packages └── app │ ├── assets │ └── hero.jpg │ ├── design │ ├── image.tsx │ ├── index.ts │ ├── layout.tsx │ ├── pressable.tsx │ ├── svg.tsx │ ├── tailwind │ │ ├── custom-css-classes.ts │ │ └── theme.js │ ├── typography.tsx │ └── view.tsx │ ├── index.ts │ ├── lib │ ├── config.ts │ ├── constants.ts │ ├── context │ │ ├── account-context.tsx │ │ ├── cart-dropdown-context.tsx │ │ ├── checkout-context.tsx │ │ ├── checkout-context.web.tsx │ │ ├── mobile-menu-context.tsx │ │ ├── modal-context.tsx │ │ ├── product-context.tsx │ │ └── store-context.tsx │ ├── data │ │ └── index.ts │ ├── hooks │ │ ├── use-country-options.tsx │ │ ├── use-current-width.tsx │ │ ├── use-debounce.tsx │ │ ├── use-enrich-line-items.tsx │ │ ├── use-in-view.tsx │ │ ├── use-layout-data.tsx │ │ ├── use-previews.tsx │ │ ├── use-product-price.tsx │ │ ├── use-product.tsx │ │ ├── use-toggle-state.tsx │ │ └── use-universal-pathname │ │ │ ├── index.tsx │ │ │ ├── useUniversalPathname.tsx │ │ │ └── useUniversalPathname.web.tsx │ └── util │ │ ├── can-buy.ts │ │ ├── get-collection-ids.ts │ │ ├── get-number-of-skeletons.ts │ │ ├── get-precentage-diff.ts │ │ ├── get-product-handles.ts │ │ ├── handle-error.ts │ │ ├── noop.ts │ │ ├── only-unique.ts │ │ ├── prices.ts │ │ ├── regex.ts │ │ ├── repeat.ts │ │ └── transform-product-preview.ts │ ├── modules │ ├── account │ │ ├── account-screen.tsx │ │ ├── addresses-screen.tsx │ │ ├── components │ │ │ ├── account-info │ │ │ │ └── index.tsx │ │ │ ├── account-nav │ │ │ │ └── index.tsx │ │ │ ├── address-book │ │ │ │ └── index.tsx │ │ │ ├── address-card │ │ │ │ ├── add-address.native.tsx │ │ │ │ ├── add-address.web.tsx │ │ │ │ ├── edit-address-modal.native.tsx │ │ │ │ ├── edit-address-modal.web.tsx │ │ │ │ └── remove-address-dialog.tsx │ │ │ ├── detail-container │ │ │ │ └── index.tsx │ │ │ ├── edit-button │ │ │ │ └── index.tsx │ │ │ ├── login-details │ │ │ │ ├── edit-email-modal.tsx │ │ │ │ ├── edit-password-modal.tsx │ │ │ │ └── index.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 │ │ ├── login-screen.tsx │ │ ├── orders-screen.tsx │ │ ├── profile-screen.tsx │ │ └── templates │ │ │ ├── account-layout.tsx │ │ │ ├── addresses-template.tsx │ │ │ ├── login-template.tsx │ │ │ ├── order-details-template.tsx │ │ │ ├── orders-template.tsx │ │ │ ├── overview-template.tsx │ │ │ └── profile-template.tsx │ ├── cart │ │ ├── components │ │ │ ├── empty-cart-message │ │ │ │ └── index.tsx │ │ │ ├── item │ │ │ │ └── index.tsx │ │ │ └── sign-in-prompt │ │ │ │ └── index.tsx │ │ ├── screen.tsx │ │ └── templates │ │ │ ├── index.tsx │ │ │ ├── items.tsx │ │ │ └── summary.tsx │ ├── checkout │ │ ├── components │ │ │ ├── address-select │ │ │ │ ├── index.native.tsx │ │ │ │ └── index.web.tsx │ │ │ ├── addresses │ │ │ │ └── index.tsx │ │ │ ├── billing_address │ │ │ │ └── index.tsx │ │ │ ├── checkout-loader │ │ │ │ └── index.tsx │ │ │ ├── country-select │ │ │ │ ├── index.native.tsx │ │ │ │ └── index.web.tsx │ │ │ ├── discount-code │ │ │ │ └── index.tsx │ │ │ ├── gift-card │ │ │ │ └── index.tsx │ │ │ ├── payment-button │ │ │ │ └── index.tsx │ │ │ ├── payment-container │ │ │ │ └── index.tsx │ │ │ ├── payment-stripe │ │ │ │ └── index.tsx │ │ │ ├── payment-test │ │ │ │ └── index.tsx │ │ │ ├── payment-wrapper │ │ │ │ ├── index.tsx │ │ │ │ └── index.web.tsx │ │ │ ├── payment │ │ │ │ └── index.tsx │ │ │ ├── shipping-address │ │ │ │ └── index.tsx │ │ │ ├── shipping │ │ │ │ └── index.tsx │ │ │ └── step-container │ │ │ │ └── index.tsx │ │ ├── screen.tsx │ │ └── templates │ │ │ ├── checkout-form │ │ │ └── index.tsx │ │ │ ├── checkout-summary │ │ │ └── index.tsx │ │ │ └── index.tsx │ ├── common │ │ ├── components │ │ │ ├── button │ │ │ │ └── index.tsx │ │ │ ├── cart-totals │ │ │ │ └── index.tsx │ │ │ ├── checkbox │ │ │ │ └── index.tsx │ │ │ ├── connect-form │ │ │ │ └── index.tsx │ │ │ ├── hamburger │ │ │ │ └── index.tsx │ │ │ ├── head │ │ │ │ └── index.tsx │ │ │ ├── input │ │ │ │ └── index.tsx │ │ │ ├── line-item-options │ │ │ │ └── index.tsx │ │ │ ├── line-item-price │ │ │ │ └── index.tsx │ │ │ ├── modal │ │ │ │ └── index.tsx │ │ │ ├── native-select │ │ │ │ ├── index.tsx │ │ │ │ └── index.web.tsx │ │ │ ├── radio │ │ │ │ └── index.tsx │ │ │ └── underline-link │ │ │ │ └── index.tsx │ │ └── icons │ │ │ ├── alert.tsx │ │ │ ├── arrow-right.tsx │ │ │ ├── back.tsx │ │ │ ├── cart.tsx │ │ │ ├── chevron-down.tsx │ │ │ ├── edit.tsx │ │ │ ├── eye-off.tsx │ │ │ ├── eye.tsx │ │ │ ├── fast-delivery.tsx │ │ │ ├── gift.tsx │ │ │ ├── map-pin.tsx │ │ │ ├── minus.tsx │ │ │ ├── package.tsx │ │ │ ├── placeholder-image.tsx │ │ │ ├── plus.tsx │ │ │ ├── refresh.tsx │ │ │ ├── search.tsx │ │ │ ├── sorting.tsx │ │ │ ├── spinner.tsx │ │ │ ├── trash.tsx │ │ │ ├── user.tsx │ │ │ └── x.tsx │ ├── home │ │ ├── components │ │ │ ├── featured-products │ │ │ │ └── index.tsx │ │ │ └── hero │ │ │ │ └── index.tsx │ │ └── screen.tsx │ ├── layout │ │ ├── components │ │ │ ├── cart-dropdown │ │ │ │ └── index.tsx │ │ │ ├── country-select │ │ │ │ └── index.tsx │ │ │ ├── dropdown-menu │ │ │ │ └── index.tsx │ │ │ ├── footer-cta │ │ │ │ └── index.tsx │ │ │ ├── footer-nav │ │ │ │ └── index.tsx │ │ │ ├── medusa-cta │ │ │ │ └── index.tsx │ │ │ └── site-info │ │ │ │ └── index.tsx │ │ └── templates │ │ │ ├── footer │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── nav │ │ │ └── index.tsx │ ├── mobile-menu │ │ ├── components │ │ │ ├── container │ │ │ │ └── index.tsx │ │ │ ├── country-menu │ │ │ │ └── index.tsx │ │ │ ├── main-menu │ │ │ │ └── index.tsx │ │ │ └── search-menu │ │ │ │ └── index.tsx │ │ └── templates │ │ │ └── index.tsx │ ├── order │ │ ├── components │ │ │ ├── help │ │ │ │ └── index.tsx │ │ │ ├── items │ │ │ │ └── index.tsx │ │ │ ├── order-details │ │ │ │ └── index.tsx │ │ │ ├── order-summary │ │ │ │ └── index.tsx │ │ │ ├── payment-details │ │ │ │ └── index.tsx │ │ │ └── shipping-details │ │ │ │ └── index.tsx │ │ ├── order-confirmed-screen.tsx │ │ ├── order-details-screen.tsx │ │ └── templates │ │ │ ├── order-completed-template.tsx │ │ │ └── order-details-template.tsx │ ├── products │ │ ├── components │ │ │ ├── image-gallary │ │ │ │ └── index.tsx │ │ │ ├── infinite-products │ │ │ │ └── index.tsx │ │ │ ├── mobile-actions │ │ │ │ └── index.tsx │ │ │ ├── option-select │ │ │ │ └── index.tsx │ │ │ ├── product-actions │ │ │ │ └── index.tsx │ │ │ ├── product-preview │ │ │ │ └── index.tsx │ │ │ ├── product-tabs │ │ │ │ └── index.tsx │ │ │ ├── related-products │ │ │ │ └── index.tsx │ │ │ └── thumbnail │ │ │ │ └── index.tsx │ │ ├── screen.tsx │ │ └── templates │ │ │ ├── index.tsx │ │ │ └── product-info │ │ │ └── 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 │ │ │ └── skeleton-product-tabs │ │ │ │ └── index.tsx │ │ └── templates │ │ │ ├── skeleton-cart-page │ │ │ └── index.tsx │ │ │ ├── skeleton-collection-page │ │ │ └── index.tsx │ │ │ ├── skeleton-order-confirmed │ │ │ └── index.tsx │ │ │ └── skeleton-product-page │ │ │ └── index.tsx │ ├── store │ │ ├── components │ │ │ └── refinement-list │ │ │ │ └── index.tsx │ │ └── store-screen.tsx │ └── user │ │ └── detail-screen.tsx │ ├── nativewind.d.ts │ ├── package.json │ ├── provider │ ├── bottom-sheet │ │ ├── index.tsx │ │ └── index.web.tsx │ ├── index.tsx │ ├── medusa │ │ └── index.tsx │ ├── navigation │ │ ├── index.tsx │ │ └── index.web.tsx │ ├── safe-area │ │ ├── index.tsx │ │ ├── index.web.tsx │ │ ├── use-safe-area.ts │ │ └── use-safe-area.web.ts │ └── toasts │ │ ├── index.tsx │ │ └── index.web.tsx │ ├── public │ ├── cta_four.jpg │ ├── cta_one.jpg │ ├── cta_three.jpg │ ├── cta_two.jpg │ ├── favicon.ico │ └── hero.jpg │ ├── rnw-overrides.d.ts │ ├── tsconfig.json │ └── types │ ├── global.ts │ ├── icon.ts │ └── medusa.ts ├── readme.md ├── tsconfig.json ├── turbo.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'next', 3 | settings: { 4 | next: { 5 | rootDir: 'apps/next/', 6 | }, 7 | }, 8 | root: true, 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifacts 56 | *.jsbundle 57 | 58 | # CocoaPods 59 | /ios/Pods/ 60 | 61 | # Expo 62 | .expo/* 63 | web-build/ 64 | 65 | **/*/.expo 66 | 67 | **/*/.next 68 | 69 | **/*/ios 70 | **/*/android 71 | 72 | .turbo 73 | build/** 74 | 75 | 76 | .pnp.* 77 | .yarn/* 78 | !.yarn/patches 79 | !.yarn/plugins 80 | !.yarn/releases 81 | !.yarn/sdks 82 | !.yarn/versions -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "useTabs": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "plugins": ["prettier-plugin-tailwindcss"] 7 | } 8 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-3.4.1.cjs 2 | nodeLinker: node-modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rodrigo Figueroa Gonzalez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": {} 3 | } -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/create-universal-medusa-app/.yarn/install-state.gz -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/format-connection-string.ts: -------------------------------------------------------------------------------- 1 | type ConnectionStringOptions = { 2 | user?: string 3 | password?: string 4 | host?: string 5 | db: string 6 | } 7 | 8 | export function encodeDbValue(value: string): string { 9 | return encodeURIComponent(value) 10 | } 11 | 12 | export default ({ user, password, host, db }: ConnectionStringOptions) => { 13 | let connection = `postgres://` 14 | if (user) { 15 | connection += encodeDbValue(user) 16 | } 17 | 18 | if (password) { 19 | connection += `:${encodeDbValue(password)}` 20 | } 21 | 22 | if (user || password) { 23 | connection += "@" 24 | } 25 | 26 | connection += `${host}/${db}` 27 | 28 | return connection 29 | } -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-universal-medusa-app", 3 | "version": "0.2.6", 4 | "devDependencies": { 5 | "@types/async-retry": "1.4.2", 6 | "@types/cross-spawn": "^6.0.2", 7 | "@types/inquirer": "^9.0.3", 8 | "@types/node": "^12.6.8", 9 | "@types/pg": "^8.10.2", 10 | "@types/prompts": "2.0.1", 11 | "@types/rimraf": "3.0.0", 12 | "@types/tar": "6.1.3", 13 | "@types/validate-npm-package-name": "3.0.0", 14 | "@vercel/ncc": "0.33.1", 15 | "async-retry": "1.3.1", 16 | "chalk": "2.4.2", 17 | "commander": "2.20.0", 18 | "cpy": "7.3.0", 19 | "cross-spawn": "6.0.5", 20 | "got": "10.7.0", 21 | "prompts": "2.1.0", 22 | "rimraf": "3.0.0", 23 | "tar": "6.1.12", 24 | "update-check": "1.5.4", 25 | "validate-npm-package-name": "3.0.0" 26 | }, 27 | "engines": { 28 | "node": ">=12.22.0" 29 | }, 30 | "dependencies": { 31 | "@expo/package-manager": "^0.0.50", 32 | "inquirer": "^9.2.10", 33 | "nanoid": "^4.0.2", 34 | "ora": "^7.0.1", 35 | "pg": "^8.11.3", 36 | "ts-node": "10.7.0", 37 | "typescript": "4.5.5" 38 | }, 39 | "scripts": { 40 | "start": "ts-node index.ts", 41 | "test": "rimraf ./create-test-app/ && yarn release && node dist/index.js create-test-app", 42 | "dev": "ncc build ./index.ts -w -o dist/", 43 | "prerelease": "rimraf ./dist/", 44 | "release": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register", 45 | "prepublish": "yarn release" 46 | }, 47 | "bin": "./dist/index.js" 48 | } 49 | -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/postgres-client.ts: -------------------------------------------------------------------------------- 1 | import pg from "pg" 2 | const { Client } = pg 3 | 4 | type PostgresConnection = { 5 | user?: string 6 | password?: string 7 | connectionString?: string 8 | } 9 | 10 | export default async (connect: PostgresConnection) => { 11 | const client = new Client(connect) 12 | 13 | await client.connect() 14 | 15 | return client 16 | } -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/readme.md: -------------------------------------------------------------------------------- 1 | # `create-universal-medusa-app` 2 | 3 | ```sh 4 | npx create-universal-medusa-app@latest 5 | ``` 6 | 7 | A script that creates a [universal medusa app](https://github.com/bidah/universal-medusa/tree/main) for you in seconds. 8 | 9 | ## Credits 10 | 11 | Based on [create-universal-medusa-app](https://github.com/nandorojo/solito/tree/master/create-solito-app) by Fernando Rojo. 12 | -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('child_process').execSync('npx ts-node ./index.ts', { 3 | stdio: 'inherit', 4 | }) 5 | -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "moduleResolution": "node", 5 | "strict": true, 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": false 9 | }, 10 | "include": ["index.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /apps/create-universal-medusa-app/utils/get-current-os.ts: -------------------------------------------------------------------------------- 1 | export const getCurrentOs = (): string => { 2 | switch (process.platform) { 3 | case "darwin": 4 | return "macos" 5 | case "linux": 6 | return "linux" 7 | default: 8 | return "windows" 9 | } 10 | } -------------------------------------------------------------------------------- /apps/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /apps/docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /apps/docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /apps/docs/blog/2019-05-28-first-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: first-blog-post 3 | title: First Blog Post 4 | authors: 5 | name: Gao Wei 6 | title: Docusaurus Core Team 7 | url: https://github.com/wgao19 8 | image_url: https://github.com/wgao19.png 9 | tags: [hola, docusaurus] 10 | --- 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | -------------------------------------------------------------------------------- /apps/docs/blog/2021-08-01-mdx-blog-post.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: mdx-blog-post 3 | title: MDX Blog Post 4 | authors: [slorber] 5 | tags: [docusaurus] 6 | --- 7 | 8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). 9 | 10 | :::tip 11 | 12 | Use the power of React to create interactive blog posts. 13 | 14 | ```js 15 | 16 | ``` 17 | 18 | 19 | 20 | ::: 21 | -------------------------------------------------------------------------------- /apps/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg -------------------------------------------------------------------------------- /apps/docs/blog/2021-08-26-welcome/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | authors: [slorber, yangshun] 5 | tags: [facebook, hello, docusaurus] 6 | --- 7 | 8 | [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). 9 | 10 | Simply add Markdown files (or folders) to the `blog` directory. 11 | 12 | Regular blog authors can be added to `authors.yml`. 13 | 14 | The blog post date can be extracted from filenames, such as: 15 | 16 | - `2019-05-30-welcome.md` 17 | - `2019-05-30-welcome/index.md` 18 | 19 | A blog post folder can be convenient to co-locate blog post images: 20 | 21 | ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) 22 | 23 | The blog supports tags as well! 24 | 25 | **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. 26 | -------------------------------------------------------------------------------- /apps/docs/blog/authors.yml: -------------------------------------------------------------------------------- 1 | endi: 2 | name: Endilie Yacop Sucipto 3 | title: Maintainer of Docusaurus 4 | url: https://github.com/endiliey 5 | image_url: https://github.com/endiliey.png 6 | 7 | yangshun: 8 | name: Yangshun Tay 9 | title: Front End Engineer @ Facebook 10 | url: https://github.com/yangshun 11 | image_url: https://github.com/yangshun.png 12 | 13 | slorber: 14 | name: Sébastien Lorber 15 | title: Docusaurus maintainer 16 | url: https://sebastienlorber.com 17 | image_url: https://github.com/slorber.png 18 | -------------------------------------------------------------------------------- /apps/docs/docs/Styling/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Styling", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index", 6 | }, 7 | "collapsed": false 8 | } 9 | -------------------------------------------------------------------------------- /apps/docs/docs/Styling/breakpoints.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Breakpoints 6 | 7 | Breakpoints are configured to be used when styling with NativeWind and when creating layouts with Stacks. The name of each breakpoint can be used interchangeably with both libraries. 8 | 9 | ```js title="packages/app/design/tailwind/theme.js" 10 | const breakPointsInPx = { 11 | '2xsmall': '320px', 12 | xsmall: '512px', 13 | small: '1024px', 14 | medium: '1280px', 15 | large: '1440px', 16 | xlarge: '1680px', 17 | '2xlarge': '1920px', 18 | } 19 | ``` 20 | 21 | For example you could setup a layout picking up the `medium` breakpoint with the `Columns` component of Stacks library. 22 | 23 | ```tsx 24 | 25 | 26 | … 27 | 28 | 29 | … 30 | 31 | 32 | ``` 33 | 34 | Or when styling within the className prop in Nativewind. 35 | 36 | Here a snippet from the codebase using `small` and `medium`. 37 | ```tsx 38 | 39 | 40 | SUMMER styles are finally here 41 | 42 | 43 | This year, our new summer collection will shelter you from the harsh 44 | elements of a world that doesn't care if you live or die. 45 | 46 | 47 | ``` 48 | 49 | # Usage 50 | Please refer to the breakpoints documentation on: 51 | - [Stacks library website](https://mobily.github.io/stacks/docs/getting-started/breakpoints) 52 | - [Tailwind.js website](https://tailwindcss.com/docs/responsive-design) 53 | -------------------------------------------------------------------------------- /apps/docs/docs/Styling/design-system.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Design System 6 | 7 | The base design system primitives are available at `packages/app/design`. 8 | 9 | You have 5 categories available. Each with one or more components within which you will use to build your UI. 10 | 11 | - image.tsx 12 | - layout.tsx 13 | - pressable.tsx 14 | - svg.tsx 15 | - typography.tsx 16 | - view.tsx 17 | 18 | 19 | ### Extend 20 | 21 | You can build on top of current primitives. All components are written using the Nativewind `styled()` higher-order component. 22 | 23 | Pickup one of the categories to extend or create a new one. You can start building new UI primitives like so: 24 | 25 | ```tsx 26 | // packages/app/design/typography 27 | import { Text } from 'react-native' 28 | import { styled } from 'nativewind' 29 | 30 | export const DarkText = styled(Text, 'text-base text-black my-4') 31 | ``` 32 | 33 | *Notice that you can set base styles using the second argument of `styled`.* 34 | 35 | You can then use the `className` prop, just like regular Tailwind CSS: 36 | 37 | ```tsx 38 | 39 | ``` 40 | 41 | 42 | :::info 43 | If you're reading the NativeWind docs, you might find that you can use `className` directly without using `styled`. Since this requires the Babel plugin for all platforms, it won't work with Universal Muedusa and the Solito structure its build on top. Be sure to always wrap your components with `styled`. 44 | ::: 45 | -------------------------------------------------------------------------------- /apps/docs/docs/Styling/layouts.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Layouts 6 | 7 | Build universal, responsive and flexible layouts (Stacks, Columns, Rows, Tiles, Inline) with correct spacing and ease by leveraging [Stacks library](https://mobily.github.io/stacks/) 8 | 9 | With Nativewind you are missing from the API what Tailwind gets you out of the box in web by building on top of CSS grid layout. 10 | 11 | With Stacks we get back the best layout handling for multi platforms and screen sizes. 12 | 13 | # Usage 14 | 15 | Please refer to the documentation on the [Stacks library website](https://mobily.github.io/stacks/) 16 | -------------------------------------------------------------------------------- /apps/docs/docs/Styling/tailwind-with-nativewind.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Tailwind with Nativewind 6 | 7 | Style your apps with [Tailwind CSS](https://tailwindcss.com) class name syntax by using [Nativewind](http://nativewind.dev) 8 | 9 | ## Nativewind 10 | NativeWind uses [Tailwind CSS](https://tailwindcss.com) as scripting language to create a **universal styles**. Styled components used on your React components will be shared between all React Native platforms you setup on your mobile app and web too, using the best style engine for each platform. CSS StyleSheet on web and StyleSheet.create for native. 11 | 12 | ### Key Features 13 | 14 | 🌐 **Universal** Uses the best style system for each platform. 15 | 16 | 🛠️ **Precompiled** Uses the Tailwind CSS compile, styles are generated at build time 17 | 18 | 🚀 **Fast runtime** Small runtime keeps everything fast 19 | 20 | 🖥️ **DevUX** Plugins for simple setup and improving intellisense support 21 | 22 | 🔥 **Lots of features** dark mode / arbitrary classes / media queries / themes / custom values / plugins 23 | 24 | ✨ **Pseudo classes** hover / focus / active on compatible components [(docs)](../core-concepts/states#hover-focus-and-active) 25 | 26 | 👪 **Parent state styles** automatically style children based upon parent pseudo classes [(docs)](../core-concepts/states#styling-based-on-parent-state) 27 | 28 | # Usage 29 | 30 | Please refer to the documentation on the [Nativewind website](http://nativewind.dev) 31 | -------------------------------------------------------------------------------- /apps/docs/docs/headless-ecommerce/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Headless ecommerce", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index" 6 | }, 7 | "collapsed": false 8 | } 9 | -------------------------------------------------------------------------------- /apps/docs/docs/headless-ecommerce/about-medusa-js.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # About Medusa.js 6 | Medusa.js is the core of Universal Medusa. A suite of commerce modules that are the foundation of the universal architecture. 7 | 8 | This enables you to start an ecommerce website and mobile app with rich, reliable, and performant commerce logic without reinventing core commerce logic and building or extending once and have it replicated on each platform without any extra development per platform. 9 | 10 | You get all the modules connected to the frontend and backend to get right out of the box a running ecommerce application. 11 | 12 | Get to know all about it on [Medusajs.com](https://medusajs.com/) 13 | -------------------------------------------------------------------------------- /apps/docs/docs/navigation/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Navigation", 3 | "position": 4, 4 | "link": { 5 | "type": "generated-index" 6 | }, 7 | "collapsed": false 8 | } 9 | -------------------------------------------------------------------------------- /apps/docs/docs/navigation/discoverability/SEO.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # SEO 6 | 7 | ## Default Setup 8 | We pass a `seo` object from `getStaticProps` to `pages/products/[handle].tsx` to enable SEO discoverability on all products. 9 | 10 | 11 | ```jsx title=pages/products/[handle].tsx 12 | 13 | import {fetchProduct} from "app/lib/hooks/use-product"; 14 | 15 | export default ProductScreen 16 | 17 | export const getStaticProps = async ({ params }) => { 18 | const product = await fetchProduct(params.slug) 19 | return { 20 | props: { 21 | // this will get passed to pageProps 22 | seo: { title: product.title, description: product.description }, 23 | }, 24 | revalidate: 1, 25 | } 26 | } 27 | ``` 28 | 29 | ## Extend Usage 30 | 31 | To extend setup please refer to the documentation on the [Next-seo repo](https://github.com/garmeeh/next-seo#readme) 32 | -------------------------------------------------------------------------------- /apps/docs/docs/navigation/discoverability/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Discoverability", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index" 6 | }, 7 | "collapsed": false 8 | } 9 | -------------------------------------------------------------------------------- /apps/docs/docs/navigation/discoverability/app-clips.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # App Clips 6 | 7 | :::info 8 | Feature not implemented on current version. Under development 9 | ::: 10 | 11 | 12 | Leverage discoverability of app with prospect users by not needing to install your mobile app. 13 | 14 | By exposing your consumer to a bite-sized version of your app you encourage full-version downloads of it which extends user reach out by future push notifications and other native mobile app features. 15 | 16 | ## Extend Usage 17 | 18 | To extend setup please refer to the documentation on the [react-native-app-clip repo](https://github.com/bndkt/react-native-app-clip) 19 | -------------------------------------------------------------------------------- /apps/docs/docs/navigation/linking.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Linking 6 | 7 | ```ts 8 | import { Link } from 'app/design' 9 | 10 | ``` 11 | 12 | ```tsx 13 | 14 | {item.title} 15 | 16 | ``` 17 | 18 | ## UnderlineLink 19 | 20 | ```jsx 21 | import UnderlineLink from 'app/modules/common/components/underline-link' 22 | 23 | Explore products 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /apps/docs/docs/navigation/routing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Routing 6 | 7 | Please refer to the [documentation on the Solito website for use-router](https://solito.dev/usage/use-router) 8 | 9 | -------------------------------------------------------------------------------- /apps/docs/docs/navigation/use-universal-pathname.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # useUniversalPathname 6 | 7 | When using both solito and expo-router the solito library is missing a way to get the current pathname. 8 | This hook solves this gap. Use it to get the current pathname on both platforms. 9 | 10 | ## Usage 11 | 12 | ```tsx title="app/lib/hooks/use-universal-pathname" 13 | const pathname = useUniversalPathname() 14 | ``` 15 | 16 | -------------------------------------------------------------------------------- /apps/docs/docs/project-structure/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Project structure", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Get to know the project structure and how to use it" 7 | }, 8 | "collapsed": false 9 | } 10 | -------------------------------------------------------------------------------- /apps/docs/docs/project-structure/add-new-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Add new dependencies 6 | 7 | ## Pure JS dependencies 8 | 9 | If you're installing a JavaScript-only dependency that will be used across platforms, install it in `packages/app`: 10 | 11 | ```sh 12 | cd packages/app 13 | yarn add date-fns 14 | cd ../.. 15 | yarn 16 | ``` 17 | 18 | ## Native dependencies 19 | 20 | If you're installing a library with any native code, you must install it in `apps/expo`: 21 | 22 | ```sh 23 | cd apps/expo 24 | yarn add react-native-reanimated 25 | 26 | cd ../.. 27 | yarn 28 | ``` 29 | 30 | ## Useful links 31 | 32 | - Read the [Solito docs](https://solito.dev) 33 | -------------------------------------------------------------------------------- /apps/docs/docs/project-structure/apps-workspace.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # `apps` workspace 6 | 7 | The `apps` folder contains all the apps that make up the monorepo. Each app is a separate project that can be run independently. They cosist of: 8 | 9 | - expo 10 | - next 11 | - medusa-store 12 | 13 | ## Useful links 14 | 15 | - Read the [Monorepo handbook](https://turbo.build/repo/docs/handbook) 16 | -------------------------------------------------------------------------------- /apps/docs/docs/project-structure/packages-app-workspace.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # `packages/app` workspace 6 | 7 | This workspace contains the `app` package that includes the following: 8 | 9 | - assets 10 | - design 11 | - lib 12 | - context 13 | - data 14 | - hooks 15 | - utils 16 | - modules 17 | - provider 18 | - bottom-sheet 19 | - medusa 20 | - react-query-devtools 21 | - safe-area 22 | - types 23 | -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start --port 3001", 8 | "swizzle": "docusaurus swizzle", 9 | "build": "docusaurus build", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.4.1", 18 | "@docusaurus/preset-classic": "2.4.1", 19 | "@mdx-js/react": "^1.6.22", 20 | "autoprefixer": "^10.4.14", 21 | "clsx": "^1.2.1", 22 | "postcss": "^8.4.24", 23 | "prism-react-renderer": "^1.3.5", 24 | "react": "^17.0.2", 25 | "react-dom": "^17.0.2", 26 | "tailwindcss": "^3.3.2" 27 | }, 28 | "devDependencies": { 29 | "@docusaurus/module-type-aliases": "2.4.1" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.5%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | }, 43 | "engines": { 44 | "node": ">=16.14" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /apps/docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | 'intro', 23 | 'hello', 24 | { 25 | type: 'category', 26 | label: 'Tutorial', 27 | items: ['tutorial-basics/create-a-document'], 28 | }, 29 | ], 30 | */ 31 | }; 32 | 33 | module.exports = sidebars; 34 | -------------------------------------------------------------------------------- /apps/docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /apps/docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /apps/docs/src/pages/_index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Link from '@docusaurus/Link'; 4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 5 | import Layout from '@theme/Layout'; 6 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 7 | 8 | import styles from './index.module.css'; 9 | 10 | function HomepageHeader() { 11 | const {siteConfig} = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 |

{siteConfig.title}

16 |

{siteConfig.tagline}

17 |
18 | 21 | Docusaurus Tutorial - 5min ⏱️ 22 | 23 |
24 |
25 |
26 | ); 27 | } 28 | 29 | export default function Home() { 30 | const {siteConfig} = useDocusaurusContext(); 31 | return ( 32 | 35 | 36 |
37 | 38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /apps/docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 10rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /apps/docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /apps/docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/docs/static/.nojekyll -------------------------------------------------------------------------------- /apps/docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /apps/docs/tailwind.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/docs/tailwind.config.js -------------------------------------------------------------------------------- /apps/expo/App.tsx: -------------------------------------------------------------------------------- 1 | import { NativeNavigation } from 'app/navigation/native' 2 | import { Provider } from 'app/provider' 3 | 4 | export default function App() { 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /apps/expo/app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /apps/expo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "universal-medusa-app", 4 | "slug": "universal-medusa-app", 5 | "version": "1.0.0", 6 | "scheme": "medusauniversal", 7 | "platforms": [ 8 | "ios", 9 | "android" 10 | ], 11 | "ios": { 12 | "bundleIdentifier": "com.medusa.universal" 13 | }, 14 | "extra": { 15 | "eas": { 16 | "projectId": "67b76865-0bdd-4a76-a9e8-db716bd5bd2c" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/(home)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router' 2 | import Layout from 'app/modules/layout/templates' 3 | import { Text, View } from 'app/design' 4 | 5 | const HomeLayout = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default HomeLayout 15 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/(home)/index.tsx: -------------------------------------------------------------------------------- 1 | import { HomeScreen } from 'app/modules/home/screen' 2 | import Layout from 'app/modules/layout/templates' 3 | 4 | // add React Native LogBox to suppress message object hasn't been initialized' 5 | 6 | export default function Index() { 7 | return ( 8 | // 9 | 10 | // 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/(home)/products/[handle].tsx: -------------------------------------------------------------------------------- 1 | import { ProductScreen } from 'app/modules/products/screen' 2 | import { Stack, useLocalSearchParams, useRouter } from 'expo-router' 3 | import useProduct from 'app/lib/hooks/use-product' 4 | 5 | export default function Handle(props) { 6 | const { handle } = useLocalSearchParams() 7 | const { data } = useProduct(handle) 8 | 9 | return ( 10 | <> 11 | 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/(home)/products/__layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router' 2 | 3 | export default function HomeLayout() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/(home)/store.tsx: -------------------------------------------------------------------------------- 1 | import { StoreScreen } from 'app/modules/store/store-screen' 2 | 3 | export default function StorePage() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/account/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router' 2 | import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' 3 | 4 | const AccountLayout = () => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default AccountLayout 15 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/account/addresses.tsx: -------------------------------------------------------------------------------- 1 | import { AddressesScreen } from 'app/modules/account/addresses-screen' 2 | import { Stack } from 'expo-router' 3 | 4 | export default function AddressesPage() { 5 | return ( 6 | <> 7 | 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/account/index.tsx: -------------------------------------------------------------------------------- 1 | import { AccountScreen } from 'app/modules/account/account-screen' 2 | import AccountLayout from "app/modules/account/templates/account-layout"; 3 | 4 | export default function AccountPage() { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/account/login.tsx: -------------------------------------------------------------------------------- 1 | import { LoginScreen } from 'app/modules/account/login-screen' 2 | 3 | export default function LoginPage() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/account/orders.tsx: -------------------------------------------------------------------------------- 1 | import { OrdersScreen } from 'app/modules/account/orders-screen' 2 | import { Stack } from 'expo-router' 3 | 4 | export default function OrdersPage() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/account/profile.tsx: -------------------------------------------------------------------------------- 1 | import { ProfileScreen } from 'app/modules/account/profile-screen' 2 | import { Stack } from 'expo-router' 3 | 4 | export default function ProfilePage() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/cart.tsx: -------------------------------------------------------------------------------- 1 | import { CartScreen } from 'app/modules/cart/screen' 2 | 3 | export default function CartPage() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/expo/app/(tabs)/my-bag.tsx: -------------------------------------------------------------------------------- 1 | import { CartScreen } from 'app/modules/cart/screen' 2 | import { Stack } from 'expo-router' 3 | import { useCart } from 'medusa-react' 4 | 5 | export default function CartPage() { 6 | const { totalItems } = useCart() 7 | 8 | return ( 9 | <> 10 | 15 | 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /apps/expo/app/[...unmatched].tsx: -------------------------------------------------------------------------------- 1 | import { Redirect } from 'expo-router' 2 | 3 | export default function UnmatchedScreen() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/expo/app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router' 2 | import { Provider } from 'app/provider' 3 | 4 | export default function Layout() { 5 | return ( 6 | 7 | 8 | 14 | 20 | 26 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /apps/expo/app/checkout.tsx: -------------------------------------------------------------------------------- 1 | import { CheckoutScreen } from 'app/modules/checkout/screen' 2 | import { Stack } from 'expo-router' 3 | 4 | import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' 5 | export default function CheckoutPage() { 6 | return ( 7 | <> 8 | 9 | 14 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /apps/expo/app/order/confirmed/[id].tsx: -------------------------------------------------------------------------------- 1 | import { OrderConfirmedScreen } from 'app/modules/order/order-confirmed-screen' 2 | import { Stack } from 'expo-router' 3 | 4 | export default function ConfirmedPage() { 5 | return <> 6 | 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /apps/expo/app/order/details/[id].tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router' 2 | import { OrderDetailsScreen } from 'app/modules/order/order-details-screen' 3 | export default function ConfirmedPage() { 4 | return ( 5 | <> 6 | 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /apps/expo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | return { 4 | presets: [['babel-preset-expo', { jsxRuntime: 'automatic' }]], 5 | plugins: [ 6 | 'react-native-reanimated/plugin', 7 | 'nativewind/babel', 8 | require.resolve('expo-router/babel'), 9 | ], 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/expo/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 3.8.1" 4 | }, 5 | "build": { 6 | "development-simulator": { 7 | "developmentClient": true, 8 | "distribution": "internal", 9 | "ios": { 10 | "simulator": true 11 | } 12 | }, 13 | "development": { 14 | "developmentClient": true, 15 | "distribution": "internal", 16 | "ios": { 17 | "resourceClass": "m-medium" 18 | } 19 | }, 20 | "preview": { 21 | "distribution": "internal", 22 | "ios": { 23 | "resourceClass": "m-medium" 24 | } 25 | }, 26 | "production": { 27 | "ios": { 28 | "resourceClass": "m-medium" 29 | } 30 | } 31 | }, 32 | "submit": { 33 | "production": {} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/expo/index.js: -------------------------------------------------------------------------------- 1 | // registerRootComponent happens in "expo-router/entry" 2 | import 'react-native-get-random-values' 3 | import 'expo-router/entry' 4 | -------------------------------------------------------------------------------- /apps/expo/metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.dev/guides/monorepos 2 | // Learn more https://docs.expo.io/guides/customizing-metro 3 | /** 4 | * @type {import('expo/metro-config')} 5 | */ 6 | const { getDefaultConfig } = require('expo/metro-config') 7 | const path = require('path') 8 | 9 | // Find the project and workspace directories 10 | const projectRoot = __dirname 11 | // This can be replaced with `find-yarn-workspace-root` 12 | const workspaceRoot = path.resolve(projectRoot, '../..') 13 | 14 | const config = getDefaultConfig(projectRoot) 15 | 16 | // 1. Watch all files within the monorepo 17 | config.watchFolders = [workspaceRoot] 18 | // 2. Let Metro know where to resolve packages and in what order 19 | config.resolver.nodeModulesPaths = [ 20 | path.resolve(projectRoot, 'node_modules'), 21 | path.resolve(workspaceRoot, 'node_modules'), 22 | ] 23 | // 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths` 24 | config.resolver.disableHierarchicalLookup = true 25 | 26 | module.exports = config 27 | -------------------------------------------------------------------------------- /apps/expo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@babel/plugin-proposal-export-namespace-from": "^7.18.9", 4 | "@backpackapp-io/react-native-toast": "^0.10.0", 5 | "@expo/vector-icons": "^13.0.0", 6 | "@gorhom/bottom-sheet": "^4.4.5", 7 | "@nandorojo/heroicons": "^0.0.4", 8 | "@react-native-async-storage/async-storage": "1.17.11", 9 | "app": "*", 10 | "expo": "~48.0.18", 11 | "expo-dev-client": "~2.2.1", 12 | "expo-linear-gradient": "~12.1.2", 13 | "expo-linking": "^4.0.1", 14 | "expo-router": "^1.5.3", 15 | "expo-splash-screen": "~0.18.2", 16 | "expo-status-bar": "^1.4.4", 17 | "install": "^0.13.0", 18 | "npx": "^10.2.2", 19 | "patch-package": "^8.0.0", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "react-native": "0.71.8", 23 | "react-native-dialog": "^9.3.0", 24 | "react-native-gesture-handler": "~2.9.0", 25 | "react-native-get-random-values": "~1.8.0", 26 | "react-native-heroicons": "^3.2.0", 27 | "react-native-reanimated": "3.1.0", 28 | "react-native-safe-area-context": "4.5.0", 29 | "react-native-screens": "~3.20.0", 30 | "react-native-svg": "13.4.0", 31 | "react-native-web": "~0.18.11" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.20.0", 35 | "@types/react": "~18.0.27", 36 | "@types/react-native": "~0.69.1", 37 | "tailwindcss": "^3.0.24", 38 | "typescript": "^4.9.4" 39 | }, 40 | "scripts": { 41 | "start": "expo start --dev-client", 42 | "android": "expo run:android", 43 | "ios": "expo run:ios", 44 | "postinstall": "yarn patch-package" 45 | }, 46 | "version": "1.0.0", 47 | "private": true, 48 | "name": "expo-app", 49 | "main": "index.js" 50 | } 51 | -------------------------------------------------------------------------------- /apps/expo/patches/react-native-reanimated+3.0.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/react-native-reanimated/RNReanimated.podspec b/node_modules/react-native-reanimated/RNReanimated.podspec 2 | index 1b3b165..dbe0f39 100644 3 | --- a/node_modules/react-native-reanimated/RNReanimated.podspec 4 | +++ b/node_modules/react-native-reanimated/RNReanimated.podspec 5 | @@ -3,7 +3,7 @@ require_relative './scripts/reanimated_utils' 6 | 7 | reanimated_package_json = JSON.parse(File.read(File.join(__dir__, "package.json"))) 8 | config = find_config() 9 | -assert_no_multiple_instances(config) 10 | +# assert_no_multiple_instances(config) 11 | assert_no_reanimated2_with_new_architecture(reanimated_package_json) 12 | assert_latest_react_native_with_new_architecture(config, reanimated_package_json) 13 | 14 | -------------------------------------------------------------------------------- /apps/expo/tailwind.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { theme } = require('app/design/tailwind/theme') 4 | 5 | /** 6 | * @type {import('tailwindcss').Config} 7 | */ 8 | module.exports = { 9 | content: ['./App.tsx', '../../packages/**/*.{js,jsx,ts,tsx}'], 10 | theme: { 11 | ...theme, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /apps/expo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig" 3 | } 4 | -------------------------------------------------------------------------------- /apps/medusa-store/.babelrc.js: -------------------------------------------------------------------------------- 1 | let ignore = [`**/dist`] 2 | 3 | // Jest needs to compile this code, but generally we don't want this copied 4 | // to output folders 5 | if (process.env.NODE_ENV !== `test`) { 6 | ignore.push(`**/__tests__`) 7 | } 8 | 9 | module.exports = { 10 | presets: [["babel-preset-medusa-package"], ["@babel/preset-typescript"]], 11 | ignore, 12 | } 13 | -------------------------------------------------------------------------------- /apps/medusa-store/.env.template: -------------------------------------------------------------------------------- 1 | JWT_SECRET=something 2 | COOKIE_SECRET=something 3 | 4 | DATABASE_TYPE="postgres" 5 | REDIS_URL=redis://localhost:6379 6 | -------------------------------------------------------------------------------- /apps/medusa-store/.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 | yarn.lock 17 | medusa-db.sql 18 | build 19 | -------------------------------------------------------------------------------- /apps/medusa-store/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const { GracefulShutdownServer } = require("medusa-core-utils") 3 | 4 | const loaders = require("@medusajs/medusa/dist/loaders/index").default 5 | 6 | ;(async() => { 7 | async function start() { 8 | const app = express() 9 | const directory = process.cwd() 10 | 11 | try { 12 | const { container } = await loaders({ 13 | directory, 14 | expressApp: app 15 | }) 16 | const configModule = container.resolve("configModule") 17 | const port = process.env.PORT ?? configModule.projectConfig.port ?? 9000 18 | 19 | const server = GracefulShutdownServer.create( 20 | app.listen(port, (err) => { 21 | if (err) { 22 | return 23 | } 24 | console.log(`Server is ready on port: ${port}`) 25 | }) 26 | ) 27 | 28 | // Handle graceful shutdown 29 | const gracefulShutDown = () => { 30 | server 31 | .shutdown() 32 | .then(() => { 33 | console.info("Gracefully stopping the server.") 34 | process.exit(0) 35 | }) 36 | .catch((e) => { 37 | console.error("Error received when shutting down the server.", e) 38 | process.exit(1) 39 | }) 40 | } 41 | process.on("SIGTERM", gracefulShutDown) 42 | process.on("SIGINT", gracefulShutDown) 43 | } catch (err) { 44 | console.error("Error starting server", err) 45 | process.exit(1) 46 | } 47 | } 48 | 49 | await start() 50 | })() 51 | -------------------------------------------------------------------------------- /apps/medusa-store/src/api/README.md: -------------------------------------------------------------------------------- 1 | # Custom endpoints 2 | 3 | You may define custom endpoints by putting files in the `/api` directory that export functions returning an express router or a collection of express routers. 4 | 5 | ```ts 6 | import { Router } from "express" 7 | import { getConfigFile } from "medusa-core-utils" 8 | import { getStoreRouter } from "./routes/store" 9 | import { ConfigModule } from "@medusajs/medusa/dist/types/global"; 10 | 11 | export default (rootDirectory) => { 12 | const { configModule: { projectConfig } } = getConfigFile( 13 | rootDirectory, 14 | "medusa-config" 15 | ) as { configModule: ConfigModule } 16 | 17 | const storeCorsOptions = { 18 | origin: projectConfig.store_cors.split(","), 19 | credentials: true, 20 | } 21 | 22 | const storeRouter = getStoreRouter(storeCorsOptions) 23 | 24 | return [storeRouter] 25 | } 26 | ``` 27 | 28 | A global container is available on `req.scope` to allow you to use any of the registered services from the core, installed plugins or your local project: 29 | ```js 30 | import { Router } from "express" 31 | 32 | export default () => { 33 | const router = Router() 34 | 35 | router.get("/hello-product", async (req, res) => { 36 | const productService = req.scope.resolve("productService") 37 | 38 | const [product] = await productService.list({}, { take: 1 }) 39 | 40 | res.json({ 41 | message: `Welcome to ${product.title}!` 42 | }) 43 | }) 44 | 45 | return router; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /apps/medusa-store/src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express" 2 | 3 | export default (rootDirectory: string): Router | Router[] => { 4 | // add your custom routes here 5 | return [] 6 | } 7 | -------------------------------------------------------------------------------- /apps/medusa-store/src/api/routes/store/custom-route-handler.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | 3 | export default async (req: Request, res: Response): Promise => { 4 | res.sendStatus(200) 5 | } 6 | -------------------------------------------------------------------------------- /apps/medusa-store/src/api/routes/store/index.ts: -------------------------------------------------------------------------------- 1 | import * as cors from "cors" 2 | import { Router } from "express" 3 | import * as bodyParser from "body-parser" 4 | import customRouteHandler from "./custom-route-handler" 5 | import { wrapHandler } from "@medusajs/medusa"; 6 | 7 | const storeRouter = Router() 8 | export function getStoreRouter(storeCorsOptions): Router { 9 | storeRouter.use(cors(storeCorsOptions), bodyParser.json()) 10 | 11 | storeRouter.post( 12 | "/store/my-custom-path", 13 | wrapHandler(customRouteHandler) 14 | ) 15 | 16 | return storeRouter 17 | } 18 | -------------------------------------------------------------------------------- /apps/medusa-store/src/loaders/README.md: -------------------------------------------------------------------------------- 1 | # Custom loader 2 | 3 | The loader allows you have access to the Medusa service container. This allows you to access the database and the services registered on the container. 4 | you can register custom registrations in the container or run custom code on startup. 5 | 6 | ```ts 7 | // src/loaders/my-loader.ts 8 | 9 | import { AwilixContainer } from 'awilix' 10 | 11 | /** 12 | * 13 | * @param container The container in which the registrations are made 14 | * @param config The options of the plugin or the entire config object 15 | */ 16 | export default (container: AwilixContainer, config: Record): void | Promise => { 17 | /* Implement your own loader. */ 18 | } 19 | ``` -------------------------------------------------------------------------------- /apps/medusa-store/src/migrations/README.md: -------------------------------------------------------------------------------- 1 | # Custom migrations 2 | 3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`. 4 | In that case you also need to provide a migration in order to create the table in the database. 5 | 6 | ## Example 7 | 8 | ### 1. Create the migration 9 | 10 | See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation. 11 | 12 | ```ts 13 | // src/migration/my-migration.ts 14 | 15 | import { MigrationInterface, QueryRunner } from "typeorm" 16 | 17 | export class MyMigration1617703530229 implements MigrationInterface { 18 | name = "myMigration1617703530229" 19 | 20 | public async up(queryRunner: QueryRunner): Promise { 21 | // write you migration here 22 | } 23 | 24 | public async down(queryRunner: QueryRunner): Promise { 25 | // write you migration here 26 | } 27 | } 28 | 29 | ``` -------------------------------------------------------------------------------- /apps/medusa-store/src/models/README.md: -------------------------------------------------------------------------------- 1 | # Custom models 2 | 3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`. 4 | 5 | ## Example 6 | 7 | ### 1. Create the Entity 8 | 9 | ```ts 10 | // src/models/post.ts 11 | 12 | import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm"; 13 | import { generateEntityId } from "@medusajs/utils"; 14 | import { BaseEntity } from "@medusajs/medusa"; 15 | 16 | @Entity() 17 | export class Post extends BaseEntity { 18 | @Column({type: 'varchar'}) 19 | title: string | null; 20 | 21 | @BeforeInsert() 22 | private beforeInsert(): void { 23 | this.id = generateEntityId(this.id, "post") 24 | } 25 | } 26 | ``` 27 | 28 | ### 2. Create the Migration 29 | 30 | You also need to create a Migration to create the new table in the database. See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation. 31 | 32 | ### 3. Create a Repository 33 | Entities data can be easily accessed and modified using [TypeORM Repositories](https://typeorm.io/working-with-repository). To create a repository, create a file in `src/repositories`. For example, here’s a repository `PostRepository` for the `Post` entity: 34 | 35 | ```ts 36 | // src/repositories/post.ts 37 | 38 | import { EntityRepository, Repository } from "typeorm" 39 | 40 | import { Post } from "../models/post" 41 | 42 | @EntityRepository(Post) 43 | export class PostRepository extends Repository { } 44 | ``` 45 | 46 | See more about defining and accesing your custom [Entities](https://docs.medusajs.com/advanced/backend/entities/overview) in the documentation. -------------------------------------------------------------------------------- /apps/medusa-store/src/services/README.md: -------------------------------------------------------------------------------- 1 | # Custom services 2 | 3 | You may define custom services that will be registered on the global container by creating files in the `/services` directory that export an instance of `BaseService`. 4 | 5 | ```ts 6 | // src/services/my-custom.ts 7 | 8 | import { Lifetime } from "awilix" 9 | import { TransactionBaseService } from "@medusajs/medusa"; 10 | import { IEventBusService } from "@medusajs/types"; 11 | 12 | export default class MyCustomService extends TransactionBaseService { 13 | static LIFE_TIME = Lifetime.SCOPED 14 | protected readonly eventBusService_: IEventBusService 15 | 16 | constructor( 17 | { eventBusService }: { eventBusService: IEventBusService }, 18 | options: Record 19 | ) { 20 | // @ts-ignore 21 | super(...arguments) 22 | 23 | this.eventBusService_ = eventBusService 24 | } 25 | } 26 | 27 | ``` 28 | 29 | The first argument to the `constructor` is the global giving you access to easy dependency injection. The container holds all registered services from the core, installed plugins and from other files in the `/services` directory. The registration name is a camelCased version of the file name with the type appended i.e.: `my-custom.js` is registered as `myCustomService`, `custom-thing.js` is registered as `customThingService`. 30 | 31 | You may use the services you define here in custom endpoints by resolving the services defined. 32 | 33 | ```js 34 | import { Router } from "express" 35 | 36 | export default () => { 37 | const router = Router() 38 | 39 | router.get("/hello-product", async (req, res) => { 40 | const myService = req.scope.resolve("myCustomService") 41 | 42 | res.json({ 43 | message: await myService.getProductMessage() 44 | }) 45 | }) 46 | 47 | return router; 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /apps/medusa-store/src/services/__tests__/test-service.spec.ts: -------------------------------------------------------------------------------- 1 | describe('MyService', () => { 2 | it('should do this', async () => { 3 | expect(true).toBe(true) 4 | }) 5 | }) 6 | -------------------------------------------------------------------------------- /apps/medusa-store/src/subscribers/README.md: -------------------------------------------------------------------------------- 1 | # Custom subscribers 2 | 3 | You may define custom eventhandlers, `subscribers` by creating files in the `/subscribers` directory. 4 | 5 | ```ts 6 | import MyCustomService from "../services/my-custom"; 7 | import { EntityManager } from "typeorm"; 8 | import { OrderService } from "@medusajs/medusa"; 9 | import { IEventBusService } from "@medusajs/types"; 10 | 11 | export default class MySubscriber { 12 | protected readonly manager_: EntityManager; 13 | protected readonly myCustomService_: MyCustomService 14 | 15 | constructor( 16 | { 17 | manager, 18 | eventBusService, 19 | myCustomService, 20 | }: { 21 | manager: EntityManager; 22 | eventBusService: IEventBusService; 23 | myCustomService: MyCustomService; 24 | } 25 | ) { 26 | this.manager_ = manager; 27 | this.myCustomService_ = myCustomService; 28 | 29 | eventBusService.subscribe(OrderService.Events.PLACED, this.handleOrderPlaced); 30 | } 31 | 32 | handleOrderPlaced = async (data): Promise => { 33 | return true; 34 | } 35 | } 36 | 37 | ``` 38 | 39 | A subscriber is defined as a `class` which is registered as a subscriber by invoking `eventBusService.subscribe` in the `constructor` of the class. 40 | 41 | The type of event that the subscriber subscribes to is passed as the first parameter to the `eventBusService.subscribe` and the eventhandler is passed as the second parameter. The types of events a service can emmit are described in the individual service. 42 | 43 | An eventhandler has one parameter; a data `object` which contain information relating to the event, including relevant `id's`. The `id` can be used to fetch the appropriate entity in the eventhandler. 44 | -------------------------------------------------------------------------------- /apps/medusa-store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es5", "es6"], 4 | "target": "esnext", 5 | "allowJs": true, 6 | "esModuleInterop": false, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "skipLibCheck": true, 12 | "skipDefaultLibCheck": true, 13 | "declaration": false, 14 | "sourceMap": false, 15 | "outDir": "./dist", 16 | "rootDir": "src", 17 | "baseUrl": "src" 18 | }, 19 | "include": ["src"], 20 | "exclude": [ 21 | "**/__tests__", 22 | "**/__fixtures__", 23 | "node_modules" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /apps/medusa-store/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /apps/next/.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.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /apps/next/app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /apps/next/global.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Do not edit this file! 3 | * You should not write CSS directly when using React Native. 4 | * We are using CSS resets here to support React Native for Web and Tailwind CSS. 5 | */ 6 | 7 | @tailwind base; 8 | @tailwind components; 9 | @tailwind utilities; 10 | 11 | /** 12 | * Building on the RNWeb reset: 13 | * https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js 14 | */ 15 | html, body, #__next { 16 | width: 100%; 17 | /* To smooth any scrolling behavior */ 18 | -webkit-overflow-scrolling: touch; 19 | margin: 0px; 20 | padding: 0px; 21 | /* Allows content to fill the viewport and go beyond the bottom */ 22 | min-height: 100%; 23 | } 24 | 25 | #__next { 26 | flex-shrink: 0; 27 | flex-basis: auto; 28 | flex-direction: column; 29 | flex-grow: 1; 30 | display: flex; 31 | flex: 1; 32 | } 33 | 34 | html { 35 | /* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */ 36 | -webkit-text-size-adjust: 100%; 37 | height: 100%; 38 | } 39 | 40 | body { 41 | display: flex; 42 | /* Allows you to scroll below the viewport; default value is visible */ 43 | overflow-y: auto; 44 | overscroll-behavior-y: none; 45 | text-rendering: optimizeLegibility; 46 | -webkit-font-smoothing: antialiased; 47 | -moz-osx-font-smoothing: grayscale; 48 | -ms-overflow-style: scrollbar; 49 | } -------------------------------------------------------------------------------- /apps/next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/next/next.config.js: -------------------------------------------------------------------------------- 1 | const { withExpo } = require('@expo/next-adapter') 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | // reanimated (and thus, Moti) doesn't work with strict mode currently... 6 | // https://github.com/nandorojo/moti/issues/224 7 | // https://github.com/necolas/react-native-web/pull/2330 8 | // https://github.com/nandorojo/moti/issues/224 9 | // once that gets fixed, set this back to true 10 | reactStrictMode: false, 11 | transpilePackages: [ 12 | 'react-native', 13 | 'react-native-web', 14 | 'solito', 15 | 'dripsy', 16 | '@dripsy/core', 17 | 'moti', 18 | 'app', 19 | 'react-native-reanimated', 20 | 'nativewind', 21 | 'react-native-gesture-handler', 22 | 'react-native-safe-area-context', 23 | '@mobily/stacks', 24 | 'react-native-svg', 25 | 'expo-linear-gradient', 26 | '@backpackapp-io/react-native-toast', 27 | 'react-native-intersection-observer', 28 | ], 29 | } 30 | 31 | module.exports = withExpo(nextConfig) 32 | -------------------------------------------------------------------------------- /apps/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@expo/next-adapter": "5.0.2", 13 | "app": "*", 14 | "next": "13.2.4", 15 | "raf": "^3.4.1", 16 | "setimmediate": "^1.0.5" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "17.0.21", 20 | "autoprefixer": "^10.4.7", 21 | "eslint-config-next": "13.2.0", 22 | "tailwindcss": "^3.0.24" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/next/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import 'raf/polyfill' 2 | import 'setimmediate' 3 | const fixReanimatedIssue = () => { 4 | // FIXME remove this once this reanimated fix gets released 5 | // https://github.com/software-mansion/react-native-reanimated/issues/3355 6 | if (process.browser) { 7 | // @ts-ignore 8 | window._frameTimestamp = null 9 | } 10 | } 11 | 12 | fixReanimatedIssue() 13 | 14 | import { Provider } from 'app/provider' 15 | import Head from 'next/head' 16 | import React from 'react' 17 | 18 | import '../global.css' 19 | import { AppProps } from 'next/app' 20 | import Nav from 'app/modules/layout/templates/nav' 21 | import { MobileMenuProvider } from 'app/lib/context/mobile-menu-context' 22 | import Layout from 'app/modules/layout/templates' 23 | 24 | function MyApp({ Component, pageProps }: AppProps) { 25 | return ( 26 | <> 27 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ) 44 | } 45 | 46 | export default MyApp 47 | -------------------------------------------------------------------------------- /apps/next/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppRegistry } from 'react-native' 3 | 4 | import NextDocument, { Html, Head, Main, NextScript } from 'next/document' 5 | import type { DocumentContext } from 'next/document' 6 | 7 | class Document extends NextDocument { 8 | static async getInitialProps(ctx: DocumentContext) { 9 | AppRegistry.registerComponent('Main', () => Main) 10 | // @ts-ignore 11 | const { getStyleElement } = AppRegistry.getApplication('Main') 12 | const styles = [getStyleElement()] 13 | 14 | const initialProps = await NextDocument.getInitialProps(ctx) 15 | return { ...initialProps, styles: React.Children.toArray(styles) } 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | ) 31 | } 32 | } 33 | 34 | export default Document 35 | -------------------------------------------------------------------------------- /apps/next/pages/account/addresses.tsx: -------------------------------------------------------------------------------- 1 | import { AddressesScreen } from 'app/modules/account/addresses-screen' 2 | import AccountLayout from 'app/modules/account/templates/account-layout' 3 | import Head from 'app/modules/common/components/head' 4 | import React from 'react' 5 | 6 | const AddressesPage = () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | } 16 | export default AddressesPage 17 | -------------------------------------------------------------------------------- /apps/next/pages/account/index.tsx: -------------------------------------------------------------------------------- 1 | import { AccountScreen } from 'app/modules/account/account-screen' 2 | import { NextPageWithLayout } from 'app/types/global' 3 | import Head from 'app/modules/common/components/head' 4 | import AccountLayout from 'app/modules/account/templates/account-layout' 5 | import React from 'react' 6 | import { View } from 'app/design' 7 | 8 | const AccountPage = () => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | 19 | export default AccountPage 20 | -------------------------------------------------------------------------------- /apps/next/pages/account/login.tsx: -------------------------------------------------------------------------------- 1 | import { LoginScreen } from 'app/modules/account/login-screen' 2 | 3 | export default LoginScreen 4 | -------------------------------------------------------------------------------- /apps/next/pages/account/orders.tsx: -------------------------------------------------------------------------------- 1 | import { ProfileScreen } from 'app/modules/account/profile-screen' 2 | import AccountLayout from 'app/modules/account/templates/account-layout' 3 | import Head from 'app/modules/common/components/head' 4 | import { AddressesScreen } from 'app/modules/account/addresses-screen' 5 | import React from 'react' 6 | import { OrdersScreen } from 'app/modules/account/orders-screen' 7 | 8 | const OrdersPage = () => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | export default OrdersPage 19 | -------------------------------------------------------------------------------- /apps/next/pages/account/profile.tsx: -------------------------------------------------------------------------------- 1 | import { ProfileScreen } from 'app/modules/account/profile-screen' 2 | import AccountLayout from 'app/modules/account/templates/account-layout' 3 | import Head from 'app/modules/common/components/head' 4 | import { AddressesScreen } from 'app/modules/account/addresses-screen' 5 | import React from 'react' 6 | 7 | const ProfilePage = () => { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | export default ProfilePage 18 | -------------------------------------------------------------------------------- /apps/next/pages/cart.tsx: -------------------------------------------------------------------------------- 1 | import { CartScreen } from 'app/modules/cart/screen' 2 | 3 | export default CartScreen 4 | -------------------------------------------------------------------------------- /apps/next/pages/checkout.tsx: -------------------------------------------------------------------------------- 1 | import { CheckoutScreen } from 'app/modules/checkout/screen' 2 | 3 | export default CheckoutScreen 4 | -------------------------------------------------------------------------------- /apps/next/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { HomeScreen } from 'app/modules/home/screen' 2 | 3 | export default HomeScreen 4 | -------------------------------------------------------------------------------- /apps/next/pages/order/confirmed/[id].tsx: -------------------------------------------------------------------------------- 1 | import { OrderConfirmedScreen } from 'app/modules/order/order-confirmed-screen' 2 | 3 | export default OrderConfirmedScreen 4 | -------------------------------------------------------------------------------- /apps/next/pages/order/details/[id].tsx: -------------------------------------------------------------------------------- 1 | import { OrderDetailsScreen } from 'app/modules/order/order-details-screen' 2 | 3 | export default OrderDetailsScreen 4 | -------------------------------------------------------------------------------- /apps/next/pages/products/[handle].tsx: -------------------------------------------------------------------------------- 1 | import { ProductScreen } from 'app/modules/products/screen' 2 | import {fetchProduct} from "app/lib/hooks/use-product"; 3 | 4 | export default ProductScreen 5 | // export const getStaticProps = async ({ params }) => { 6 | // const product = await fetchProduct(params.slug) 7 | // return { 8 | // props: { 9 | // // this will get passed to pageProps 10 | // seo: { title: product.title, description: product.description }, 11 | // }, 12 | // revalidate: 1, 13 | // } 14 | // } 15 | -------------------------------------------------------------------------------- /apps/next/pages/store.tsx: -------------------------------------------------------------------------------- 1 | import { StoreScreen } from 'app/modules/store/store-screen' 2 | 3 | export default StoreScreen 4 | -------------------------------------------------------------------------------- /apps/next/pages/user/[id].tsx: -------------------------------------------------------------------------------- 1 | import { UserDetailScreen } from 'app/features/user/detail-screen' 2 | 3 | export default UserDetailScreen 4 | -------------------------------------------------------------------------------- /apps/next/plugins/swc_plugin_reanimated.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/next/plugins/swc_plugin_reanimated.wasm -------------------------------------------------------------------------------- /apps/next/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/next/public/cta_four.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/next/public/cta_four.jpg -------------------------------------------------------------------------------- /apps/next/public/cta_one.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/next/public/cta_one.jpg -------------------------------------------------------------------------------- /apps/next/public/cta_three.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/next/public/cta_three.jpg -------------------------------------------------------------------------------- /apps/next/public/cta_two.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/next/public/cta_two.jpg -------------------------------------------------------------------------------- /apps/next/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/next/public/favicon.ico -------------------------------------------------------------------------------- /apps/next/public/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/apps/next/public/hero.jpg -------------------------------------------------------------------------------- /apps/next/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /apps/next/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { theme } = require('app/design/tailwind/theme') 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: [ 6 | './pages/**/*.{js,jsx,ts,tsx}', 7 | '../../packages/**/*.{js,jsx,ts,tsx}', 8 | ], 9 | plugins: [require('nativewind/tailwind/css')], 10 | important: 'html', 11 | theme: { 12 | ...theme, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /apps/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "app-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "universal-medusa", 3 | "private": true, 4 | "workspaces": [ 5 | "apps/*", 6 | "packages/*" 7 | ], 8 | "devDependencies": { 9 | "@types/react": "^18.0.17", 10 | "@types/react-native": "^0.69.5", 11 | "eslint": "^8.21.0", 12 | "patch-package": "^7.0.2", 13 | "prettier": "^2.7.1", 14 | "prettier-plugin-tailwindcss": "^0.1.13", 15 | "turbo": "^1.4.2", 16 | "typescript": "^4.7.4" 17 | }, 18 | "scripts": { 19 | "native": "cd apps/expo && yarn start", 20 | "web": "cd apps/next && yarn next", 21 | "medusa": "cd apps/medusa-store && yarn start", 22 | "medusa:seed": "cd apps/medusa-store && yarn seed", 23 | "docs": "cd apps/docs && yarn start", 24 | "clean:node-modules": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +" 25 | }, 26 | "resolutions": { 27 | "metro": "^0.73.1", 28 | "metro-resolver": "^0.73.1", 29 | "react-native-svg": "13.4.0", 30 | "react": "18.2.0", 31 | "react-dom": "18.2.0", 32 | "react-native-reanimated": "3.1.0" 33 | }, 34 | "nohoist": [ 35 | "**/expo-router", 36 | "**/expo-router/**", 37 | "**/react-native-reanimated", 38 | "**/react-native-reanimated/**", 39 | "**/packages/**/react-native-reanimated", 40 | "**/packages/**/react-native-reanimated/**", 41 | "**/@gorhom/bottom-sheet", 42 | "**/@gorhom/bottom-sheet/**" 43 | ], 44 | "packageManager": "yarn@3.4.1", 45 | "dependencies": { 46 | "@backpackapp-io/react-native-toast": "^0.10.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/app/assets/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bidah/universal-medusa/d5db8f3f2f015c1e9ef874c3a9c31fc5e17bce1e/packages/app/assets/hero.jpg -------------------------------------------------------------------------------- /packages/app/design/image.tsx: -------------------------------------------------------------------------------- 1 | import { Image as ReactNativeImage } from 'react-native' 2 | import { styled } from 'nativewind' 3 | 4 | export const Image = styled(ReactNativeImage) 5 | -------------------------------------------------------------------------------- /packages/app/design/index.ts: -------------------------------------------------------------------------------- 1 | export * from './view' 2 | export * from './typography' 3 | export * from './layout' 4 | export * from './image' 5 | export * from './pressable' 6 | export * from './svg' 7 | -------------------------------------------------------------------------------- /packages/app/design/layout.tsx: -------------------------------------------------------------------------------- 1 | import { View } from 'react-native' 2 | import { styled } from 'nativewind' 3 | import { 4 | Stack as MobilyStacks, 5 | Tiles as MobilyTiles, 6 | Columns as MobilyColumns, 7 | Row as MobilyRow, 8 | Column as MobilyColumn, 9 | } from '@mobily/stacks' 10 | 11 | export const Stack = styled(MobilyStacks) 12 | export const Columns = styled(MobilyColumns) 13 | 14 | export const Column = styled(MobilyColumn) 15 | export const Tiles = styled(MobilyTiles) 16 | export const Row = styled(MobilyRow) 17 | -------------------------------------------------------------------------------- /packages/app/design/pressable.tsx: -------------------------------------------------------------------------------- 1 | import { Pressable as ReactNativePressable } from 'react-native' 2 | import { styled } from 'nativewind' 3 | 4 | export const Pressable = styled(ReactNativePressable) 5 | -------------------------------------------------------------------------------- /packages/app/design/svg.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Svg as ReactNativeSvg, 3 | Path as ReactNativePath, 4 | Circle as ReactNativeCircle, 5 | } from 'react-native-svg' 6 | import { styled } from 'nativewind' 7 | 8 | export const Svg = styled(ReactNativeSvg) 9 | export const Path = styled(ReactNativePath) 10 | export const Circle = styled(ReactNativeCircle) 11 | -------------------------------------------------------------------------------- /packages/app/design/tailwind/custom-css-classes.ts: -------------------------------------------------------------------------------- 1 | export const contentContainer = 2 | 'web:small:max-w-[1440px] web:small:px-8 mx-auto w-full' 3 | 4 | export const contrastBtn = 5 | 'px-4 py-2 border border-black rounded-full hover:bg-black hover:text-white transition-colors duration-200 ease-in' 6 | 7 | export const textXsmallRegular = 'text-[10px] leading-4 font-normal' 8 | 9 | export const textSmallRegular = 'text-xs leading-5 font-normal' 10 | 11 | export const textSmallSemi = 'text-xs leading-5 font-semibold' 12 | 13 | export const textBaseRegular = 'text-sm leading-6 font-normal' 14 | 15 | export const textBaseSemi = 'text-sm leading-6 font-semibold' 16 | 17 | export const textLargeRegular = 'text-base leading-6 font-normal' 18 | 19 | export const textLargeSemi = 'text-base leading-6 font-semibold' 20 | 21 | export const textXlRegular = 'text-2xl leading-[36px] font-normal' 22 | 23 | export const textXlSemi = 'text-2xl leading-[36px] font-semibold' 24 | 25 | export const text2xlRegular = 'text-[30px] leading-[48px] font-normal' 26 | 27 | export const text2xlSemi = 'text-[30px] leading-[48px] font-semibold' 28 | 29 | export const text3xlRegular = 'text-[36px] leading-[48px] font-normal' 30 | 31 | export const text3xlSemi = 'text-[36px] leading-[48px] font-semibold' 32 | -------------------------------------------------------------------------------- /packages/app/design/tailwind/theme.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('tailwindcss').Config['theme']} */ 4 | 5 | const breakPointsInPx = { 6 | '2xsmall': '320px', 7 | xsmall: '512px', 8 | small: '1024px', 9 | medium: '1280px', 10 | large: '1440px', 11 | xlarge: '1680px', 12 | '2xlarge': '1920px', 13 | } 14 | 15 | const breakPointsAsArray = Object.entries(breakPointsInPx).map( 16 | ([key, value]) => [key, parseInt(value.replace('px', ''), 10)] 17 | ) 18 | 19 | const breakPointsAsNumber = Object.keys(breakPointsInPx).reduce((acc, key) => { 20 | acc[key] = breakPointsInPx[key].replace('px', '') 21 | return acc 22 | }, {}) 23 | 24 | const theme = { 25 | extend: { 26 | transitionProperty: { 27 | width: 'width', 28 | spacing: 'margin, padding', 29 | }, 30 | maxWidth: { 31 | '8xl': '100rem', 32 | }, 33 | screens: breakPointsInPx, 34 | fontFamily: { 35 | sans: [ 36 | 'Inter', 37 | '-apple-system', 38 | 'BlinkMacSystemFont', 39 | 'Segoe UI', 40 | 'Roboto', 41 | 'Helvetica Neue', 42 | 'Ubuntu', 43 | 'sans-serif', 44 | ], 45 | }, 46 | }, 47 | } 48 | 49 | module.exports = { 50 | theme, 51 | breakPointsInPx, 52 | breakPointsAsNumber, 53 | breakPointsAsArray, 54 | } 55 | -------------------------------------------------------------------------------- /packages/app/design/typography.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, forwardRef } from 'react' 2 | import { 3 | Text as NativeText, 4 | TextStyle, 5 | TextInput as NativeTextInput, 6 | } from 'react-native' 7 | import { styled } from 'nativewind' 8 | import { TextLink as SolitoTextLink, Link as SolitoLink } from 'solito/link' 9 | 10 | export const Text = styled(NativeText) 11 | 12 | export const TextInput = styled(NativeTextInput) 13 | 14 | export const Link = styled(SolitoLink) 15 | /** 16 | * Solito's TextLink doesn't work directly with styled() since it has a textProps prop 17 | * By wrapping it in a function, we can forward style down properly. 18 | */ 19 | export const TextLink = styled< 20 | ComponentProps & { style?: TextStyle } 21 | >(function TextLink({ style, textProps, ...props }) { 22 | return ( 23 | 27 | ) 28 | }, 'text-base font-bold hover:underline text-blue-500') 29 | -------------------------------------------------------------------------------- /packages/app/design/view.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | FlatList as ReactNativeFlatList, 3 | View as ReactNativeView, 4 | ScrollView as ReactNativeScrollView, 5 | } from 'react-native' 6 | import { styled } from 'nativewind' 7 | import { Skeleton as MotiSkeleton } from 'moti/skeleton' 8 | import { MotiView as DefaultMotiView } from 'moti' 9 | 10 | export const View = styled(ReactNativeView) 11 | 12 | export const ScrollView = styled(ReactNativeScrollView, 'flex-1') 13 | 14 | export const FlatList = styled(ReactNativeFlatList) 15 | 16 | // TODO: further review integration. Nativewind className not working. 17 | export const Skeleton = styled(MotiSkeleton) 18 | 19 | export const MotiView = styled(DefaultMotiView) 20 | -------------------------------------------------------------------------------- /packages/app/index.ts: -------------------------------------------------------------------------------- 1 | // leave this blank 2 | // don't re-export files from this workspace. it'll break next.js tree shaking 3 | // https://github.com/vercel/next.js/issues/12557 4 | export {} 5 | -------------------------------------------------------------------------------- /packages/app/lib/config.ts: -------------------------------------------------------------------------------- 1 | import Medusa from '@medusajs/medusa-js' 2 | import { QueryClient } from 'react-query' 3 | 4 | // Defaults to standard port for Medusa server 5 | let MEDUSA_BACKEND_URL = 'http://localhost:9000' 6 | // let MEDUSA_BACKEND_URL = 'http://universal-medusa-server.ngrok.io' 7 | 8 | //TODO: setup .env config for monorepo 9 | // if (process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL) { 10 | // MEDUSA_BACKEND_URL = process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL 11 | // } 12 | 13 | const queryClient = new QueryClient({ 14 | defaultOptions: { 15 | queries: { 16 | refetchOnWindowFocus: false, 17 | staleTime: 1000 * 60 * 60 * 24, 18 | retry: 1, 19 | }, 20 | }, 21 | }) 22 | 23 | const medusaClient = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 }) 24 | 25 | export { MEDUSA_BACKEND_URL, queryClient, medusaClient } 26 | -------------------------------------------------------------------------------- /packages/app/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const IS_BROWSER = typeof window !== "undefined" 2 | -------------------------------------------------------------------------------- /packages/app/lib/context/cart-dropdown-context.tsx: -------------------------------------------------------------------------------- 1 | import useToggleState from 'app/lib/hooks/use-toggle-state' 2 | import { createContext, useContext, useEffect, useState } from 'react' 3 | 4 | interface CartDropdownContext { 5 | state: boolean 6 | open: () => void 7 | timedOpen: () => void 8 | close: () => void 9 | } 10 | 11 | export const CartDropdownContext = createContext( 12 | null 13 | ) 14 | 15 | export const CartDropdownProvider = ({ 16 | children, 17 | }: { 18 | children: React.ReactNode 19 | }) => { 20 | const { state, close, open } = useToggleState() 21 | const [activeTimer, setActiveTimer] = useState( 22 | undefined 23 | ) 24 | 25 | const timedOpen = () => { 26 | open() 27 | 28 | const timer = setTimeout(close, 5000) 29 | 30 | setActiveTimer(timer) 31 | } 32 | 33 | const openAndCancel = () => { 34 | if (activeTimer) { 35 | clearTimeout(activeTimer) 36 | } 37 | 38 | open() 39 | } 40 | 41 | // Clean up the timer when the component unmounts 42 | useEffect(() => { 43 | return () => { 44 | if (activeTimer) { 45 | clearTimeout(activeTimer) 46 | } 47 | } 48 | }, [activeTimer]) 49 | 50 | return ( 51 | 54 | {children} 55 | 56 | ) 57 | } 58 | 59 | export const useCartDropdown = () => { 60 | const context = useContext(CartDropdownContext) 61 | 62 | if (context === null) { 63 | throw new Error( 64 | 'useCartDropdown must be used within a CartDropdownProvider' 65 | ) 66 | } 67 | 68 | return context 69 | } 70 | -------------------------------------------------------------------------------- /packages/app/lib/context/modal-context.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from "react" 2 | 3 | interface ModalContext { 4 | close: () => void 5 | } 6 | 7 | const ModalContext = createContext(null) 8 | 9 | interface ModalProviderProps { 10 | children?: React.ReactNode 11 | close: () => void 12 | } 13 | 14 | export const ModalProvider = ({ children, close }: ModalProviderProps) => { 15 | return ( 16 | 21 | {children} 22 | 23 | ) 24 | } 25 | 26 | export const useModal = () => { 27 | const context = useContext(ModalContext) 28 | if (context === null) { 29 | throw new Error("useModal must be used within a ModalProvider") 30 | } 31 | return context 32 | } 33 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-country-options.tsx: -------------------------------------------------------------------------------- 1 | import { useRegions } from "medusa-react" 2 | import { useMemo } from "react" 3 | 4 | type CountryOption = { 5 | country: string 6 | region: string 7 | label: string 8 | } 9 | 10 | const useCountryOptions = () => { 11 | const { regions } = useRegions() 12 | 13 | const options: CountryOption[] | undefined = useMemo(() => { 14 | return regions 15 | ?.map((r) => { 16 | return r.countries.map((c) => ({ 17 | country: c.iso_2, 18 | region: r.id, 19 | label: c.display_name, 20 | })) 21 | }) 22 | .flat() 23 | }, [regions]) 24 | 25 | return options 26 | } 27 | 28 | export default useCountryOptions 29 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-current-width.tsx: -------------------------------------------------------------------------------- 1 | import { IS_BROWSER } from 'app/lib/constants' 2 | import { useEffect, useState } from 'react' 3 | 4 | const getWidth = () => { 5 | if (IS_BROWSER) { 6 | return ( 7 | window.innerWidth || 8 | document.documentElement.clientWidth || 9 | document.body.clientWidth 10 | ) 11 | } 12 | 13 | return 0 14 | } 15 | 16 | const useCurrentWidth = () => { 17 | const [width, setWidth] = useState(getWidth()) 18 | 19 | useEffect(() => { 20 | const resizeListener = () => { 21 | setWidth(getWidth()) 22 | } 23 | 24 | window.addEventListener('resize', resizeListener) 25 | 26 | return () => { 27 | window.removeEventListener('resize', resizeListener) 28 | } 29 | }, []) 30 | 31 | return width 32 | } 33 | 34 | export default useCurrentWidth 35 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-debounce.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | const useDebounce = (value: T, delay: number) => { 4 | const [debouncedValue, setDebouncedValue] = useState(value) 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value) 9 | }, delay) 10 | 11 | return () => { 12 | clearTimeout(handler) 13 | } 14 | }, [value, delay]) 15 | return debouncedValue 16 | } 17 | export default useDebounce 18 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-in-view.tsx: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useState } from "react" 2 | 3 | export const useIntersection = ( 4 | element: RefObject, 5 | rootMargin: string 6 | ) => { 7 | const [isVisible, setState] = useState(false) 8 | 9 | useEffect(() => { 10 | if (!element.current) { 11 | return 12 | } 13 | 14 | const el = element.current 15 | 16 | const observer = new IntersectionObserver( 17 | ([entry]) => { 18 | setState(entry.isIntersecting) 19 | }, 20 | { rootMargin } 21 | ) 22 | 23 | observer.observe(el) 24 | 25 | return () => observer.unobserve(el) 26 | }, [element, rootMargin]) 27 | 28 | return isVisible 29 | } 30 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-previews.tsx: -------------------------------------------------------------------------------- 1 | import transformProductPreview from 'app/lib/util/transform-product-preview' 2 | import { Product, Region } from '@medusajs/medusa' 3 | import { useMemo } from 'react' 4 | import { InfiniteProductPage, ProductPreviewType } from 'app/types/global' 5 | 6 | type UsePreviewProps = { 7 | pages?: T[] 8 | region?: Region 9 | } 10 | 11 | const usePreviews = ({ 12 | pages, 13 | region, 14 | }: UsePreviewProps) => { 15 | const previews: ProductPreviewType[] = useMemo(() => { 16 | if (!pages || !region) { 17 | return [] 18 | } 19 | 20 | const products: Product[] = [] 21 | 22 | for (const page of pages) { 23 | products.push(...page.response.products) 24 | } 25 | 26 | const transformedProducts = products.map((p) => 27 | transformProductPreview(p, region) 28 | ) 29 | 30 | return transformedProducts 31 | }, [pages, region]) 32 | 33 | return previews 34 | } 35 | 36 | export default usePreviews 37 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-product.tsx: -------------------------------------------------------------------------------- 1 | import { medusaClient } from '../config' 2 | import { useQuery } from 'react-query' 3 | 4 | export const fetchProduct = async (handle: string) => { 5 | return await medusaClient.products 6 | .list({ handle }) 7 | .then(({ products }) => products[0]) 8 | } 9 | 10 | const useProduct = (handle) => { 11 | const { data, isError, isLoading, isSuccess } = useQuery( 12 | [`get_product`, handle], 13 | () => fetchProduct(handle), 14 | { 15 | enabled: handle.length > 0, 16 | keepPreviousData: true, 17 | } 18 | ) 19 | 20 | return { data, isError, isLoading, isSuccess } 21 | } 22 | 23 | export default useProduct 24 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-toggle-state.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | export type StateType = [boolean, () => void, () => void, () => void] & { 4 | state: boolean 5 | open: () => void 6 | close: () => void 7 | toggle: () => void 8 | } 9 | 10 | /** 11 | * 12 | * @param initialState - boolean 13 | * @returns An array like object with `state`, `open`, `close`, and `toggle` properties 14 | * to allow both object and array destructuring 15 | * 16 | * ``` 17 | * const [showModal, openModal, closeModal, toggleModal] = useToggleState() 18 | * // or 19 | * const { state, open, close, toggle } = useToggleState() 20 | * ``` 21 | */ 22 | 23 | const useToggleState = (initialState = false) => { 24 | const [state, setState] = useState(initialState) 25 | 26 | const close = () => { 27 | setState(false) 28 | } 29 | 30 | const open = () => { 31 | setState(true) 32 | } 33 | 34 | const toggle = () => { 35 | setState((state) => !state) 36 | } 37 | 38 | const hookData = [state, open, close, toggle] as StateType 39 | hookData.state = state 40 | hookData.open = open 41 | hookData.close = close 42 | hookData.toggle = toggle 43 | return hookData 44 | } 45 | 46 | export default useToggleState 47 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-universal-pathname/index.tsx: -------------------------------------------------------------------------------- 1 | export { useUniversalPathname } from './useUniversalPathname' 2 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-universal-pathname/useUniversalPathname.tsx: -------------------------------------------------------------------------------- 1 | import { usePathname } from 'expo-router' 2 | 3 | export function useUniversalPathname() { 4 | const pathname = usePathname() 5 | 6 | return pathname 7 | } 8 | -------------------------------------------------------------------------------- /packages/app/lib/hooks/use-universal-pathname/useUniversalPathname.web.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | 3 | export function useUniversalPathname() { 4 | const router = useRouter() 5 | const pathname = router.pathname 6 | 7 | return pathname 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/lib/util/can-buy.ts: -------------------------------------------------------------------------------- 1 | import { ProductVariant } from "@medusajs/medusa" 2 | 3 | export const canBuy = (variant: Omit) => { 4 | return variant.inventory_quantity > 0 || variant.allow_backorder === true 5 | } 6 | -------------------------------------------------------------------------------- /packages/app/lib/util/get-collection-ids.ts: -------------------------------------------------------------------------------- 1 | import { medusaClient } from "../config" 2 | 3 | export const getCollectionIds = async (): Promise => { 4 | const data = await medusaClient.collections 5 | .list({ limit: 100 }) 6 | .then(({ collections }) => { 7 | return collections.map(({ id }) => id) 8 | }) 9 | 10 | return data 11 | } 12 | -------------------------------------------------------------------------------- /packages/app/lib/util/get-number-of-skeletons.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculates the number of spooky skeletons to show while an infinite scroll is loading the next page. 3 | * Per default we fetch 12 products per page, so we need to calculate the number of skeletons to show, 4 | * if the remaing products are less than 12. 5 | */ 6 | 7 | import { InfiniteProductPage } from "types/global" 8 | 9 | const getNumberOfSkeletons = (pages?: InfiniteProductPage[]) => { 10 | if (!pages) { 11 | return 0 12 | } 13 | 14 | const count = pages[pages.length - 1].response.count 15 | const retrieved = 16 | count - pages.reduce((acc, curr) => acc + curr.response.products.length, 0) 17 | 18 | if (count - retrieved < 12) { 19 | return count - retrieved 20 | } 21 | 22 | return 12 23 | } 24 | 25 | export default getNumberOfSkeletons 26 | -------------------------------------------------------------------------------- /packages/app/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 | -------------------------------------------------------------------------------- /packages/app/lib/util/get-product-handles.ts: -------------------------------------------------------------------------------- 1 | import { medusaClient } from "../config" 2 | 3 | export const getProductHandles = async (): Promise => { 4 | const products = await medusaClient.products 5 | .list({ limit: 25 }) 6 | .then(({ products }) => products) 7 | 8 | const handles: string[] = [] 9 | 10 | for (const product of products) { 11 | if (product.handle) { 12 | handles.push(product.handle) 13 | } 14 | } 15 | 16 | return handles 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/lib/util/handle-error.ts: -------------------------------------------------------------------------------- 1 | export const handleError = (error: Error) => { 2 | if (process.env.NODE_ENV === "development") { 3 | console.error(error) 4 | } 5 | 6 | // TODO: user facing error message 7 | } 8 | -------------------------------------------------------------------------------- /packages/app/lib/util/noop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A funtion that does nothing. 3 | */ 4 | const noop = () => {} 5 | 6 | export default noop 7 | -------------------------------------------------------------------------------- /packages/app/lib/util/only-unique.ts: -------------------------------------------------------------------------------- 1 | export const onlyUnique = (value: unknown, index: number, self: unknown[]) => 2 | self.indexOf(value) === index 3 | -------------------------------------------------------------------------------- /packages/app/lib/util/regex.ts: -------------------------------------------------------------------------------- 1 | export const emailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/g -------------------------------------------------------------------------------- /packages/app/lib/util/repeat.ts: -------------------------------------------------------------------------------- 1 | const repeat = (times: number) => { 2 | return Array.from(Array(times).keys()) 3 | } 4 | 5 | export default repeat 6 | -------------------------------------------------------------------------------- /packages/app/lib/util/transform-product-preview.ts: -------------------------------------------------------------------------------- 1 | import { getPercentageDiff } from 'app/lib/util/get-precentage-diff' 2 | import { Product, Region } from '@medusajs/medusa' 3 | import { formatAmount } from 'medusa-react' 4 | import { ProductPreviewType } from 'app/types/global' 5 | import { CalculatedVariant } from 'app/types/medusa' 6 | 7 | const transformProductPreview = ( 8 | product: Product, 9 | region: Region 10 | ): ProductPreviewType => { 11 | const variants = product.variants as CalculatedVariant[] 12 | 13 | const cheapestVariant = variants.reduce((acc, curr) => { 14 | if (acc.calculated_price > curr.calculated_price) { 15 | return curr 16 | } 17 | return acc 18 | }, variants[0]) 19 | 20 | return { 21 | id: product.id, 22 | title: product.title, 23 | handle: product.handle, 24 | thumbnail: product.thumbnail, 25 | price: { 26 | calculated_price: formatAmount({ 27 | amount: cheapestVariant.calculated_price, 28 | region: region, 29 | includeTaxes: false, 30 | }), 31 | original_price: formatAmount({ 32 | amount: cheapestVariant.original_price, 33 | region: region, 34 | includeTaxes: false, 35 | }), 36 | difference: getPercentageDiff( 37 | cheapestVariant.original_price, 38 | cheapestVariant.calculated_price 39 | ), 40 | price_type: cheapestVariant.calculated_price_type, 41 | }, 42 | } 43 | } 44 | 45 | export default transformProductPreview 46 | -------------------------------------------------------------------------------- /packages/app/modules/account/account-screen.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, View, Text } from 'app/design' 2 | import OverviewTemplate from './templates/overview-template' 3 | 4 | export const AccountScreen = () => { 5 | return ( 6 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/modules/account/addresses-screen.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, Text } from 'app/design' 2 | import AddressesTemplate from './templates/addresses-template' 3 | 4 | export const AddressesScreen = () => { 5 | return ( 6 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/modules/account/components/address-book/index.tsx: -------------------------------------------------------------------------------- 1 | import { Customer } from '@medusajs/medusa' 2 | import React from 'react' 3 | import { View, Text } from 'app/design' 4 | 5 | import AddAddress from '../address-card/add-address' 6 | import EditAddress from '../address-card/edit-address-modal' 7 | 8 | type AddressBookProps = { 9 | customer: Omit 10 | } 11 | 12 | const AddressBook: React.FC = ({ customer }) => { 13 | return ( 14 | 15 | 16 | {customer.shipping_addresses.map((address) => { 17 | return 18 | })} 19 | 20 | ) 21 | } 22 | 23 | export default AddressBook 24 | -------------------------------------------------------------------------------- /packages/app/modules/account/components/address-card/remove-address-dialog.tsx: -------------------------------------------------------------------------------- 1 | import { View } from 'app/design' 2 | import Dialog from 'react-native-dialog' 3 | const RemoveAddressDialog = ({ 4 | setShowDialog, 5 | showDialog = false, 6 | removeAddress, 7 | }: { 8 | setShowDialog: () => void 9 | showDialog: boolean 10 | removeAddress: () => void 11 | }) => { 12 | const handleCancel = () => { 13 | setShowDialog(false) 14 | } 15 | 16 | const handleDelete = () => { 17 | removeAddress() 18 | setShowDialog(false) 19 | } 20 | return ( 21 | 22 | 23 | Remove Address 24 | 25 | Do you want to delete addresss? You cannot undo this action. 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | export default RemoveAddressDialog 35 | -------------------------------------------------------------------------------- /packages/app/modules/account/components/detail-container/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {View, Text} from 'app/design' 3 | 4 | 5 | type DetailProps = { 6 | title: string 7 | } 8 | 9 | type SubDetailProps = { 10 | title?: string 11 | } 12 | 13 | const Detail: React.FC & { 14 | SubDetail: React.FC 15 | } = ({ title, children }) => { 16 | return ( 17 | 18 | {title} 19 | 20 | {children} 21 | 22 | 23 | ) 24 | } 25 | 26 | const SubDetail: React.FC = ({ title, children }) => { 27 | return ( 28 | 29 | {title && {title}} 30 | {children} 31 | 32 | ) 33 | } 34 | 35 | Detail.SubDetail = SubDetail 36 | 37 | export default Detail 38 | -------------------------------------------------------------------------------- /packages/app/modules/account/components/edit-button/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Pressable, Text } from 'app/design' 3 | 4 | const EditButton: React.FC = (props) => { 5 | return ( 6 | 7 | 8 | Edit 9 | 10 | 11 | ) 12 | } 13 | 14 | export default EditButton 15 | -------------------------------------------------------------------------------- /packages/app/modules/account/components/login-details/index.tsx: -------------------------------------------------------------------------------- 1 | import { Customer } from '@medusajs/medusa' 2 | import React from 'react' 3 | import { View, Text } from 'app/design' 4 | 5 | import Detail from '../detail-container' 6 | import EditEmailModal from './edit-email-modal' 7 | import EditPasswordModal from './edit-password-modal' 8 | 9 | type LoginDetailsProps = { 10 | customer: Omit 11 | } 12 | 13 | const LoginDetails: React.FC = ({ customer }) => { 14 | return ( 15 | 16 | 17 | 18 | {customer.email} 19 | 20 | 21 | 22 | ••••••••••• 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | export default LoginDetails 31 | -------------------------------------------------------------------------------- /packages/app/modules/account/components/order-overview/index.tsx: -------------------------------------------------------------------------------- 1 | import Button from 'app/modules/common/components/button' 2 | import Spinner from 'app/modules/common/icons/spinner' 3 | import { useCustomerOrders } from 'medusa-react' 4 | import OrderCard from '../order-card' 5 | import { Text, View, Link } from 'app/design' 6 | import { 7 | textBaseRegular, 8 | textLargeSemi, 9 | } from '../../../../design/tailwind/custom-css-classes' 10 | import { useRouter } from 'solito/router' 11 | 12 | const OrderOverview = () => { 13 | const router = useRouter() 14 | const { orders, isLoading } = useCustomerOrders() 15 | 16 | if (isLoading) { 17 | return ( 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | if (orders?.length) { 25 | return ( 26 | 27 | {orders.map((o) => ( 28 | 32 | 33 | 34 | ))} 35 | 36 | ) 37 | } 38 | 39 | return ( 40 | 41 | Nothing to see here 42 | 43 | You don't have any orders yet, let us change that {':)'} 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default OrderOverview 53 | -------------------------------------------------------------------------------- /packages/app/modules/account/login-screen.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, View, Text } from 'app/design' 2 | import LoginTemplate from './templates/login-template' 3 | 4 | export const LoginScreen = () => { 5 | return ( 6 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/modules/account/orders-screen.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, View, Text } from 'app/design' 2 | import OrderDetails from './templates/orders-template' 3 | 4 | export const OrdersScreen = () => { 5 | return ( 6 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/modules/account/profile-screen.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, Text } from 'app/design' 2 | import ProfileTemplate from './templates/profile-template' 3 | 4 | export const ProfileScreen = () => { 5 | return ( 6 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/modules/account/templates/addresses-template.tsx: -------------------------------------------------------------------------------- 1 | import { useAccount } from 'app/lib/context/account-context' 2 | import AddressBook from '../components/address-book' 3 | import { View, Pressable, Text } from 'app/design' 4 | import { 5 | text2xlSemi, 6 | textBaseRegular, 7 | } from 'app/design/tailwind/custom-css-classes' 8 | 9 | const AddressesTemplate = () => { 10 | const { customer, retrievingCustomer } = useAccount() 11 | 12 | if (retrievingCustomer || !customer) { 13 | return null 14 | } 15 | 16 | return ( 17 | 18 | 19 | Shipping Addresses 20 | 21 | View and update your shipping addresses, you can add as many as you 22 | like. Saving your addresses will make them available during checkout. 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | export default AddressesTemplate 31 | -------------------------------------------------------------------------------- /packages/app/modules/account/templates/login-template.tsx: -------------------------------------------------------------------------------- 1 | import { useAccount } from 'app/lib/context/account-context' 2 | import Register from 'app/modules/account/components/register' 3 | import { useRouter } from 'solito/router' 4 | import { useEffect } from 'react' 5 | import { View, Text } from 'app/design' 6 | import Login from '../components/login' 7 | 8 | const LoginTemplate = () => { 9 | const { loginView, customer, retrievingCustomer } = useAccount() 10 | const [currentView, _] = loginView 11 | 12 | const router = useRouter() 13 | 14 | useEffect(() => { 15 | if (!retrievingCustomer && customer) { 16 | router.push('/account') 17 | } 18 | }, [customer, retrievingCustomer, router]) 19 | 20 | return ( 21 | 22 | {currentView === 'sign-in' ? : } 23 | 24 | ) 25 | } 26 | 27 | export default LoginTemplate 28 | -------------------------------------------------------------------------------- /packages/app/modules/account/templates/order-details-template.tsx: -------------------------------------------------------------------------------- 1 | import OrderCompletedTemplate from 'app/modules/order/templates/order-completed-template' 2 | import { useOrder } from 'medusa-react' 3 | import { useRouter } from 'next/router' 4 | import { Text } from '../../../design' 5 | 6 | const OrderDetailsTemplate = () => { 7 | const router = useRouter() 8 | 9 | const { order } = router.query 10 | 11 | const { order: details, isLoading } = useOrder(order as string, { 12 | enabled: !!order, 13 | }) 14 | 15 | if (isLoading || !details) { 16 | return Loading... 17 | } 18 | 19 | return 20 | } 21 | 22 | export default OrderDetailsTemplate 23 | -------------------------------------------------------------------------------- /packages/app/modules/account/templates/orders-template.tsx: -------------------------------------------------------------------------------- 1 | import OrderOverview from '../components/order-overview' 2 | import { View, Text } from 'app/design' 3 | import { 4 | text2xlSemi, 5 | textBaseRegular, 6 | } from '../../../design/tailwind/custom-css-classes' 7 | 8 | const OrdersTemplate = () => { 9 | return ( 10 | 11 | 12 | Orders 13 | 14 | View your previous orders and their status. You can also create 15 | returns or exchanges for your orders if needed. 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default OrdersTemplate 26 | -------------------------------------------------------------------------------- /packages/app/modules/account/templates/overview-template.tsx: -------------------------------------------------------------------------------- 1 | import Overview from 'app/modules/account/components/overview' 2 | import { useCustomerOrders, useMeCustomer } from 'medusa-react' 3 | 4 | const OverviewTemplate = () => { 5 | const { orders } = useCustomerOrders() 6 | const { customer } = useMeCustomer() 7 | 8 | return 9 | } 10 | 11 | export default OverviewTemplate 12 | -------------------------------------------------------------------------------- /packages/app/modules/account/templates/profile-template.tsx: -------------------------------------------------------------------------------- 1 | import { useAccount } from 'app/lib/context/account-context' 2 | import ProfileEmail from 'app/modules/account/components/profile-email' 3 | import ProfileName from 'app/modules/account/components/profile-name' 4 | import ProfilePassword from 'app/modules/account/components/profile-password' 5 | import ProfileBillingAddress from '../components/profile-billing-address' 6 | import ProfilePhone from '../components/profile-phone' 7 | import { View, Pressable, Text, Stack } from 'app/design' 8 | import { text2xlSemi } from '../../../design/tailwind/custom-css-classes' 9 | 10 | const ProfileTemplate = () => { 11 | const { customer, retrievingCustomer, refetchCustomer } = useAccount() 12 | 13 | if (retrievingCustomer || !customer) { 14 | return null 15 | } 16 | 17 | return ( 18 | 19 | 20 | Profile 21 | 22 | View and update your profile information, including your name, email, 23 | and phone number. You can also update your billing address, or change 24 | your password. 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | const Divider = () => { 43 | return 44 | } 45 | 46 | export default ProfileTemplate 47 | -------------------------------------------------------------------------------- /packages/app/modules/cart/components/empty-cart-message/index.tsx: -------------------------------------------------------------------------------- 1 | import UnderlineLink from 'app/modules/common/components/underline-link' 2 | import { View, Text } from 'app/design' 3 | import { 4 | text2xlSemi, 5 | textBaseRegular, 6 | } from '../../../../design/tailwind/custom-css-classes' 7 | 8 | const EmptyCartMessage = () => { 9 | return ( 10 | 11 | 12 | Your shopping bag is empty 13 | 14 | 17 | You don't have anything in your bag. Let's change that, use 18 | the link below to start browsing our products. 19 | 20 | 21 | Explore products 22 | 23 | 24 | ) 25 | } 26 | 27 | export default EmptyCartMessage 28 | -------------------------------------------------------------------------------- /packages/app/modules/cart/components/sign-in-prompt/index.tsx: -------------------------------------------------------------------------------- 1 | import Button from 'app/modules/common/components/button' 2 | import { View, Text, Link } from 'app/design' 3 | import { 4 | textBaseRegular, 5 | textXlSemi, 6 | } from '../../../../design/tailwind/custom-css-classes' 7 | import { useRouter } from 'solito/router' 8 | 9 | const SignInPrompt = () => { 10 | const { push } = useRouter() 11 | return ( 12 | 13 | 14 | Already have an account? 15 | 16 | Sign in for a better experience. 17 | 18 | 19 | 20 | 23 | 24 | 25 | ) 26 | } 27 | 28 | export default SignInPrompt 29 | -------------------------------------------------------------------------------- /packages/app/modules/cart/screen.tsx: -------------------------------------------------------------------------------- 1 | import CartTemplate from 'app/modules/cart/templates' 2 | import { ScrollView, View, Text } from 'app/design' 3 | 4 | export const CartScreen = () => { 5 | return ( 6 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/app/modules/cart/templates/items.tsx: -------------------------------------------------------------------------------- 1 | import { LineItem, Region } from '@medusajs/medusa' 2 | import Item from 'app/modules/cart/components/item' 3 | import SkeletonLineItem from 'app/modules/skeletons/components/skeleton-line-item' 4 | import { Text, View, Stack } from 'app/design' 5 | import { textXlSemi } from '../../../design/tailwind/custom-css-classes' 6 | import { Platform } from 'react-native' 7 | 8 | type ItemsTemplateProps = { 9 | items?: Omit[] 10 | region?: Region 11 | } 12 | 13 | const ItemsTemplate = ({ items, region }: ItemsTemplateProps) => { 14 | return ( 15 | 16 | {Platform.OS === 'web' && ( 17 | 22 | Shopping Bag 23 | 24 | )} 25 | 26 | {items && region 27 | ? items 28 | .sort((a, b) => { 29 | return a.created_at > b.created_at ? -1 : 1 30 | }) 31 | .map((item) => { 32 | return 33 | }) 34 | : Array.from(Array(5).keys()).map((i) => { 35 | return 36 | })} 37 | 38 | 39 | ) 40 | } 41 | 42 | export default ItemsTemplate 43 | -------------------------------------------------------------------------------- /packages/app/modules/cart/templates/summary.tsx: -------------------------------------------------------------------------------- 1 | import { Cart } from '@medusajs/medusa' 2 | import Button from 'app/modules/common/components/button' 3 | import CartTotals from 'app/modules/common/components/cart-totals' 4 | import { Link, Stack } from 'app/design' 5 | import { useRouter } from 'solito/router' 6 | 7 | type SummaryProps = { 8 | cart: Omit 9 | } 10 | 11 | const Summary = ({ cart }: SummaryProps) => { 12 | const { push } = useRouter() 13 | return ( 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default Summary 22 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/components/checkout-loader/index.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from '@headlessui/react' 2 | import { useCheckout } from 'app/lib/context/checkout-context' 3 | import noop from 'app/lib/util/noop' 4 | import Spinner from 'app/modules/common/icons/spinner' 5 | 6 | const CheckoutLoader = () => { 7 | const { isLoading } = useCheckout() 8 | 9 | return ( 10 | 11 | 12 | 20 |
21 | 22 |
23 |
24 |
25 |
26 | ) 27 | } 28 | 29 | export default CheckoutLoader 30 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/components/country-select/index.web.tsx: -------------------------------------------------------------------------------- 1 | import NativeSelect, { 2 | NativeSelectProps, 3 | } from 'app/modules/common/components/native-select' 4 | import { useCart, useRegions } from 'medusa-react' 5 | import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react' 6 | 7 | const CountrySelect = forwardRef( 8 | ({ placeholder = 'Country', ...props }, ref) => { 9 | const innerRef = useRef(null) 10 | 11 | useImperativeHandle( 12 | ref, 13 | () => innerRef.current 14 | ) 15 | 16 | const { regions } = useRegions() 17 | const { cart } = useCart() 18 | 19 | const countryOptions = useMemo(() => { 20 | const currentRegion = regions?.find((r) => r.id === cart?.region_id) 21 | 22 | if (!currentRegion) { 23 | return [] 24 | } 25 | 26 | return currentRegion.countries.map((country) => ({ 27 | value: country.iso_2, 28 | label: country.display_name, 29 | })) 30 | }, [regions, cart]) 31 | 32 | return ( 33 | 34 | {countryOptions.map(({ value, label }, index) => ( 35 | 38 | ))} 39 | 40 | ) 41 | } 42 | ) 43 | 44 | CountrySelect.displayName = 'CountrySelect' 45 | 46 | export default CountrySelect 47 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/components/payment-test/index.tsx: -------------------------------------------------------------------------------- 1 | import Alert from 'app/modules/common/icons/alert' 2 | import {View,Text} from 'app/design' 3 | 4 | const PaymentTest = () => { 5 | return ( 6 | 7 | 8 | {/**/} 9 | 10 | Attention: For testing purposes 11 | only. 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | export default PaymentTest 19 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/components/payment-wrapper/index.tsx: -------------------------------------------------------------------------------- 1 | const Wrapper: React.FC = ({ children }) => { 2 | return <>{children} 3 | } 4 | 5 | export default Wrapper 6 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/components/payment-wrapper/index.web.tsx: -------------------------------------------------------------------------------- 1 | import { PaymentSession } from '@medusajs/medusa' 2 | import { Elements } from '@stripe/react-stripe-js' 3 | import { loadStripe, StripeElementsOptions } from '@stripe/stripe-js' 4 | import React from 'react' 5 | 6 | type WrapperProps = { 7 | paymentSession?: PaymentSession | null 8 | } 9 | 10 | const Wrapper: React.FC = ({ paymentSession, children }) => { 11 | if (!paymentSession) { 12 | return
{children}
13 | } 14 | 15 | switch (paymentSession.provider_id) { 16 | case 'stripe': 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | 23 | default: 24 | return
{children}
25 | } 26 | } 27 | 28 | const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY || '') 29 | 30 | const StripeWrapper: React.FC = ({ 31 | paymentSession, 32 | children, 33 | }) => { 34 | const options: StripeElementsOptions = { 35 | clientSecret: paymentSession!.data.client_secret as string | undefined, 36 | } 37 | 38 | return ( 39 | 40 | {children} 41 | 42 | ) 43 | } 44 | 45 | export default Wrapper 46 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/screen.tsx: -------------------------------------------------------------------------------- 1 | import CartTemplate from 'app/modules/cart/templates' 2 | import { ScrollView, View, Text } from 'app/design' 3 | import CheckoutTemplate from './templates' 4 | 5 | export const CheckoutScreen = () => { 6 | return ( 7 | 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/templates/checkout-form/index.tsx: -------------------------------------------------------------------------------- 1 | import Addresses from 'app/modules/checkout/components/addresses' 2 | import Payment from 'app/modules/checkout/components/payment' 3 | import Shipping from 'app/modules/checkout/components/shipping' 4 | import { useCart } from 'medusa-react' 5 | import { Text, View } from '../../../../design' 6 | 7 | const CheckoutForm = () => { 8 | const { cart } = useCart() 9 | 10 | if (!cart?.id) { 11 | return null 12 | } 13 | 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } 32 | 33 | export default CheckoutForm 34 | -------------------------------------------------------------------------------- /packages/app/modules/checkout/templates/checkout-summary/index.tsx: -------------------------------------------------------------------------------- 1 | import DiscountCode from 'app/modules/checkout/components/discount-code' 2 | import GiftCard from 'app/modules/checkout/components/gift-card' 3 | import PaymentButton from 'app/modules/checkout/components/payment-button' 4 | import CartTotals from 'app/modules/common/components/cart-totals' 5 | import { useCart } from 'medusa-react' 6 | import {View, Pressable, Text} from 'app/design' 7 | 8 | 9 | const CheckoutSummary = () => { 10 | const { cart } = useCart() 11 | 12 | if (!cart?.id) { 13 | return null 14 | } 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | {/**/} 24 | 25 | {/**/} 26 | 27 | ) 28 | } 29 | 30 | export default CheckoutSummary 31 | -------------------------------------------------------------------------------- /packages/app/modules/common/components/checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Pressable, Text, View } from '../../../../design' 3 | import { textBaseRegular } from '../../../../design/tailwind/custom-css-classes' 4 | 5 | type CheckboxProps = { 6 | checked?: boolean 7 | onChange?: () => void 8 | label: string 9 | } 10 | 11 | const Checkbox: React.FC = ({ 12 | checked = false, 13 | onChange, 14 | label, 15 | }) => { 16 | return ( 17 | 23 | 27 | {checked ? '✓' : null} 28 | 29 | {label} 30 | 31 | ) 32 | } 33 | 34 | export default Checkbox 35 | -------------------------------------------------------------------------------- /packages/app/modules/common/components/connect-form/index.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from "react" 2 | import type { FieldValues, UseFormReturn } from "react-hook-form" 3 | import { useFormContext } from "react-hook-form" 4 | 5 | interface ConnectFormProps { 6 | children(children: UseFormReturn): ReactElement 7 | } 8 | 9 | /** 10 | * Utility component for nested forms. 11 | */ 12 | const ConnectForm = ({ 13 | children, 14 | }: ConnectFormProps) => { 15 | const methods = useFormContext() 16 | 17 | return children({ ...methods }) 18 | } 19 | 20 | export default ConnectForm 21 | -------------------------------------------------------------------------------- /packages/app/modules/common/components/hamburger/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import React from 'react' 3 | import { Pressable, Text, View } from 'app/design' 4 | 5 | type HamburgerProps = { 6 | setOpen: () => void 7 | } 8 | 9 | const Hamburger: React.FC = ({ setOpen }) => { 10 | return ( 11 | 15 | Open main menu 16 | 17 | 23 | 29 | 35 | 36 | 37 | ) 38 | } 39 | 40 | export default Hamburger 41 | -------------------------------------------------------------------------------- /packages/app/modules/common/components/head/index.tsx: -------------------------------------------------------------------------------- 1 | import NextHead from "next/head" 2 | import React from "react" 3 | 4 | type HeadProps = { 5 | title?: string 6 | description?: string | null 7 | image?: string | null 8 | } 9 | 10 | const Head: React.FC = ({ title, description, image }) => { 11 | return ( 12 | 13 | {title} | ACME 14 | 15 | {description && } 16 | {image && } 17 | 18 | 19 | ) 20 | } 21 | 22 | export default Head 23 | -------------------------------------------------------------------------------- /packages/app/modules/common/components/line-item-options/index.tsx: -------------------------------------------------------------------------------- 1 | import { ProductVariant } from '@medusajs/medusa' 2 | import { View, Text } from 'app/design' 3 | import { textSmallRegular } from 'app/design/tailwind/custom-css-classes' 4 | 5 | type LineItemOptionsProps = { variant: ProductVariant } 6 | 7 | const LineItemOptions = ({ variant }: LineItemOptionsProps) => { 8 | return ( 9 | 10 | {variant.options.map((option) => { 11 | const optionName = 12 | variant.product.options.find((opt) => opt.id === option.option_id) 13 | ?.title || 'Option' 14 | return ( 15 | 16 | 17 | {optionName}: {option.value} 18 | 19 | 20 | ) 21 | })} 22 | 23 | ) 24 | } 25 | 26 | export default LineItemOptions 27 | -------------------------------------------------------------------------------- /packages/app/modules/common/components/radio/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { View } from 'app/design' 3 | 4 | const Radio = ({ checked }: { checked: boolean }) => { 5 | return ( 6 | 14 | {checked && } 15 | 16 | ) 17 | } 18 | 19 | export default Radio 20 | -------------------------------------------------------------------------------- /packages/app/modules/common/components/underline-link/index.tsx: -------------------------------------------------------------------------------- 1 | import ArrowRight from 'app/modules/common/icons/arrow-right' 2 | import { Link, View, Text } from 'app/design' 3 | import clsx from 'clsx' 4 | import { textLargeRegular } from '../../../../design/tailwind/custom-css-classes' 5 | 6 | type UnderlineLinkProps = { 7 | href: string 8 | color?: string 9 | children?: React.ReactNode 10 | } 11 | 12 | const UnderlineLink = ({ 13 | href, 14 | children, 15 | color = 'black', 16 | }: UnderlineLinkProps) => { 17 | return ( 18 | 19 | 22 | {children} 23 | 28 | 29 | 30 | ) 31 | } 32 | 33 | export default UnderlineLink 34 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/alert.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Alert: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 40 | 41 | ) 42 | } 43 | 44 | export default Alert 45 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/arrow-right.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const ArrowRight: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | // return null 11 | return ( 12 | 20 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default ArrowRight 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/back.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Back: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default Back 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/cart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Cart: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 40 | 47 | 48 | ) 49 | } 50 | 51 | export default Cart 52 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/chevron-down.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const ChevronDown: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 27 | ) 28 | } 29 | 30 | export default ChevronDown 31 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/edit.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Edit: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default Edit 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/eye-off.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const EyeOff: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default EyeOff 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/eye.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Eye: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default Eye 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/map-pin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const MapPin: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default MapPin 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/minus.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Minus: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 27 | ) 28 | } 29 | 30 | export default Minus 31 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/package.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Package: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 40 | 41 | ) 42 | } 43 | 44 | export default Package 45 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/placeholder-image.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const PlaceholderImage: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 40 | 41 | ) 42 | } 43 | 44 | export default PlaceholderImage 45 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/plus.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Plus: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default Plus 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/refresh.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Refresh: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 40 | 47 | 48 | ) 49 | } 50 | 51 | export default Refresh 52 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/search.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Search: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 23 | 24 | ) 25 | } 26 | 27 | export default Search 28 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/sorting.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | type SortingIconProps = { 6 | ascendingColor?: string 7 | descendingColor?: string 8 | } & IconProps 9 | 10 | const Sorting: React.FC = ({ 11 | size = '16', 12 | color = 'black', 13 | ascendingColor, 14 | descendingColor, 15 | ...attributes 16 | }) => { 17 | return ( 18 | 26 | 33 | 40 | 41 | ) 42 | } 43 | 44 | export default Sorting 45 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/spinner.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | 3 | import { Text, StyleSheet, View } from 'react-native' 4 | 5 | import Animated, { 6 | useAnimatedStyle, 7 | useSharedValue, 8 | withRepeat, 9 | withTiming, 10 | cancelAnimation, 11 | Easing, 12 | } from 'react-native-reanimated' 13 | import { Circle } from 'react-native-svg' 14 | 15 | const Spinner: React.FC = ({ 16 | size = '16', 17 | color = 'black', 18 | ...attributes 19 | }) => { 20 | const rotation = useSharedValue(0) 21 | const animatedStyles = useAnimatedStyle(() => { 22 | return { 23 | transform: [ 24 | { 25 | rotateZ: `${rotation.value}deg`, 26 | }, 27 | ], 28 | } 29 | }, [rotation.value]) 30 | 31 | useEffect(() => { 32 | rotation.value = withRepeat( 33 | withTiming(360, { 34 | duration: 1000, 35 | easing: Easing.linear, 36 | }), 37 | 200 38 | ) 39 | return () => cancelAnimation(rotation) 40 | }, []) 41 | 42 | return ( 43 | 44 | 45 | 46 | ) 47 | } 48 | const styles = StyleSheet.create({ 49 | container: { 50 | flex: 1, 51 | justifyContent: 'center', 52 | alignItems: 'center', 53 | height: '100%', 54 | }, 55 | spinner: { 56 | height: 20, 57 | width: 20, 58 | borderRadius: 40, 59 | borderWidth: 4, 60 | borderTopColor: 'gray', 61 | borderRightColor: 'gray', 62 | borderBottomColor: 'gray', 63 | borderLeftColor: 'lightgray', 64 | }, 65 | }) 66 | 67 | export default Spinner 68 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/trash.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const Trash: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 40 | 47 | 48 | ) 49 | } 50 | 51 | export default Trash 52 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/user.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const User: React.FC = ({ 6 | size = '16', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default User 38 | -------------------------------------------------------------------------------- /packages/app/modules/common/icons/x.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Svg, Path, Circle } from 'app/design' 3 | import { IconProps } from 'app/types/icon' 4 | 5 | const X: React.FC = ({ 6 | size = '20', 7 | color = 'black', 8 | ...attributes 9 | }) => { 10 | return ( 11 | 19 | 26 | 33 | 34 | ) 35 | } 36 | 37 | export default X 38 | -------------------------------------------------------------------------------- /packages/app/modules/home/screen.tsx: -------------------------------------------------------------------------------- 1 | import { View, ScrollView, Stack } from 'app/design' 2 | 3 | import Hero from './components/hero' 4 | import FeaturedProducts from 'app/modules/home/components/featured-products' 5 | 6 | export function HomeScreen() { 7 | return ( 8 | 9 | 10 | 11 | {/**/} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/app/modules/layout/components/footer-cta/index.tsx: -------------------------------------------------------------------------------- 1 | import UnderlineLink from 'app/modules/common/components/underline-link' 2 | import { View, Image, Text } from 'app/design' 3 | import { text2xlSemi } from '../../../../design/tailwind/custom-css-classes' 4 | 5 | const FooterCTA = () => { 6 | return ( 7 | 8 | 9 | 10 | Shop the latest styles 11 | 12 | Explore products 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | export default FooterCTA 30 | -------------------------------------------------------------------------------- /packages/app/modules/layout/templates/footer/index.tsx: -------------------------------------------------------------------------------- 1 | import FooterCTA from 'app/modules/layout/components/footer-cta' 2 | // import FooterNav from 'app/modules/layout/components/footer-nav' 3 | // import MedusaCTA from 'app/modules/layout/components/medusa-cta' 4 | import { View } from 'app/design' 5 | 6 | const Footer = () => { 7 | return ( 8 | 9 | {/**/} 10 | {/**/} 11 | {/**/} 12 | 13 | ) 14 | } 15 | 16 | export default Footer 17 | -------------------------------------------------------------------------------- /packages/app/modules/layout/templates/index.tsx: -------------------------------------------------------------------------------- 1 | import Footer from 'app/modules/layout/templates/footer' 2 | import Nav from 'app/modules/layout/templates/nav' 3 | import React from 'react' 4 | import { Text, View } from 'app/design' 5 | import { Platform } from 'react-native' 6 | 7 | const Layout: React.FC = ({ children }) => { 8 | return ( 9 | 10 | {Platform.OS === 'web' &&