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 + +