├── .bundle └── config ├── .env.template ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── dependabot.yml ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── SECURITY.md ├── __tests__ └── App.test.tsx ├── android ├── app │ ├── build.gradle │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── Audiowide-Regular.ttf │ │ │ ├── Lato-Bold.ttf │ │ │ ├── Lato-Regular.ttf │ │ │ └── Lato-Thin.ttf │ │ ├── java │ │ └── com │ │ │ └── rnstartermedusa │ │ │ ├── MainActivity.kt │ │ │ └── MainApplication.kt │ │ └── res │ │ ├── drawable │ │ └── rn_edit_text_material.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── link-assets-manifest.json └── settings.gradle ├── app.json ├── app ├── api │ └── client.tsx ├── app.tsx ├── components │ ├── cart │ │ ├── animated-cart-button.tsx │ │ ├── cart-content.tsx │ │ ├── line-item-price.tsx │ │ ├── line-item-quantity.tsx │ │ └── promo-code-input.tsx │ ├── checkout │ │ ├── address-form.tsx │ │ ├── checkout-steps.tsx │ │ └── steps │ │ │ ├── address-step.tsx │ │ │ ├── payment-step.tsx │ │ │ ├── review-step.tsx │ │ │ └── shipping-step.tsx │ ├── common │ │ ├── accordion.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── dropdown.tsx │ │ ├── error-ui.tsx │ │ ├── fab-button.tsx │ │ ├── input.tsx │ │ ├── loader.tsx │ │ ├── navbar.tsx │ │ ├── rounded-button.tsx │ │ ├── tab-bar.tsx │ │ └── text.tsx │ ├── home │ │ ├── featured-collection.tsx │ │ ├── header.tsx │ │ └── hero-carousel.tsx │ └── product │ │ ├── image-carousel.tsx │ │ ├── option-select.tsx │ │ ├── preview-price.tsx │ │ ├── product-list.tsx │ │ ├── product-price.tsx │ │ └── wishlist-button.tsx ├── constants │ └── fluent-templates │ │ ├── en-US.ts │ │ ├── id-ID.ts │ │ ├── index.ts │ │ └── type.ts ├── data │ ├── cart-context.tsx │ ├── customer-context.tsx │ ├── hooks.ts │ ├── locale-context.tsx │ └── region-context.tsx ├── screens │ ├── address │ │ ├── address-form.tsx │ │ └── address-list.tsx │ ├── auth │ │ ├── login.tsx │ │ └── register.tsx │ ├── cart.tsx │ ├── category │ │ ├── categories.tsx │ │ └── category-detail.tsx │ ├── checkout.tsx │ ├── collection │ │ ├── collection-detail.tsx │ │ └── collections.tsx │ ├── home.tsx │ ├── order │ │ ├── order-detail.tsx │ │ └── orders.tsx │ ├── product-detail.tsx │ ├── profile │ │ ├── profile-detail.tsx │ │ └── profile.tsx │ ├── region-select.tsx │ ├── settings.tsx │ └── splash.tsx ├── styles │ ├── global.css │ ├── hooks.tsx │ ├── theme-provider.tsx │ ├── themes.tsx │ ├── types.tsx │ └── utils.tsx ├── types │ └── checkout.ts └── utils │ ├── common.tsx │ ├── image-url.tsx │ ├── order.ts │ └── product-price.tsx ├── assets └── fonts │ ├── Audiowide-Regular.ttf │ ├── Lato-Bold.ttf │ ├── Lato-Regular.ttf │ └── Lato-Thin.ttf ├── babel.config.js ├── index.js ├── ios ├── .xcode.env ├── Podfile ├── Podfile.lock ├── RnStarterMedusa.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── RnStarterMedusa.xcscheme ├── RnStarterMedusa.xcworkspace │ └── contents.xcworkspacedata ├── RnStarterMedusa │ ├── AppDelegate.swift │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── LaunchScreen.storyboard │ ├── PrivacyInfo.xcprivacy │ ├── info.plist │ └── main.m └── link-assets-manifest.json ├── jest.config.js ├── metro.config.js ├── nativewind-env.d.ts ├── package-lock.json ├── package.json ├── react-native.config.js ├── tailwind.config.js ├── tsconfig.json └── types ├── components.d.ts ├── dot-env.d.ts ├── global.d.ts ├── nativewind-env.d.ts └── navigation.d.ts /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | MEDUSA_BACKEND_URL=http://10.0.2.2:9000 2 | PUBLISHABLE_API_KEY= 3 | DEFAULT_LOCALE=en-US 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | rules: { 5 | '@typescript-eslint/no-unused-vars': 'warn', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" # adjust if your package.json is not in the root 5 | schedule: 6 | interval: "weekly" 7 | allow: 8 | - dependency-type: "all" 9 | dependency-name: "@medusajs/js-sdk" 10 | - dependency-type: "all" 11 | dependency-name: "@medusajs/types" -------------------------------------------------------------------------------- /.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 | **/.xcode.env.local 24 | 25 | # VS Code 26 | # 27 | .vscode/* 28 | 29 | # Android/IntelliJ 30 | # 31 | build/ 32 | .idea 33 | .gradle 34 | local.properties 35 | *.iml 36 | *.hprof 37 | .cxx/ 38 | *.keystore 39 | !debug.keystore 40 | .kotlin/ 41 | 42 | # node.js 43 | # 44 | node_modules/ 45 | npm-debug.log 46 | yarn-error.log 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | **/fastlane/report.xml 56 | **/fastlane/Preview.html 57 | **/fastlane/screenshots 58 | **/fastlane/test_output 59 | 60 | # Bundle artifact 61 | *.jsbundle 62 | 63 | # Ruby / CocoaPods 64 | **/Pods/ 65 | /vendor/bundle/ 66 | 67 | # Temporary files created by Metro to check the health of the file watcher 68 | .metro-health-check* 69 | 70 | # testing 71 | /coverage 72 | 73 | # Yarn 74 | .yarn/* 75 | !.yarn/patches 76 | !.yarn/plugins 77 | !.yarn/releases 78 | !.yarn/sdks 79 | !.yarn/versions 80 | .env 81 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | ranjithkumar8352@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to This Project 2 | 3 | Thank you for your interest in contributing to Medusa Mobile project! This document provides guidelines and instructions for contributing. 4 | 5 | ## Development Setup 6 | 7 | 1. Fork and clone the repository 8 | 2. Follow the pre-requisites and setup instructions from the [README](README.md) 9 | 10 | ## Making Contributions 11 | 12 | 1. Create a new branch for your feature/fix: 13 | 14 | ```bash 15 | git checkout -b feature/your-feature-name 16 | ``` 17 | 18 | 2. Make your changes and test thoroughly 19 | 3. Commit your changes following conventional commits: 20 | 21 | ```bash 22 | git commit -m "feat: add new feature" 23 | git commit -m "fix: resolve issue with..." 24 | ``` 25 | 26 | 4. Push to your fork and submit a Pull Request 27 | 28 | ## Pull Request Guidelines 29 | 30 | - Provide a clear description of the changes 31 | - Include any relevant issue numbers 32 | - Ensure your code follows the existing style conventions 33 | - Include tests if applicable 34 | - Update documentation as needed 35 | 36 | ## Code Style 37 | 38 | - Follow the existing code style and formatting 39 | - Use meaningful variable and function names 40 | - Add comments for complex logic 41 | - Keep functions focused and concise 42 | 43 | ## Need Help? 44 | 45 | If you need help or have questions, please: 46 | 47 | - Open an issue in the repository 48 | - Review existing issues and pull requests 49 | 50 | ## License 51 | 52 | By contributing, you agree that your contributions will be licensed under the same license as the project. 53 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | gem 'concurrent-ruby', '< 1.3.4' 11 | 12 | # Ruby 3.4.0 has removed some libraries from the standard library. 13 | gem 'bigdecimal' 14 | gem 'logger' 15 | gem 'benchmark' 16 | gem 'mutex_m' 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 bloomsynth 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 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | > 0.1.0 | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | Create an issue in the repository explaining the vulnerability. A security patch will be released as soon as possible. 12 | -------------------------------------------------------------------------------- /__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import React from 'react'; 6 | import ReactTestRenderer from 'react-test-renderer'; 7 | import App from '../app/app'; 8 | 9 | test('renders correctly', async () => { 10 | await ReactTestRenderer.act(() => { 11 | ReactTestRenderer.create(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | /** 6 | * This is the configuration block to customize your React Native Android app. 7 | * By default you don't need to apply any configuration, just uncomment the lines you need. 8 | */ 9 | react { 10 | /* Folders */ 11 | // The root of your project, i.e. where "package.json" lives. Default is '../..' 12 | // root = file("../../") 13 | // The folder where the react-native NPM package is. Default is ../../node_modules/react-native 14 | // reactNativeDir = file("../../node_modules/react-native") 15 | // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen 16 | // codegenDir = file("../../node_modules/@react-native/codegen") 17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js 18 | // cliFile = file("../../node_modules/react-native/cli.js") 19 | 20 | /* Variants */ 21 | // The list of variants to that are debuggable. For those we're going to 22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 24 | // debuggableVariants = ["liteDebug", "prodDebug"] 25 | 26 | /* Bundling */ 27 | // A list containing the node command and its flags. Default is just 'node'. 28 | // nodeExecutableAndArgs = ["node"] 29 | // 30 | // The command to run when bundling. By default is 'bundle' 31 | // bundleCommand = "ram-bundle" 32 | // 33 | // The path to the CLI configuration file. Default is empty. 34 | // bundleConfig = file(../rn-cli.config.js) 35 | // 36 | // The name of the generated asset file containing your JS bundle 37 | // bundleAssetName = "MyApplication.android.bundle" 38 | // 39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 40 | // entryFile = file("../js/MyApplication.android.js") 41 | // 42 | // A list of extra flags to pass to the 'bundle' commands. 43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 44 | // extraPackagerArgs = [] 45 | 46 | /* Hermes Commands */ 47 | // The hermes compiler command to run. By default it is 'hermesc' 48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 49 | // 50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 51 | // hermesFlags = ["-O", "-output-source-map"] 52 | 53 | /* Autolinking */ 54 | autolinkLibrariesWithApp() 55 | } 56 | 57 | /** 58 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 59 | */ 60 | def enableProguardInReleaseBuilds = false 61 | 62 | /** 63 | * The preferred build flavor of JavaScriptCore (JSC) 64 | * 65 | * For example, to use the international variant, you can use: 66 | * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` 67 | * 68 | * The international variant includes ICU i18n library and necessary data 69 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 70 | * give correct results when using with locales other than en-US. Note that 71 | * this variant is about 6MiB larger per architecture than default. 72 | */ 73 | def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' 74 | 75 | android { 76 | ndkVersion rootProject.ext.ndkVersion 77 | buildToolsVersion rootProject.ext.buildToolsVersion 78 | compileSdk rootProject.ext.compileSdkVersion 79 | 80 | namespace "com.rnstartermedusa" 81 | defaultConfig { 82 | applicationId "com.rnstartermedusa" 83 | minSdkVersion rootProject.ext.minSdkVersion 84 | targetSdkVersion rootProject.ext.targetSdkVersion 85 | versionCode 1 86 | versionName "1.0" 87 | } 88 | signingConfigs { 89 | debug { 90 | storeFile file('debug.keystore') 91 | storePassword 'android' 92 | keyAlias 'androiddebugkey' 93 | keyPassword 'android' 94 | } 95 | } 96 | buildTypes { 97 | debug { 98 | signingConfig signingConfigs.debug 99 | } 100 | release { 101 | // Caution! In production, you need to generate your own keystore file. 102 | // see https://reactnative.dev/docs/signed-apk-android. 103 | signingConfig signingConfigs.debug 104 | minifyEnabled enableProguardInReleaseBuilds 105 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 106 | } 107 | } 108 | } 109 | 110 | dependencies { 111 | // The version of react-native is set by the React Native Gradle Plugin 112 | implementation("com.facebook.react:react-android") 113 | implementation 'com.facebook.fresco:webpsupport:3.2.0' 114 | 115 | if (hermesEnabled.toBoolean()) { 116 | implementation("com.facebook.react:hermes-android") 117 | } else { 118 | implementation jscFlavor 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Audiowide-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/assets/fonts/Audiowide-Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/assets/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/assets/fonts/Lato-Thin.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/com/rnstartermedusa/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rnstartermedusa 2 | 3 | import android.os.Bundle; 4 | import com.facebook.react.ReactActivity 5 | import com.facebook.react.ReactActivityDelegate 6 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 7 | import com.facebook.react.defaults.DefaultReactActivityDelegate 8 | 9 | class MainActivity : ReactActivity() { 10 | 11 | /** 12 | * Returns the name of the main component registered from JavaScript. This is used to schedule 13 | * rendering of the component. 14 | */ 15 | override fun getMainComponentName(): String = "RnStarterMedusa" 16 | 17 | 18 | /** 19 | * OnCreate for react-navigation 20 | * 21 | */ 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(null) 24 | } 25 | 26 | /** 27 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 28 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 29 | */ 30 | override fun createReactActivityDelegate(): ReactActivityDelegate = 31 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 32 | } 33 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/rnstartermedusa/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.rnstartermedusa 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List = 20 | PackageList(this).packages.apply { 21 | // Packages that cannot be autolinked yet can be added manually here, for example: 22 | // add(MyReactNativePackage()) 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, OpenSourceMergedSoMapping) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RnStarterMedusa 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "35.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 35 6 | targetSdkVersion = 35 7 | ndkVersion = "27.1.12297006" 8 | kotlinVersion = "2.0.21" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloomsynth/medusa-mobile-react-native/242a0598660364a2fcf5506813dba56f07c3a280/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /android/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "assets/fonts/Audiowide-Regular.ttf", 6 | "sha1": "651503b7a0a89f49f78f14d04c62f811853573cd" 7 | }, 8 | { 9 | "path": "assets/fonts/Lato-Bold.ttf", 10 | "sha1": "542498221d97bee5bdbccf86ee8890bf8e8005c9" 11 | }, 12 | { 13 | "path": "assets/fonts/Lato-Regular.ttf", 14 | "sha1": "e923c72eda5e50a87e18ff5c71e9ef4b3b6455a3" 15 | }, 16 | { 17 | "path": "assets/fonts/Lato-Thin.ttf", 18 | "sha1": "07290446bee3f81ce501a3c3dbfde6097c70ca15" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'RnStarterMedusa' 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RnStarterMedusa", 3 | "displayName": "RnStarterMedusa" 4 | } 5 | -------------------------------------------------------------------------------- /app/api/client.tsx: -------------------------------------------------------------------------------- 1 | import Medusa from '@medusajs/js-sdk'; 2 | import {MEDUSA_BACKEND_URL, PUBLISHABLE_API_KEY} from '@env'; 3 | import AsyncStorage from '@react-native-async-storage/async-storage'; 4 | 5 | export const apiUrl = MEDUSA_BACKEND_URL || 'http://localhost:9000'; 6 | 7 | const publishableKey = PUBLISHABLE_API_KEY || ''; 8 | export const AUTH_TOKEN_KEY = 'auth_token'; 9 | 10 | const apiClient = new Medusa({ 11 | baseUrl: apiUrl, 12 | publishableKey: publishableKey, 13 | auth: { 14 | type: 'jwt', 15 | jwtTokenStorageMethod: 'custom', 16 | jwtTokenStorageKey: AUTH_TOKEN_KEY, 17 | storage: AsyncStorage, 18 | }, 19 | }); 20 | 21 | export default apiClient; 22 | -------------------------------------------------------------------------------- /app/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | createStaticNavigation, 4 | StaticParamList, 5 | } from '@react-navigation/native'; 6 | import {createNativeStackNavigator} from '@react-navigation/native-stack'; 7 | import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; 8 | import ThemeProvider from '@styles/theme-provider'; 9 | import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; 10 | import {GestureHandlerRootView} from 'react-native-gesture-handler'; 11 | import TabBar from '@components/common/tab-bar'; 12 | import Splash from '@screens/splash'; 13 | import Home from '@screens/home'; 14 | import Categories from '@screens/category/categories'; 15 | import CategoryDetail from '@screens/category/category-detail'; 16 | import Collections from '@screens/collection/collections'; 17 | import CollectionDetail from '@screens/collection/collection-detail'; 18 | import ProductDetail from '@screens/product-detail'; 19 | import Cart from '@screens/cart'; 20 | import Checkout from '@screens/checkout'; 21 | import Profile from '@screens/profile/profile'; 22 | import SignIn from '@screens/auth/login'; 23 | import Register from '@screens/auth/register'; 24 | import Orders from '@screens/order/orders'; 25 | import OrderDetail from '@screens/order/order-detail'; 26 | import ProfileDetail from '@screens/profile/profile-detail'; 27 | import {CartProvider} from '@data/cart-context'; 28 | import {RegionProvider} from '@data/region-context'; 29 | import {CustomerProvider} from '@data/customer-context'; 30 | import {LocaleProvider} from '@data/locale-context'; 31 | import AddressForm from '@screens/address/address-form'; 32 | import AddressList from '@screens/address/address-list'; 33 | import RegionSelect from '@screens/region-select'; 34 | import Settings from '@screens/settings'; 35 | 36 | import '@styles/global.css'; 37 | import {SafeAreaProvider} from 'react-native-safe-area-context'; 38 | 39 | export type RootStackParamList = StaticParamList; 40 | 41 | const queryClient = new QueryClient(); 42 | 43 | export default function App() { 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | const HomeTabs = createBottomTabNavigator({ 66 | tabBar: props => , 67 | screens: { 68 | Home: { 69 | screen: Home, 70 | options: { 71 | title: 'home', 72 | }, 73 | }, 74 | Categories: { 75 | screen: Categories, 76 | options: { 77 | title: 'categories', 78 | }, 79 | }, 80 | Collections: { 81 | screen: Collections, 82 | options: { 83 | title: 'collections', 84 | }, 85 | }, 86 | Profile: { 87 | screen: Profile, 88 | options: { 89 | title: 'profile', 90 | }, 91 | }, 92 | }, 93 | screenOptions: { 94 | headerShown: false, 95 | }, 96 | }); 97 | 98 | const RootStack = createNativeStackNavigator({ 99 | initialRouteName: 'Splash', 100 | groups: { 101 | App: { 102 | screenOptions: { 103 | headerShown: false, 104 | }, 105 | screens: { 106 | Main: HomeTabs, 107 | Splash, 108 | ProductDetail, 109 | CategoryDetail, 110 | CollectionDetail, 111 | Cart, 112 | Checkout, 113 | SignIn, 114 | Register, 115 | Orders, 116 | OrderDetail, 117 | ProfileDetail, 118 | AddressList, 119 | AddressForm, 120 | Settings, 121 | }, 122 | }, 123 | Modal: { 124 | screenOptions: { 125 | presentation: 'modal', 126 | headerShown: false, 127 | }, 128 | screens: { 129 | RegionSelect: RegionSelect, 130 | }, 131 | }, 132 | }, 133 | }); 134 | 135 | const Navigation = createStaticNavigation(RootStack); 136 | -------------------------------------------------------------------------------- /app/components/cart/animated-cart-button.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@components/common/button'; 2 | import Text from '@components/common/text'; 3 | import React, {useEffect, useRef} from 'react'; 4 | import Icon from '@react-native-vector-icons/ant-design'; 5 | import {View} from 'react-native'; 6 | import Animated, { 7 | useAnimatedStyle, 8 | useSharedValue, 9 | withTiming, 10 | withSpring, 11 | withSequence, 12 | } from 'react-native-reanimated'; 13 | import {useLocalization} from '@fluent/react'; 14 | import {useColors} from '@styles/hooks'; 15 | import {useCart} from '@data/cart-context'; 16 | import {useProductQuantity} from '@data/hooks'; 17 | import {useNavigation} from '@react-navigation/native'; 18 | import Badge from '@components/common/badge'; 19 | 20 | type AnimatedCartButtonProps = { 21 | productId: string; 22 | selectedVariantId?: string; 23 | disabled?: boolean; 24 | inStock?: boolean; 25 | hasSelectedAllOptions?: boolean; 26 | }; 27 | 28 | const AnimatedCartButton = ({ 29 | productId, 30 | selectedVariantId, 31 | disabled = false, 32 | inStock, 33 | hasSelectedAllOptions = false, 34 | }: AnimatedCartButtonProps) => { 35 | const {l10n} = useLocalization(); 36 | const {addToCart} = useCart(); 37 | const [adding, setAdding] = React.useState(false); 38 | const productQuantityInCart = useProductQuantity(productId); 39 | const showViewCart = useSharedValue(productQuantityInCart > 0); 40 | const viewCartWidth = 192; 41 | 42 | useEffect(() => { 43 | if (productQuantityInCart > 0) { 44 | showViewCart.value = true; 45 | } else { 46 | showViewCart.value = false; 47 | } 48 | }, [productQuantityInCart, showViewCart]); 49 | 50 | const rowStyles = useAnimatedStyle(() => { 51 | return { 52 | gap: withTiming(showViewCart.value ? 8 : 0), 53 | }; 54 | }); 55 | 56 | const viewCartStyles = useAnimatedStyle(() => { 57 | return { 58 | width: withTiming(showViewCart.value ? viewCartWidth : 0), 59 | opacity: withTiming(showViewCart.value ? 1 : 0), 60 | }; 61 | }); 62 | 63 | const addToCartHandler = async () => { 64 | if (!selectedVariantId || disabled || !inStock) { 65 | return; 66 | } 67 | setAdding(true); 68 | await addToCart(selectedVariantId, 1); 69 | setAdding(false); 70 | }; 71 | 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | 144 | ); 145 | }; 146 | 147 | export default AnimatedCartButton; 148 | -------------------------------------------------------------------------------- /app/components/cart/cart-content.tsx: -------------------------------------------------------------------------------- 1 | import {StoreCart} from '@medusajs/types'; 2 | import React from 'react'; 3 | import {Image, View} from 'react-native'; 4 | import {useLocalization} from '@fluent/react'; 5 | import Text from '@components/common/text'; 6 | import {HttpTypes} from '@medusajs/types'; 7 | import LineItemQuantity from '@components/cart/line-item-quantity'; 8 | import LineItemUnitPrice from '@components/cart/line-item-price'; 9 | import {convertToLocale} from '@utils/product-price'; 10 | import {formatImageUrl} from '@utils/image-url'; 11 | import PromoCodeInput from '@components/cart/promo-code-input'; 12 | 13 | type CartContentProps = { 14 | cart: HttpTypes.StoreCart; 15 | mode: 'checkout' | 'cart'; 16 | }; 17 | 18 | const CartContent = ({cart, mode}: CartContentProps) => { 19 | return ( 20 | 21 | 22 | 23 | 24 | {mode === 'cart' && } 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | const CartItems = ({ 32 | cart, 33 | mode, 34 | }: { 35 | cart?: StoreCart; 36 | mode: 'checkout' | 'cart'; 37 | }) => { 38 | if (!cart) { 39 | return null; 40 | } 41 | const sortedItems = cart?.items?.sort((a, b) => { 42 | return (a.created_at ?? '') < (b.created_at ?? '') ? -1 : 1; 43 | }); 44 | return ( 45 | 46 | {sortedItems?.map(item => ( 47 | 53 | ))} 54 | 55 | ); 56 | }; 57 | 58 | type CartItemProps = { 59 | item: HttpTypes.StoreCartLineItem; 60 | currencyCode: string; 61 | mode: 'checkout' | 'cart'; 62 | }; 63 | 64 | const CartItem = ({item, currencyCode, mode}: CartItemProps) => { 65 | const {l10n} = useLocalization(); 66 | 67 | return ( 68 | 69 | 73 | 74 | 75 | {item.product_title} 76 | 77 | {!!item.variant_title && ( 78 | 79 | {l10n.getString('variant')}: {item.variant_title} 80 | 81 | )} 82 | 83 | 88 | 89 | 90 | 91 | 92 | ); 93 | }; 94 | 95 | type SummaryItem = { 96 | name: string; 97 | key: keyof StoreCart; 98 | }; 99 | 100 | const CartSummary = ({cart}: {cart?: StoreCart}) => { 101 | const {l10n} = useLocalization(); 102 | if (!cart) { 103 | return null; 104 | } 105 | const summaryItems: SummaryItem[] = [ 106 | { 107 | name: l10n.getString('subtotal'), 108 | key: 'item_subtotal', 109 | }, 110 | { 111 | name: l10n.getString('shipping'), 112 | key: 'shipping_total', 113 | }, 114 | { 115 | name: l10n.getString('taxes'), 116 | key: 'tax_total', 117 | }, 118 | ]; 119 | 120 | const discountTotal = cart.discount_total || 0; 121 | 122 | return ( 123 | 124 | {l10n.getString('summary')} 125 | 126 | {summaryItems.map(item => ( 127 | 130 | {item.name} 131 | 132 | {convertToLocale({ 133 | amount: cart[item.key] as number, 134 | currency_code: cart.currency_code, 135 | })} 136 | 137 | 138 | ))} 139 | {discountTotal > 0 && ( 140 | 141 | {l10n.getString('discount')} 142 | 143 | - 144 | {convertToLocale({ 145 | amount: discountTotal, 146 | currency_code: cart.currency_code, 147 | })} 148 | 149 | 150 | )} 151 | 152 | 153 | 154 | {l10n.getString('total')} 155 | 156 | {convertToLocale({ 157 | amount: cart.total, 158 | currency_code: cart.currency_code, 159 | })} 160 | 161 | 162 | 163 | 164 | ); 165 | }; 166 | 167 | export default CartContent; 168 | -------------------------------------------------------------------------------- /app/components/cart/line-item-price.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text} from 'react-native'; 3 | import {convertToLocale} from '@utils/product-price'; 4 | import {HttpTypes} from '@medusajs/types'; 5 | 6 | type LineItemUnitPriceProps = { 7 | item: HttpTypes.StoreCartLineItem | HttpTypes.StoreOrderLineItem; 8 | style?: 'default' | 'tight'; 9 | currencyCode: string; 10 | }; 11 | 12 | const LineItemUnitPrice = ({ 13 | item, 14 | style = 'default', 15 | currencyCode, 16 | }: LineItemUnitPriceProps) => { 17 | const {total, original_total, unit_price} = item; 18 | 19 | const hasReducedPrice = total < original_total; 20 | 21 | const percentage_diff = Math.round( 22 | ((original_total - total) / original_total) * 100, 23 | ); 24 | 25 | return ( 26 | 27 | {hasReducedPrice && ( 28 | <> 29 | 30 | {style === 'default' && ( 31 | Original: 32 | )} 33 | 36 | {convertToLocale({ 37 | amount: original_total / item.quantity, 38 | currency_code: currencyCode, 39 | })} 40 | 41 | 42 | {style === 'default' && ( 43 | -{percentage_diff}% 44 | )} 45 | 46 | )} 47 | 51 | {convertToLocale({ 52 | amount: unit_price * item.quantity, 53 | currency_code: currencyCode, 54 | })} 55 | 56 | 57 | ); 58 | }; 59 | 60 | export default LineItemUnitPrice; 61 | -------------------------------------------------------------------------------- /app/components/cart/line-item-quantity.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useColors} from '@styles/hooks'; 3 | import {ActivityIndicator, TouchableOpacity, View} from 'react-native'; 4 | import Icon from '@react-native-vector-icons/ant-design'; 5 | import {useLocalization} from '@fluent/react'; 6 | import Text from '@components/common/text'; 7 | import {useCart} from '@data/cart-context'; 8 | 9 | type LineItemQuantityProps = { 10 | quantity: number; 11 | lineItemId: string; 12 | mode: 'checkout' | 'cart'; 13 | }; 14 | 15 | const LineItemQuantity = ({ 16 | quantity, 17 | lineItemId, 18 | mode, 19 | }: LineItemQuantityProps) => { 20 | const {l10n} = useLocalization(); 21 | const colors = useColors(); 22 | const {updateLineItem} = useCart(); 23 | const [updating, setUpdating] = React.useState(false); 24 | 25 | const increment = async () => { 26 | const newQuantity = quantity + 1; 27 | return updateQuantity(newQuantity); 28 | }; 29 | 30 | const decrement = async () => { 31 | const newQuantity = quantity - 1; 32 | return updateQuantity(newQuantity); 33 | }; 34 | 35 | const deleteLineItem = async () => { 36 | setUpdating(true); 37 | await updateLineItem(lineItemId, 0); 38 | setUpdating(false); 39 | }; 40 | 41 | const updateQuantity = async (newQuantity: number) => { 42 | setUpdating(true); 43 | await updateLineItem(lineItemId, newQuantity); 44 | setUpdating(false); 45 | }; 46 | 47 | return ( 48 | 49 | {mode === 'cart' ? ( 50 | 51 | 54 | 55 | 56 | {quantity} 57 | 60 | 61 | 62 | 63 | ) : ( 64 | 65 | {l10n.getString('qty')}: {quantity} 66 | 67 | )} 68 | {updating ? ( 69 | 70 | ) : mode === 'cart' ? ( 71 | 72 | 73 | 74 | ) : null} 75 | 76 | ); 77 | }; 78 | 79 | export default LineItemQuantity; 80 | -------------------------------------------------------------------------------- /app/components/cart/promo-code-input.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {View, TouchableOpacity, Keyboard} from 'react-native'; 3 | import {useLocalization} from '@fluent/react'; 4 | import {useCart} from '@data/cart-context'; 5 | import Input from '@components/common/input'; 6 | import RoundedButton from '@components/common/rounded-button'; 7 | import Text from '@components/common/text'; 8 | import Icon from '@react-native-vector-icons/ant-design'; 9 | import {useColors} from '@styles/hooks'; 10 | import {HttpTypes} from '@medusajs/types'; 11 | import Badge from '@components/common/badge'; 12 | import {convertToLocale} from '@utils/product-price'; 13 | import Button from '@components/common/button'; 14 | import Accordion from '@components/common/accordion'; 15 | import {useSharedValue} from 'react-native-reanimated'; 16 | 17 | type Promotion = HttpTypes.StorePromotion; 18 | 19 | const PromoCodeInput = () => { 20 | const {l10n} = useLocalization(); 21 | const [code, setCode] = useState(''); 22 | const [error, setError] = useState(null); 23 | const [isLoading, setIsLoading] = useState(false); 24 | const isExpanded = useSharedValue(false); 25 | const {applyPromoCode, removePromoCode, cart} = useCart(); 26 | const colors = useColors(); 27 | 28 | const handleCodeChange = (text: string) => { 29 | setCode(text); 30 | if (error) { 31 | setError(null); 32 | } 33 | }; 34 | 35 | const handleApplyCode = async () => { 36 | if (!code || isLoading) { 37 | return; 38 | } 39 | 40 | Keyboard.dismiss(); 41 | 42 | setError(null); 43 | setIsLoading(true); 44 | try { 45 | const applied = await applyPromoCode(code); 46 | if (applied) { 47 | setCode(''); 48 | } else { 49 | setError(l10n.getString('invalid-promo-code')); 50 | } 51 | } catch (err: any) { 52 | setError(err?.message || l10n.getString('failed-to-apply-promotion')); 53 | } finally { 54 | setIsLoading(false); 55 | } 56 | }; 57 | 58 | const handleRemoveCode = async (promoCode: string) => { 59 | if (isLoading) { 60 | return; 61 | } 62 | setIsLoading(true); 63 | try { 64 | await removePromoCode(promoCode); 65 | } catch (err: any) { 66 | setError(err?.message || l10n.getString('failed-to-remove-promotion')); 67 | } finally { 68 | setIsLoading(false); 69 | } 70 | }; 71 | 72 | const promotions = cart?.promotions as Promotion[] | undefined; 73 | 74 | return ( 75 | <> 76 | 77 | { 79 | isExpanded.value = !isExpanded.value; 80 | }}> 81 | 83 | {l10n.getString('add-a-promo-code')} 84 | 85 | 86 | 87 | 88 | 89 | 90 | 97 | 98 | 99 |