From 6b6cf2344aa02e414810887daf3df31e6ff5a41d Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Thu, 6 Nov 2025 11:01:34 +0200 Subject: [PATCH] feat: react-native and expo app --- README.md | 1 + react-native-expo/.env.template | 2 + react-native-expo/.gitignore | 43 ++ react-native-expo/README.md | 55 +++ react-native-expo/app.json | 48 ++ .../app/(drawer)/(tabs)/(cart)/_layout.tsx | 37 ++ .../app/(drawer)/(tabs)/(cart)/index.tsx | 158 +++++++ .../app/(drawer)/(tabs)/(home)/_layout.tsx | 46 ++ .../app/(drawer)/(tabs)/(home)/index.tsx | 152 ++++++ .../(drawer)/(tabs)/(home)/product/[id].tsx | 390 ++++++++++++++++ .../app/(drawer)/(tabs)/_layout.tsx | 52 +++ .../app/(drawer)/(tabs)/index.tsx | 8 + react-native-expo/app/(drawer)/_layout.tsx | 24 + react-native-expo/app/_layout.tsx | 47 ++ react-native-expo/app/checkout.tsx | 390 ++++++++++++++++ .../app/order-confirmation/[id].tsx | 434 ++++++++++++++++++ .../assets/images/android-icon-background.png | Bin 0 -> 17549 bytes .../assets/images/android-icon-foreground.png | Bin 0 -> 78796 bytes .../assets/images/android-icon-monochrome.png | Bin 0 -> 4140 bytes react-native-expo/assets/images/favicon.png | Bin 0 -> 1129 bytes react-native-expo/assets/images/icon.png | Bin 0 -> 393493 bytes .../assets/images/partial-react-logo.png | Bin 0 -> 5075 bytes .../assets/images/react-logo.png | Bin 0 -> 6341 bytes .../assets/images/react-logo@2x.png | Bin 0 -> 14225 bytes .../assets/images/react-logo@3x.png | Bin 0 -> 21252 bytes .../assets/images/splash-icon.png | Bin 0 -> 17547 bytes react-native-expo/components/cart-item.tsx | 138 ++++++ .../components/checkout/address-form.tsx | 298 ++++++++++++ .../components/checkout/delivery-step.tsx | 233 ++++++++++ .../components/checkout/payment-step.tsx | 238 ++++++++++ .../components/checkout/shipping-step.tsx | 166 +++++++ .../components/drawer-content.tsx | 21 + react-native-expo/components/haptic-tab.tsx | 18 + react-native-expo/components/loading.tsx | 36 ++ react-native-expo/components/product-card.tsx | 97 ++++ .../components/product-image-slider.tsx | 104 +++++ .../components/product-skeleton.tsx | 257 +++++++++++ .../components/region-selector.tsx | 140 ++++++ react-native-expo/components/themed-text.tsx | 60 +++ react-native-expo/components/themed-view.tsx | 14 + react-native-expo/components/ui/button.tsx | 85 ++++ .../components/ui/collapsible.tsx | 45 ++ .../components/ui/icon-symbol.ios.tsx | 32 ++ .../components/ui/icon-symbol.tsx | 41 ++ react-native-expo/components/ui/toast.tsx | 117 +++++ react-native-expo/constants/theme.ts | 65 +++ react-native-expo/context/cart-context.tsx | 223 +++++++++ react-native-expo/context/region-context.tsx | 101 ++++ react-native-expo/eslint.config.js | 10 + react-native-expo/hooks/use-color-scheme.ts | 1 + .../hooks/use-color-scheme.web.ts | 21 + react-native-expo/hooks/use-theme-color.ts | 21 + react-native-expo/lib/format-price.ts | 19 + react-native-expo/lib/inventory.ts | 18 + react-native-expo/lib/payment-providers.ts | 27 ++ react-native-expo/lib/sdk.ts | 25 + react-native-expo/package.json | 52 +++ react-native-expo/scripts/reset-project.js | 112 +++++ react-native-expo/tsconfig.json | 17 + 59 files changed, 4739 insertions(+) create mode 100644 react-native-expo/.env.template create mode 100644 react-native-expo/.gitignore create mode 100644 react-native-expo/README.md create mode 100644 react-native-expo/app.json create mode 100644 react-native-expo/app/(drawer)/(tabs)/(cart)/_layout.tsx create mode 100644 react-native-expo/app/(drawer)/(tabs)/(cart)/index.tsx create mode 100644 react-native-expo/app/(drawer)/(tabs)/(home)/_layout.tsx create mode 100644 react-native-expo/app/(drawer)/(tabs)/(home)/index.tsx create mode 100644 react-native-expo/app/(drawer)/(tabs)/(home)/product/[id].tsx create mode 100644 react-native-expo/app/(drawer)/(tabs)/_layout.tsx create mode 100644 react-native-expo/app/(drawer)/(tabs)/index.tsx create mode 100644 react-native-expo/app/(drawer)/_layout.tsx create mode 100644 react-native-expo/app/_layout.tsx create mode 100644 react-native-expo/app/checkout.tsx create mode 100644 react-native-expo/app/order-confirmation/[id].tsx create mode 100644 react-native-expo/assets/images/android-icon-background.png create mode 100644 react-native-expo/assets/images/android-icon-foreground.png create mode 100644 react-native-expo/assets/images/android-icon-monochrome.png create mode 100644 react-native-expo/assets/images/favicon.png create mode 100644 react-native-expo/assets/images/icon.png create mode 100644 react-native-expo/assets/images/partial-react-logo.png create mode 100644 react-native-expo/assets/images/react-logo.png create mode 100644 react-native-expo/assets/images/react-logo@2x.png create mode 100644 react-native-expo/assets/images/react-logo@3x.png create mode 100644 react-native-expo/assets/images/splash-icon.png create mode 100644 react-native-expo/components/cart-item.tsx create mode 100644 react-native-expo/components/checkout/address-form.tsx create mode 100644 react-native-expo/components/checkout/delivery-step.tsx create mode 100644 react-native-expo/components/checkout/payment-step.tsx create mode 100644 react-native-expo/components/checkout/shipping-step.tsx create mode 100644 react-native-expo/components/drawer-content.tsx create mode 100644 react-native-expo/components/haptic-tab.tsx create mode 100644 react-native-expo/components/loading.tsx create mode 100644 react-native-expo/components/product-card.tsx create mode 100644 react-native-expo/components/product-image-slider.tsx create mode 100644 react-native-expo/components/product-skeleton.tsx create mode 100644 react-native-expo/components/region-selector.tsx create mode 100644 react-native-expo/components/themed-text.tsx create mode 100644 react-native-expo/components/themed-view.tsx create mode 100644 react-native-expo/components/ui/button.tsx create mode 100644 react-native-expo/components/ui/collapsible.tsx create mode 100644 react-native-expo/components/ui/icon-symbol.ios.tsx create mode 100644 react-native-expo/components/ui/icon-symbol.tsx create mode 100644 react-native-expo/components/ui/toast.tsx create mode 100644 react-native-expo/constants/theme.ts create mode 100644 react-native-expo/context/cart-context.tsx create mode 100644 react-native-expo/context/region-context.tsx create mode 100644 react-native-expo/eslint.config.js create mode 100644 react-native-expo/hooks/use-color-scheme.ts create mode 100644 react-native-expo/hooks/use-color-scheme.web.ts create mode 100644 react-native-expo/hooks/use-theme-color.ts create mode 100644 react-native-expo/lib/format-price.ts create mode 100644 react-native-expo/lib/inventory.ts create mode 100644 react-native-expo/lib/payment-providers.ts create mode 100644 react-native-expo/lib/sdk.ts create mode 100644 react-native-expo/package.json create mode 100755 react-native-expo/scripts/reset-project.js create mode 100644 react-native-expo/tsconfig.json diff --git a/README.md b/README.md index 15a1a35..6df90e8 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ To learn how to use an example, open its `README.md` file. You'll find the detai | [Product Reviews](./product-reviews/README.md) | Custom Feature | Allow customers to add product reviews, and merchants to manage them. | | [Quotes Management](./quotes-management/README.md) | Custom Feature | Allow customers to send quotes, and merchants to manage and accept them. | | [Re-order Feature](./re-order/README.md) | Custom Feature | Allow customers to re-order a previous order. | +| [React Native and Expo Store](./react-native-expo/README.md) | Storefront | Create a mobile app for your Medusa backend with React Native and Expo. | | [Request Returns from Storefront](./returns-storefront/README.md) | Storefront | Let custmers request a return of their order from the storefront. | | [Resend Integration](./resend-integration/README.md) | Integration | Integrate Resend to send notifications in Medusa. | | [Restaurant Marketplace](./restaurant-marketplace/README.md) | Custom Feature | Build an Uber-Eats clone with Medusa. | diff --git a/react-native-expo/.env.template b/react-native-expo/.env.template new file mode 100644 index 0000000..8cd9456 --- /dev/null +++ b/react-native-expo/.env.template @@ -0,0 +1,2 @@ +EXPO_PUBLIC_MEDUSA_PUBLISHABLE_API_KEY= +EXPO_PUBLIC_MEDUSA_URL= \ No newline at end of file diff --git a/react-native-expo/.gitignore b/react-native-expo/.gitignore new file mode 100644 index 0000000..f8c6c2e --- /dev/null +++ b/react-native-expo/.gitignore @@ -0,0 +1,43 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +app-example + +# generated native folders +/ios +/android diff --git a/react-native-expo/README.md b/react-native-expo/README.md new file mode 100644 index 0000000..fe09ac9 --- /dev/null +++ b/react-native-expo/README.md @@ -0,0 +1,55 @@ +# Medusa v2 Example: React Native / Expo App + +This directory holds the code for the [Implement Mobile App with React Native, Expo, and Medusa](https://docs.medusajs.com/resources/storefront-development/guides/react-native-expo) guide. + +This codebase only includes the express checkout storefront and doesn't include the Medusa application. You can learn how to install it by following [this guide](https://docs.medusajs.com/learn/installation). + +## Installation + +1. Clone the repository and change to the `react-native-expo` directory: + +```bash +git clone https://github.com/medusajs/examples.git +cd examples/react-native-expo +``` + +2\. Rename the `.env.template` file to `.env` and set the following variables: + +```bash +EXPO_PUBLIC_MEDUSA_PUBLISHABLE_API_KEY= +EXPO_PUBLIC_MEDUSA_URL= +``` + +Where: + +- `EXPO_PUBLIC_MEDUSA_URL` is the URL to your Medusa application server. If the Medusa application is running locally, it should be a local IP. For example `http://192.168.1.100:9000`. +- `EXPO_PUBLIC_MEDUSA_PUBLISHABLE_API_KEY` is the publishable key for your Medusa application. You can retrieve it from the Medusa Admin by going to Settings > Publishable API Keys. + +3\. Install dependencies: + +```bash +npm install +``` + +4\. While the Medusa application is running, start the Expo server: + +```bash +npm run start +``` + +You can then test the app on a simulator or with [Expo Go](https://expo.dev/go). + +## Testing in a Browser + +If you're testing the app on the web, make sure to add `localhost:8081` (default Expo server URL) to the Medusa application's `STORE_CORS` and `AUTH_CORS` environment variables: + +```bash +STORE_CORS=previous_values...,http://localhost:8081 +AUTH_CORS=previous_values...,http://localhost:8081 +``` + +## More Resources + +- [Medusa Documentation](https://docs.medusajs.com) +- [React Native Documentation](https://reactnative.dev/docs/getting-started) +- [Expo Documentation](https://docs.expo.dev/) \ No newline at end of file diff --git a/react-native-expo/app.json b/react-native-expo/app.json new file mode 100644 index 0000000..c58bc49 --- /dev/null +++ b/react-native-expo/app.json @@ -0,0 +1,48 @@ +{ + "expo": { + "name": "react-native-store", + "slug": "react-native-store", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "reactnativestore", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "backgroundImage": "./assets/images/android-icon-background.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "edgeToEdgeEnabled": true, + "predictiveBackGestureEnabled": false + }, + "web": { + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-splash-screen", + { + "image": "./assets/images/splash-icon.png", + "imageWidth": 200, + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "dark": { + "backgroundColor": "#000000" + } + } + ] + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } + } +} diff --git a/react-native-expo/app/(drawer)/(tabs)/(cart)/_layout.tsx b/react-native-expo/app/(drawer)/(tabs)/(cart)/_layout.tsx new file mode 100644 index 0000000..503ead3 --- /dev/null +++ b/react-native-expo/app/(drawer)/(tabs)/(cart)/_layout.tsx @@ -0,0 +1,37 @@ +import { useColorScheme } from '@/hooks/use-color-scheme'; +import { DrawerActions } from '@react-navigation/native'; +import { Stack, useNavigation } from 'expo-router'; +import React from 'react'; +import { TouchableOpacity } from 'react-native'; + +import { IconSymbol } from '@/components/ui/icon-symbol'; +import { Colors } from '@/constants/theme'; + +export default function CartStackLayout() { + const colorScheme = useColorScheme(); + const navigation = useNavigation(); + const colors = Colors[colorScheme ?? 'light']; + + return ( + + ( + navigation.dispatch(DrawerActions.openDrawer())} + style={{ height: 36, width: 36, display: "flex", alignItems: "center", justifyContent: "center" }} + > + + + ), + }} + /> + + ); +} \ No newline at end of file diff --git a/react-native-expo/app/(drawer)/(tabs)/(cart)/index.tsx b/react-native-expo/app/(drawer)/(tabs)/(cart)/index.tsx new file mode 100644 index 0000000..4d02684 --- /dev/null +++ b/react-native-expo/app/(drawer)/(tabs)/(cart)/index.tsx @@ -0,0 +1,158 @@ +import { CartItem } from '@/components/cart-item'; +import { Loading } from '@/components/loading'; +import { Button } from '@/components/ui/button'; +import { Colors } from '@/constants/theme'; +import { useCart } from '@/context/cart-context'; +import { useColorScheme } from '@/hooks/use-color-scheme'; +import { formatPrice } from '@/lib/format-price'; +import { useRouter } from 'expo-router'; +import React from 'react'; +import { FlatList, StyleSheet, Text, View } from 'react-native'; + +export default function CartScreen() { + const colorScheme = useColorScheme(); + const colors = Colors[colorScheme ?? 'light']; + const router = useRouter(); + const { cart, updateItemQuantity, removeItem, loading } = useCart(); + + const isEmpty = !cart?.items || cart.items.length === 0; + + if (loading && !cart) { + return ; + } + + if (isEmpty) { + return ( + + Your cart is empty + + Add some products to get started + +