├── .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 |
107 |
108 |
109 |
110 |
111 | {coupon && }
112 |
113 |
114 |
115 |
118 |
119 |
120 |
121 | {
125 | if (data && !qrLock.current) {
126 | qrLock.current = true
127 | setTimeout(() => handleUseCoupon(data), 500)
128 | }
129 | }}
130 | />
131 |
132 |
133 |
139 |
140 |
141 |
142 | )
143 | }
144 |
--------------------------------------------------------------------------------
/mobile/src/assets/location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/src/assets/location.png
--------------------------------------------------------------------------------
/mobile/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/src/assets/logo.png
--------------------------------------------------------------------------------
/mobile/src/assets/pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-rn/bebd88335f7586cde42fecc74b6ef9d5940b5f13/mobile/src/assets/pin.png
--------------------------------------------------------------------------------
/mobile/src/components/button/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | TouchableOpacity,
3 | TouchableOpacityProps,
4 | Text,
5 | TextProps,
6 | ActivityIndicator,
7 | } from "react-native"
8 | import { IconProps as TablerIconProps } from "@tabler/icons-react-native"
9 |
10 | import { s } from "./styles"
11 | import { colors } from "@/styles/theme"
12 |
13 | type ButtonProps = TouchableOpacityProps & {
14 | isLoading?: boolean
15 | }
16 |
17 | function Button({ children, style, isLoading = false, ...rest }: ButtonProps) {
18 | return (
19 |
25 | {isLoading ? (
26 |
27 | ) : (
28 | children
29 | )}
30 |
31 | )
32 | }
33 |
34 | function Title({ children }: TextProps) {
35 | return {children}
36 | }
37 |
38 | type IconProps = {
39 | icon: React.ComponentType
40 | }
41 |
42 | function Icon({ icon: Icon }: IconProps) {
43 | return
44 | }
45 |
46 | Button.Title = Title
47 | Button.Icon = Icon
48 |
49 | export { Button }
50 |
--------------------------------------------------------------------------------
/mobile/src/components/button/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | height: 56,
7 | maxHeight: 56,
8 | backgroundColor: colors.green.base,
9 | borderRadius: 10,
10 | alignItems: "center",
11 | justifyContent: "center",
12 | flexDirection: "row",
13 | gap: 14,
14 | },
15 | title: {
16 | color: colors.gray[100],
17 | fontFamily: fontFamily.semiBold,
18 | fontSize: 16,
19 | },
20 | })
21 |
--------------------------------------------------------------------------------
/mobile/src/components/categories/index.tsx:
--------------------------------------------------------------------------------
1 | import { FlatList } from "react-native"
2 |
3 | import { s } from "./styles"
4 | import { Category } from "../category"
5 |
6 | export type CategoriesProps = {
7 | id: string
8 | name: string
9 | }[]
10 |
11 | type Props = {
12 | data: CategoriesProps
13 | selected: string
14 | onSelect: (id: string) => void
15 | }
16 |
17 | export function Categories({ data, selected, onSelect }: Props) {
18 | return (
19 | item.id}
22 | renderItem={({ item }) => (
23 | onSelect(item.id)}
27 | isSelected={item.id === selected}
28 | />
29 | )}
30 | horizontal
31 | showsHorizontalScrollIndicator={false}
32 | contentContainerStyle={s.content}
33 | style={s.container}
34 | />
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/mobile/src/components/categories/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 |
3 | export const s = StyleSheet.create({
4 | container: {
5 | maxHeight: 36,
6 | position: "absolute",
7 | zIndex: 1,
8 | top: 64,
9 | },
10 | content: {
11 | gap: 8,
12 | paddingHorizontal: 24,
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/mobile/src/components/category/index.tsx:
--------------------------------------------------------------------------------
1 | import { Text, Pressable, PressableProps } from "react-native"
2 |
3 | import { s } from "./styles"
4 | import { colors } from "@/styles/theme"
5 | import { categoriesIcons } from "@/utils/categories-icons"
6 |
7 | type Props = PressableProps & {
8 | name: string
9 | iconId: string
10 | isSelected?: boolean
11 | }
12 |
13 | export function Category({ name, iconId, isSelected = false, ...rest }: Props) {
14 | const Icon = categoriesIcons[iconId]
15 |
16 | return (
17 |
21 |
22 | {name}
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/mobile/src/components/category/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | height: 36,
7 | backgroundColor: colors.gray[100],
8 | borderWidth: 1,
9 | borderColor: colors.gray[300],
10 | borderRadius: 8,
11 | justifyContent: "center",
12 | alignItems: "center",
13 | flexDirection: "row",
14 | paddingHorizontal: 12,
15 | gap: 10,
16 | },
17 | name: {
18 | fontSize: 14,
19 | color: colors.gray[500],
20 | fontFamily: fontFamily.regular,
21 | },
22 | containerSelected: {
23 | backgroundColor: colors.green.base,
24 | borderColor: colors.green.base,
25 | },
26 | nameSelected: {
27 | color: colors.gray[100],
28 | },
29 | })
30 |
--------------------------------------------------------------------------------
/mobile/src/components/loading/index.tsx:
--------------------------------------------------------------------------------
1 | import { ActivityIndicator } from "react-native"
2 |
3 | import { s } from "./styles"
4 | import { colors } from "@/styles/theme"
5 |
6 | export function Loading() {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/mobile/src/components/loading/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | flex: 1,
7 | justifyContent: "center",
8 | alignItems: "center",
9 | backgroundColor: colors.gray[100],
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/mobile/src/components/market/coupon/index.tsx:
--------------------------------------------------------------------------------
1 | import { Text, View } from "react-native"
2 | import { IconTicket } from "@tabler/icons-react-native"
3 |
4 | import { s } from "./styles"
5 | import { colors } from "@/styles/theme"
6 |
7 | type Props = {
8 | code: string
9 | }
10 |
11 | export function Coupon({ code }: Props) {
12 | return (
13 |
14 | Utilize esse cupom
15 |
16 |
17 |
18 | {code}
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/mobile/src/components/market/coupon/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | paddingHorizontal: 32,
7 | },
8 | title: {
9 | color: colors.gray[500],
10 | fontFamily: fontFamily.medium,
11 | marginBottom: 12,
12 | fontSize: 14,
13 | },
14 | content: {
15 | flexDirection: "row",
16 | backgroundColor: colors.green.soft,
17 | paddingHorizontal: 8,
18 | paddingVertical: 10,
19 | borderRadius: 8,
20 | alignItems: "center",
21 | gap: 10,
22 | },
23 | code: {
24 | color: colors.gray[600],
25 | fontSize: 16,
26 | fontFamily: fontFamily.semiBold,
27 | textTransform: "uppercase",
28 | },
29 | })
30 |
--------------------------------------------------------------------------------
/mobile/src/components/market/cover/index.tsx:
--------------------------------------------------------------------------------
1 | import { ImageBackground, View } from "react-native"
2 | import { IconArrowLeft } from "@tabler/icons-react-native"
3 |
4 | import { router } from "expo-router"
5 |
6 | import { s } from "./styles"
7 | import { Button } from "@/components/button"
8 |
9 | type Props = {
10 | uri: string
11 | }
12 |
13 | export function Cover({ uri }: Props) {
14 | return (
15 |
16 |
17 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/mobile/src/components/market/cover/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | width: "100%",
7 | height: 232,
8 | marginBottom: -32,
9 | backgroundColor: colors.gray[200],
10 | },
11 | header: {
12 | padding: 24,
13 | paddingTop: 56,
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/mobile/src/components/market/details/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native"
2 | import { IconPhone, IconMapPin, IconTicket } from "@tabler/icons-react-native"
3 |
4 | import { Info } from "@/components/market/info"
5 | import { s } from "./styles"
6 |
7 | export type PropsDetails = {
8 | name: string
9 | description: string
10 | address: string
11 | phone: string
12 | coupons: number
13 | rules: {
14 | id: string
15 | description: string
16 | }[]
17 | }
18 |
19 | type Props = {
20 | data: PropsDetails
21 | }
22 |
23 | export function Details({ data }: Props) {
24 | return (
25 |
26 | {data.name}
27 | {data.description}
28 |
29 |
30 | Informações
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 | Regulamento
42 | {data.rules.map((item) => (
43 |
44 | {`\u2022 ${item.description}`}
45 |
46 | ))}
47 |
48 |
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/mobile/src/components/market/details/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | padding: 32,
7 | paddingBottom: 0,
8 | borderTopStartRadius: 20,
9 | borderTopEndRadius: 20,
10 | backgroundColor: colors.gray[100],
11 | },
12 | name: {
13 | fontSize: 20,
14 | fontFamily: fontFamily.bold,
15 | color: colors.gray[600],
16 | },
17 | description: {
18 | fontSize: 16,
19 | fontFamily: fontFamily.regular,
20 | color: colors.gray[500],
21 | marginTop: 12,
22 | marginBottom: 32,
23 | lineHeight: 22,
24 | },
25 | group: {
26 | width: "100%",
27 | borderBottomWidth: 1,
28 | borderBottomColor: colors.gray[200],
29 | paddingBottom: 16,
30 | marginBottom: 16,
31 | },
32 | title: {
33 | fontSize: 14,
34 | fontFamily: fontFamily.medium,
35 | color: colors.gray[500],
36 | marginBottom: 12,
37 | },
38 | rule: {},
39 | })
40 |
--------------------------------------------------------------------------------
/mobile/src/components/market/info/index.tsx:
--------------------------------------------------------------------------------
1 | import { Text, View } from "react-native"
2 | import { Icon, IconProps } from "@tabler/icons-react-native"
3 |
4 | import { s } from "./styles"
5 | import { colors } from "@/styles/theme"
6 |
7 | type Props = {
8 | description: string
9 | icon: React.ComponentType
10 | }
11 |
12 | export function Info({ icon: Icon, description }: Props) {
13 | return (
14 |
15 |
16 | {description}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/mobile/src/components/market/info/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | flexDirection: "row",
7 | alignItems: "center",
8 | gap: 8,
9 | },
10 | text: {
11 | color: colors.gray[500],
12 | fontSize: 14,
13 | fontFamily: fontFamily.regular,
14 | lineHeight: 22.4,
15 | flex: 1,
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/mobile/src/components/place/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Text,
3 | TouchableOpacity,
4 | TouchableOpacityProps,
5 | View,
6 | Image,
7 | } from "react-native"
8 | import { IconTicket } from "@tabler/icons-react-native"
9 |
10 | import { s } from "./styles"
11 | import { colors } from "@/styles/theme"
12 |
13 | export type PlaceProps = {
14 | id: string
15 | name: string
16 | description: string
17 | coupons: number
18 | cover: string
19 | address: string
20 | }
21 |
22 | type Props = TouchableOpacityProps & {
23 | data: PlaceProps
24 | }
25 |
26 | export function Place({ data, ...rest }: Props) {
27 | return (
28 |
29 |
30 |
31 |
32 | {data.name}
33 |
34 | {data.description}
35 |
36 |
37 |
38 |
39 | {data.coupons} cupons disponíveis
40 |
41 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/mobile/src/components/place/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | height: 120,
7 | width: "100%",
8 | padding: 8,
9 | borderWidth: 1,
10 | borderColor: colors.gray[200],
11 | borderRadius: 12,
12 | flexDirection: "row",
13 | gap: 16,
14 | alignItems: "center",
15 | },
16 | image: {
17 | width: 116,
18 | height: 104,
19 | backgroundColor: colors.gray[200],
20 | borderRadius: 8,
21 | },
22 | content: {
23 | flex: 1,
24 | gap: 4,
25 | },
26 | name: {
27 | fontSize: 14,
28 | fontFamily: fontFamily.medium,
29 | color: colors.gray[600],
30 | },
31 | description: {
32 | fontSize: 12,
33 | fontFamily: fontFamily.regular,
34 | color: colors.gray[500],
35 | },
36 | footer: {
37 | flexDirection: "row",
38 | gap: 7,
39 | marginTop: 10,
40 | },
41 | tickets: {
42 | fontSize: 12,
43 | fontFamily: fontFamily.regular,
44 | color: colors.gray[400],
45 | },
46 | })
47 |
--------------------------------------------------------------------------------
/mobile/src/components/places/index.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from "react"
2 | import { Text, useWindowDimensions } from "react-native"
3 | import BottomSheet, { BottomSheetFlatList } from "@gorhom/bottom-sheet"
4 | import { router } from "expo-router"
5 |
6 | import { s } from "./styles"
7 | import { Place, PlaceProps } from "../place"
8 |
9 | type Props = {
10 | data: PlaceProps[]
11 | }
12 |
13 | export function Places({ data }: Props) {
14 | const dimensions = useWindowDimensions()
15 | const bottomSheetRef = useRef(null)
16 |
17 | const snapPoints = {
18 | min: 278,
19 | max: dimensions.height - 128,
20 | }
21 |
22 | return (
23 |
30 | item.id}
33 | renderItem={({ item }) => (
34 | router.navigate(`/market/${item.id}`)}
37 | />
38 | )}
39 | contentContainerStyle={s.content}
40 | ListHeaderComponent={() => (
41 | Explore locais perto de você
42 | )}
43 | showsVerticalScrollIndicator={false}
44 | />
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/mobile/src/components/places/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | backgroundColor: colors.gray[100],
7 | },
8 | content: {
9 | gap: 12,
10 | padding: 24,
11 | paddingBottom: 100,
12 | },
13 | indicator: {
14 | width: 80,
15 | height: 4,
16 | backgroundColor: colors.gray[300],
17 | },
18 | title: {
19 | color: colors.gray[600],
20 | fontSize: 16,
21 | fontFamily: fontFamily.regular,
22 | marginBottom: 16,
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/mobile/src/components/step/index.tsx:
--------------------------------------------------------------------------------
1 | import { Text, View } from "react-native"
2 | import { IconProps } from "@tabler/icons-react-native"
3 |
4 | import { colors } from "@/styles/theme"
5 | import { s } from "./styles"
6 |
7 | type Props = {
8 | title: string
9 | description: string
10 | icon: React.ComponentType
11 | }
12 |
13 | export function Step({ title, description, icon: Icon }: Props) {
14 | return (
15 |
16 | {Icon && }
17 |
18 |
19 | {title}
20 | {description}
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/mobile/src/components/step/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | width: "100%",
7 | flexDirection: "row",
8 | gap: 16,
9 | },
10 | details: {
11 | flex: 1,
12 | },
13 | title: {
14 | fontSize: 16,
15 | fontFamily: fontFamily.semiBold,
16 | color: colors.gray[600],
17 | },
18 | description: {
19 | fontSize: 14,
20 | fontFamily: fontFamily.regular,
21 | color: colors.gray[500],
22 | marginTop: 4,
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/mobile/src/components/steps/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native"
2 | import { IconMapPin, IconQrcode, IconTicket } from "@tabler/icons-react-native"
3 |
4 | import { s } from "./styles"
5 | import { Step } from "../step"
6 |
7 | export function Steps() {
8 | return (
9 |
10 | Veja como funciona:
11 |
12 |
17 |
18 |
23 |
24 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/mobile/src/components/steps/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | container: {
6 | gap: 24,
7 | flex: 1,
8 | },
9 | title: {
10 | fontSize: 16,
11 | fontFamily: fontFamily.regular,
12 | color: colors.gray[500],
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/mobile/src/components/welcome/index.tsx:
--------------------------------------------------------------------------------
1 | import { Image, Text, View } from "react-native"
2 |
3 | import { s } from "./styles"
4 |
5 | export function Welcome() {
6 | return (
7 |
8 |
9 |
10 | Boas vindas ao Nearby!
11 |
12 |
13 | Tenha cupons de vantagem para usar em {"\n"}
14 | seus estabelecimentos favoritos.
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/mobile/src/components/welcome/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native"
2 | import { colors, fontFamily } from "@/styles/theme"
3 |
4 | export const s = StyleSheet.create({
5 | logo: {
6 | width: 48,
7 | height: 48,
8 | marginTop: 24,
9 | marginBottom: 28,
10 | },
11 | title: {
12 | fontSize: 24,
13 | fontFamily: fontFamily.bold,
14 | color: colors.gray[600],
15 | },
16 | subtitle: {
17 | fontSize: 16,
18 | fontFamily: fontFamily.regular,
19 | color: colors.gray[500],
20 | marginTop: 12,
21 | },
22 | })
23 |
--------------------------------------------------------------------------------
/mobile/src/services/api.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 |
3 | export const api = axios.create({
4 | baseURL: "http://192.168.0.213:3333",
5 | timeout: 700,
6 | })
7 |
--------------------------------------------------------------------------------
/mobile/src/styles/colors.ts:
--------------------------------------------------------------------------------
1 | export const colors = {
2 | gray: {
3 | 100: "#FCFDFE",
4 | 200: "#E1EBF4",
5 | 300: "#C4D0DB",
6 | 400: "#73808C",
7 | 500: "#45525F",
8 | 600: "#1A1F24",
9 | },
10 |
11 | green: {
12 | soft: "#E9F3EF",
13 | light: "#3B9B62",
14 | base: "#257F49",
15 | dark: "#052914",
16 | },
17 |
18 | red: {
19 | light: "#FDEDED",
20 | base: "#F94144",
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/mobile/src/styles/font-family.ts:
--------------------------------------------------------------------------------
1 | export const fontFamily = {
2 | bold: "Rubik_700Bold",
3 | medium: "Rubik_500Medium",
4 | regular: "Rubik_400Regular",
5 | semiBold: "Rubik_600SemiBold",
6 | }
7 |
--------------------------------------------------------------------------------
/mobile/src/styles/theme.ts:
--------------------------------------------------------------------------------
1 | import { colors } from "./colors"
2 | import { fontFamily } from "./font-family"
3 |
4 | export { colors, fontFamily }
5 |
--------------------------------------------------------------------------------
/mobile/src/utils/categories-icons.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IconBed,
3 | IconMovie,
4 | IconCoffee,
5 | IconShoppingBag,
6 | IconToolsKitchen2,
7 | IconProps,
8 | } from "@tabler/icons-react-native"
9 |
10 | export const categoriesIcons: Record> = {
11 | "146b1a88-b3d3-4232-8b8f-c1f006f1e86d": IconToolsKitchen2,
12 | "52e81585-f71a-44cd-8bd0-49771e45da44": IconShoppingBag,
13 | "57d6e5ff-35f6-4d21-a521-84f23d511d25": IconBed,
14 | "826910d4-187d-4c15-88f4-382b7e056739": IconMovie,
15 | "abce52cf-b33b-4b3c-8972-eb72c66c83e4": IconCoffee,
16 | }
17 |
--------------------------------------------------------------------------------
/mobile/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true,
5 | "paths": {
6 | "@/*": ["./src/*"]
7 | }
8 | },
9 | "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------