diff --git a/apps/frontend-mobile/.env.example b/apps/frontend-mobile/.env.example index 0668dde..b544ae9 100644 --- a/apps/frontend-mobile/.env.example +++ b/apps/frontend-mobile/.env.example @@ -6,8 +6,3 @@ EXPO_PUBLIC_API_URL=http://localhost:3001/api # For production builds, update this to: # EXPO_PUBLIC_API_URL=https://api.yourdomain.com/api - -# Grafana Alloy OTLP/HTTP endpoint for Faro RUM (must be reachable from the -# device — use a LAN IP / public hostname, NOT localhost). -# Open router :4318 → srv84-macos:4318, or proxy /faro via Nginx Proxy Manager. -EXPO_PUBLIC_FARO_URL=http://grafana.hacecalor.net:4318 diff --git a/apps/frontend-mobile/app.json b/apps/frontend-mobile/app.json index 55b0a94..8fe6891 100644 --- a/apps/frontend-mobile/app.json +++ b/apps/frontend-mobile/app.json @@ -24,13 +24,7 @@ "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#007AFF" }, - "package": "com.farmafinder.app", - "googleServicesFile": "./google-services.json", - "config": { - "googleMaps": { - "apiKey": "" - } - } + "package": "com.farmafinder.app" }, "plugins": [ "expo-router", diff --git a/apps/frontend-mobile/app/(tabs)/map.tsx b/apps/frontend-mobile/app/(tabs)/map.tsx index 6e0d523..acb25e2 100644 --- a/apps/frontend-mobile/app/(tabs)/map.tsx +++ b/apps/frontend-mobile/app/(tabs)/map.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { View, StyleSheet, Text } from 'react-native'; -import MapView, { Marker } from 'react-native-maps'; +import MapView, { Marker, UrlTile } from 'react-native-maps'; import { useRouter } from 'expo-router'; import { getPharmacies } from '../../services/pharmacies'; import { LoadingSpinner } from '../../components/LoadingSpinner'; @@ -54,11 +54,18 @@ export default function MapScreen() { + {pharmacies.map((pharmacy) => ( item.nregistro} renderItem={({ item }) => } - ListHeaderComponent={ - <> - {products.length > 0 && ( - - {t('search.parapharmacy')} - {products.map((product) => ( - router.push(`/product/${product.source}/${product.id || product._id}`)} - activeOpacity={0.7} - > - - - - {t('search.parapharmacy')} - + ListEmptyComponent={ + products.length === 0 ? null : undefined + } + ListFooterComponent={ + products.length > 0 ? ( + + {t('search.parapharmacy')} + {products.map((product) => ( + router.push(`/product/${product.source}/${product.id || product._id}`)} + activeOpacity={0.7} + > + + + + {t('search.parapharmacy')} - {product.name} - {product.brand} • {product.price}€ - - ))} - - )} - + {product.name} + {product.brand} • {product.price}€ + + + ))} + + ) : null } contentContainerStyle={[styles.list, isTablet && styles.listTablet]} showsVerticalScrollIndicator={false} diff --git a/apps/frontend-mobile/app/_layout.tsx b/apps/frontend-mobile/app/_layout.tsx index 3283cf9..45fa389 100644 --- a/apps/frontend-mobile/app/_layout.tsx +++ b/apps/frontend-mobile/app/_layout.tsx @@ -9,10 +9,6 @@ import { useAuthStore } from '../store/authStore'; import { registerForPushNotifications, addNotificationListener, addNotificationResponseListener } from '../services/notifications'; import { ThemeProvider, useThemeContext } from '../components/ThemeProvider'; import { LanguageProvider, useTranslation } from '../src/i18n'; -import { initFaro } from '../services/faro'; - -// Boot Faro RUM once, as early as possible. -initFaro(); const queryClient = new QueryClient(); diff --git a/apps/frontend-mobile/app/medicine/[id].tsx b/apps/frontend-mobile/app/medicine/[id].tsx index 1914c45..20f053a 100644 --- a/apps/frontend-mobile/app/medicine/[id].tsx +++ b/apps/frontend-mobile/app/medicine/[id].tsx @@ -3,6 +3,7 @@ import { View, Text, ScrollView, StyleSheet, TouchableOpacity, Linking } from 'r import { useLocalSearchParams, useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import * as Location from 'expo-location'; +import MapView, { Marker, UrlTile } from 'react-native-maps'; import { getMedicine, getMedicinePharmacies } from '../../services/medicines'; import { subscribeToMedicine, unsubscribeFromMedicine } from '../../services/notifications'; import { useAuth } from '../../hooks/useAuth'; @@ -205,11 +206,38 @@ export default function MedicineDetailScreen() { {locatedPharmacies.length > 0 && ( - - - - {t('medicine.mapSoon')} - + + + + {locatedPharmacies.map((pharm) => { + const lat = getPharmacyLat(pharm); + const lon = getPharmacyLon(pharm); + if (lat == null || lon == null) return null; + return ( + router.push(`/pharmacy/${pharm.pharmacy_id || pharm.id}`)} + /> + ); + })} + )} @@ -362,13 +390,9 @@ const styles = StyleSheet.create({ height: 200, borderRadius: borderRadius.lg, overflow: 'hidden', - alignItems: 'center', - justifyContent: 'center', - gap: spacing.sm, }, - mapPlaceholder: { - fontSize: 14, - fontWeight: '500', + map: { + flex: 1, }, pharmaciesSection: { marginTop: spacing.sm, diff --git a/apps/frontend-mobile/app/pharmacy/[id].tsx b/apps/frontend-mobile/app/pharmacy/[id].tsx index a2165a0..721e033 100644 --- a/apps/frontend-mobile/app/pharmacy/[id].tsx +++ b/apps/frontend-mobile/app/pharmacy/[id].tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { View, Text, ScrollView, StyleSheet, TouchableOpacity, Linking } from 'react-native'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; -import MapView, { Marker } from 'react-native-maps'; +import MapView, { Marker, UrlTile } from 'react-native-maps'; import { getPharmacy, getPharmacyMedicines } from '../../services/pharmacies'; import { subscribeToMedicine, unsubscribeFromMedicine } from '../../services/notifications'; import { useAuth } from '../../hooks/useAuth'; @@ -125,6 +125,7 @@ export default function PharmacyDetailScreen() { + =6.0.0", - "react": ">=18.0.0", - "react-native": ">=0.70.0", - "react-native-mmkv": ">=2.0.0" - }, - "peerDependenciesMeta": { - "@react-navigation/native": { - "optional": true - }, - "react-native-mmkv": { - "optional": true - } - } - }, - "apps/frontend-mobile/node_modules/@grafana/faro-react-native/node_modules/@react-native-async-storage/async-storage": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", - "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", - "dependencies": { - "merge-options": "^3.0.4" - }, - "peerDependencies": { - "react-native": "^0.0.0-0 || >=0.60 <1.0" - } - }, - "apps/frontend-mobile/node_modules/@opentelemetry/api-logs": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.219.0.tgz", - "integrity": "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "apps/frontend-mobile/node_modules/@opentelemetry/core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", - "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "apps/frontend-mobile/node_modules/@opentelemetry/otlp-transformer": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.219.0.tgz", - "integrity": "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==", - "dependencies": { - "@opentelemetry/api-logs": "0.219.0", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/sdk-logs": "0.219.0", - "@opentelemetry/sdk-metrics": "2.8.0", - "@opentelemetry/sdk-trace-base": "2.8.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "apps/frontend-mobile/node_modules/@opentelemetry/resources": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", - "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", - "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "apps/frontend-mobile/node_modules/@opentelemetry/sdk-logs": { - "version": "0.219.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.219.0.tgz", - "integrity": "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==", - "dependencies": { - "@opentelemetry/api-logs": "0.219.0", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "apps/frontend-mobile/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", - "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", - "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "apps/frontend-mobile/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", - "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", - "dependencies": { - "@opentelemetry/core": "2.8.0", - "@opentelemetry/resources": "2.8.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, "apps/frontend-mobile/node_modules/@react-native-async-storage/async-storage": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", @@ -1278,14 +1125,6 @@ } } }, - "apps/frontend-mobile/node_modules/react-native-device-info": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-11.1.0.tgz", - "integrity": "sha512-hzXJSObJdezEz0hF7MAJ3tGeoesuQWenXXt9mrQR9Mjb8kXpZ09rqSsZ/quNpJdZpQ3rYiFa3/0GFG5KNn9PBg==", - "peerDependencies": { - "react-native": "*" - } - }, "apps/frontend-mobile/node_modules/react-native-drawer-layout": { "version": "4.2.7", "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-4.2.7.tgz",