├── .DS_Store └── mobile ├── .gitignore ├── app.json ├── assets └── images │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ ├── splash-icon.png │ └── splash.png ├── package-lock.json ├── package.json ├── src ├── app │ ├── _layout.tsx │ ├── home.tsx │ ├── index.tsx │ └── market │ │ └── [id].tsx ├── assets │ ├── location.png │ ├── logo.png │ └── pin.png ├── components │ ├── button │ │ ├── index.tsx │ │ └── styles.ts │ ├── categories │ │ ├── index.tsx │ │ └── styles.ts │ ├── category │ │ ├── index.tsx │ │ └── styles.ts │ ├── loading │ │ ├── index.tsx │ │ └── styles.ts │ ├── market │ │ ├── coupon │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── cover │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── details │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ └── info │ │ │ ├── index.tsx │ │ │ └── styles.ts │ ├── place │ │ ├── index.tsx │ │ └── styles.ts │ ├── places │ │ ├── index.tsx │ │ └── styles.ts │ ├── step │ │ ├── index.tsx │ │ └── styles.ts │ ├── steps │ │ ├── index.tsx │ │ └── styles.ts │ └── welcome │ │ ├── index.tsx │ │ └── styles.ts ├── services │ └── api.ts ├── styles │ ├── colors.ts │ ├── font-family.ts │ └── theme.ts └── utils │ └── categories-icons.ts └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/.DS_Store -------------------------------------------------------------------------------- /mobile/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | *.orig.* 14 | *.jks 15 | *.p8 16 | *.p12 17 | *.key 18 | *.mobileprovision 19 | 20 | # Metro 21 | .metro-health-check* 22 | 23 | # debug 24 | npm-debug.* 25 | yarn-debug.* 26 | yarn-error.* 27 | 28 | # macOS 29 | .DS_Store 30 | *.pem 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | -------------------------------------------------------------------------------- /mobile/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "nearby", 4 | "slug": "nearby", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "newArchEnabled": true, 11 | "splash": { 12 | "image": "./assets/images/splash.png", 13 | "resizeMode": "contain", 14 | "backgroundColor": "#257F49" 15 | }, 16 | "ios": { 17 | "supportsTablet": true 18 | }, 19 | "android": { 20 | "adaptiveIcon": { 21 | "foregroundImage": "./assets/images/adaptive-icon.png", 22 | "backgroundColor": "#257F49" 23 | } 24 | }, 25 | "web": { 26 | "bundler": "metro", 27 | "output": "static", 28 | "favicon": "./assets/images/favicon.png" 29 | }, 30 | "plugins": ["expo-router", "expo-font"], 31 | "experiments": { 32 | "typedRoutes": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mobile/assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /mobile/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/assets/images/favicon.png -------------------------------------------------------------------------------- /mobile/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/assets/images/icon.png -------------------------------------------------------------------------------- /mobile/assets/images/splash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/assets/images/splash-icon.png -------------------------------------------------------------------------------- /mobile/assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/assets/images/splash.png -------------------------------------------------------------------------------- /mobile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nearby", 3 | "main": "expo-router/entry", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "web": "expo start --web", 10 | "test": "jest --watchAll" 11 | }, 12 | "jest": { 13 | "preset": "jest-expo" 14 | }, 15 | "dependencies": { 16 | "@expo-google-fonts/rubik": "^0.2.3", 17 | "@expo/vector-icons": "^14.0.2", 18 | "@gorhom/bottom-sheet": "^5.0.6", 19 | "@react-navigation/native": "^7.0.0", 20 | "@tabler/icons-react-native": "^3.22.0", 21 | "axios": "^1.7.7", 22 | "expo": "~52.0.11", 23 | "expo-font": "~13.0.1", 24 | "expo-linking": "~7.0.3", 25 | "expo-router": "~4.0.9", 26 | "expo-splash-screen": "~0.29.13", 27 | "expo-status-bar": "~2.0.0", 28 | "expo-system-ui": "~4.0.4", 29 | "expo-web-browser": "~14.0.1", 30 | "react": "18.3.1", 31 | "react-dom": "18.3.1", 32 | "react-native": "0.76.3", 33 | "react-native-reanimated": "~3.16.1", 34 | "react-native-safe-area-context": "4.12.0", 35 | "react-native-screens": "~4.1.0", 36 | "react-native-svg": "15.8.0", 37 | "react-native-web": "~0.19.13", 38 | "react-native-gesture-handler": "~2.20.2", 39 | "react-native-maps": "1.18.0", 40 | "expo-location": "~18.0.2", 41 | "expo-camera": "~16.0.7" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.25.2", 45 | "@types/react": "~18.3.12", 46 | "jest": "^29.2.1", 47 | "jest-expo": "~52.0.2", 48 | "react-test-renderer": "18.3.1", 49 | "typescript": "~5.3.3" 50 | }, 51 | "private": true 52 | } 53 | -------------------------------------------------------------------------------- /mobile/src/app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router" 2 | import { colors } from "@/styles/theme" 3 | import { GestureHandlerRootView } from "react-native-gesture-handler" 4 | 5 | import { 6 | useFonts, 7 | Rubik_600SemiBold, 8 | Rubik_400Regular, 9 | Rubik_500Medium, 10 | Rubik_700Bold, 11 | } from "@expo-google-fonts/rubik" 12 | 13 | import { Loading } from "@/components/loading" 14 | 15 | export default function Layout() { 16 | const [fontsLoaded] = useFonts({ 17 | Rubik_600SemiBold, 18 | Rubik_400Regular, 19 | Rubik_500Medium, 20 | Rubik_700Bold, 21 | }) 22 | 23 | if (!fontsLoaded) { 24 | return 25 | } 26 | 27 | return ( 28 | 29 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /mobile/src/app/home.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { View, Alert, Text } from "react-native" 3 | import MapView, { Callout, Marker } from "react-native-maps" 4 | import * as Location from "expo-location" 5 | import { router } from "expo-router" 6 | 7 | import { api } from "@/services/api" 8 | import { fontFamily, colors } from "@/styles/theme" 9 | 10 | import { Places } from "@/components/places" 11 | import { PlaceProps } from "@/components/place" 12 | import { Categories, CategoriesProps } from "@/components/categories" 13 | 14 | type MarketsProps = PlaceProps & { 15 | latitude: number 16 | longitude: number 17 | } 18 | 19 | const currentLocation = { 20 | latitude: -23.561187293883442, 21 | longitude: -46.656451388116494, 22 | } 23 | 24 | export default function Home() { 25 | const [categories, setCategories] = useState([]) 26 | const [category, setCategory] = useState("") 27 | const [markets, setMarkets] = useState([]) 28 | 29 | async function fetchCategories() { 30 | try { 31 | const { data } = await api.get("/categories") 32 | setCategories(data) 33 | setCategory(data[0].id) 34 | } catch (error) { 35 | console.log(error) 36 | Alert.alert("Categorias", "Não foi possível carregar as categorias.") 37 | } 38 | } 39 | 40 | async function fetchMarkets() { 41 | try { 42 | if (!category) { 43 | return 44 | } 45 | 46 | const { data } = await api.get("/markets/category/" + category) 47 | setMarkets(data) 48 | } catch (error) { 49 | console.log(error) 50 | Alert.alert("Locais", "Não foi possível carregar os locais.") 51 | } 52 | } 53 | 54 | async function getCurrentLocation() { 55 | try { 56 | const { granted } = await Location.requestForegroundPermissionsAsync() 57 | 58 | if (granted) { 59 | const location = await Location.getCurrentPositionAsync() 60 | console.log(location) 61 | } 62 | } catch (error) { 63 | console.log(error) 64 | } 65 | } 66 | 67 | useEffect(() => { 68 | fetchCategories() 69 | }, []) 70 | 71 | useEffect(() => { 72 | fetchMarkets() 73 | }, [category]) 74 | 75 | return ( 76 | 77 | 82 | 83 | 92 | 100 | 101 | {markets.map((item) => ( 102 | 111 | router.navigate(`/market/${item.id}`)}> 112 | 113 | 120 | {item.name} 121 | 122 | 123 | 130 | {item.address} 131 | 132 | 133 | 134 | 135 | ))} 136 | 137 | 138 | 139 | 140 | ) 141 | } 142 | -------------------------------------------------------------------------------- /mobile/src/app/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "react-native" 2 | import { router } from "expo-router" 3 | 4 | import { Steps } from "@/components/steps" 5 | import { Button } from "@/components/button" 6 | import { Welcome } from "@/components/welcome" 7 | 8 | export default function Index() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /mobile/src/app/market/[id].tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useRef } from "react" 2 | import { View, Alert, Modal, StatusBar, ScrollView } from "react-native" 3 | import { router, useLocalSearchParams, Redirect } from "expo-router" 4 | import { useCameraPermissions, CameraView } from "expo-camera" 5 | 6 | import { Button } from "@/components/button" 7 | import { Loading } from "@/components/loading" 8 | import { Cover } from "@/components/market/cover" 9 | import { Coupon } from "@/components/market/coupon" 10 | import { Details, PropsDetails } from "@/components/market/details" 11 | 12 | import { api } from "@/services/api" 13 | 14 | type DataProps = PropsDetails & { 15 | cover: string 16 | } 17 | 18 | export default function Market() { 19 | const [data, setData] = useState() 20 | const [coupon, setCoupon] = useState(null) 21 | const [isLoading, setIsLoading] = useState(true) 22 | const [couponIsFetching, setCouponIsFetching] = useState(false) 23 | const [isVisibleCameraModal, setIsVisibleCameraModal] = useState(false) 24 | 25 | const [_, requestPermission] = useCameraPermissions() 26 | const params = useLocalSearchParams<{ id: string }>() 27 | 28 | const qrLock = useRef(false) 29 | console.log(params.id) 30 | 31 | async function fetchMarket() { 32 | try { 33 | const { data } = await api.get(`/markets/${params.id}`) 34 | setData(data) 35 | setIsLoading(false) 36 | } catch (error) { 37 | console.log(error) 38 | Alert.alert("Erro", "Não foi possível carregar os dados", [ 39 | { 40 | text: "OK", 41 | onPress: () => router.back(), 42 | }, 43 | ]) 44 | } 45 | } 46 | 47 | async function handleOpenCamera() { 48 | try { 49 | const { granted } = await requestPermission() 50 | 51 | if (!granted) { 52 | return Alert.alert("Câmera", "Você precisa habilitar o uso da câmera") 53 | } 54 | 55 | qrLock.current = false 56 | setIsVisibleCameraModal(true) 57 | } catch (error) { 58 | console.log(error) 59 | Alert.alert("Câmera", "Não foi possível utilizar a câmera") 60 | } 61 | } 62 | 63 | async function getCoupon(id: string) { 64 | try { 65 | setCouponIsFetching(true) 66 | 67 | const { data } = await api.patch("/coupons/" + id) 68 | 69 | Alert.alert("Cupom", data.coupon) 70 | setCoupon(data.coupon) 71 | } catch (error) { 72 | console.log(error) 73 | Alert.alert("Erro", "Não foi possível utilizar o cupom") 74 | } finally { 75 | setCouponIsFetching(false) 76 | } 77 | } 78 | 79 | function handleUseCoupon(id: string) { 80 | setIsVisibleCameraModal(false) 81 | 82 | Alert.alert( 83 | "Cupom", 84 | "Não é possível reutilizar um cupom resgatado. Deseja realmente resgatar o cupom?", 85 | [ 86 | { style: "cancel", text: "Não" }, 87 | { text: "Sim", onPress: () => getCoupon(id) }, 88 | ] 89 | ) 90 | } 91 | 92 | useEffect(() => { 93 | fetchMarket() 94 | }, [params.id, coupon]) 95 | 96 | if (isLoading) { 97 | return 98 | } 99 | 100 | if (!data) { 101 | return 102 | } 103 | 104 | return ( 105 | 106 |