Grapevine_Disease_Detection/VinEye/package.json
Yanis 26d0f39986 feat(mobile): backend sync auth + ban handling + scan push
Wires the React Native app to the vineye-admin backend so user accounts
and scans flow into the admin panel, and so a ban applied via the panel
takes effect on the device on the next app boot (or sooner on any
authenticated request).

Core
- Install expo-secure-store for storing the better-auth session token.
  Falls back to AsyncStorage on web/Expo Go where SecureStore is unavailable.
- New tokenStorage service with saveToken/getToken/removeToken and a
  stable per-install getDeviceId() (used to derive the deterministic
  password the backend signs sign-up/sign-in with).
- Extend the API client with apiPost(), automatic Bearer header attach,
  and a tiny pub/sub (authEvents) that emits 'unauthorized' on 401 and
  'banned' on 403 with banned: true. Handlers are global so any request
  can trigger logout or open the BannedModal.

Auth
- New services/api/auth.ts: syncUser (POST /auth/sync), fetchMe (GET
  /auth/me), signOutServer (POST /auth/sign-out, best-effort).
- types/auth.User now carries optional banned/bannedReason/role/xp/level
  hydrated from the backend.
- AuthContext.login is now async vs the backend; the server-side id
  replaces any locally-generated UUID so mobile and admin agree on the
  same User row. Hydration is optimistic from AsyncStorage (never blocks
  the splash on the network) and a background fetchMe picks up server
  ban changes. logout/resetAccount best-effort revoke the server session.
- AuthChoiceScreen surfaces sign-up failures through a toast instead of
  silently dropping the user into the app with no account.

Ban UX
- BannedModal: non-dismissible Tailwind modal with the bannedReason
  interpolated and a CTA that calls resetAccount. Mounted globally in
  RootNavigator and toggled by isBanned from AuthContext.
- Banned state is persisted alongside the User in AsyncStorage so the
  modal stays visible across restarts even when /auth/me is unreachable.

Scan sync
- New services/api/scans.pushScan() that maps the mobile ScanRecord to
  the backend body: confidence /100 (0-100 → 0-1 backend), diseaseSlug
  passthrough (server resolves to diseaseId), latitude/longitude direct,
  imageUrl always null (V1 keeps photos local-only), customName dropped
  (no column server-side, stays in AsyncStorage).
- useHistory.addScan now fires pushScan after the local save and ignores
  failures so the app stays usable offline.

i18n
- New keys auth.errors.network/signupFailed and auth.banned.{title,
  description,descriptionNoReason,cta}. The fr/en files also include
  scanner gallery placeholder keys from an adjacent feature WIP — not
  part of this commit's scope but bundled here to avoid splitting a
  small JSON.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:11:11 +02:00

78 lines
2.3 KiB
JSON

{
"name": "vineye",
"version": "1.0.0",
"main": "index.ts",
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web"
},
"dependencies": {
"@expo/vector-icons": "^15.1.1",
"@gorhom/bottom-sheet": "^5.2.12",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-native-community/netinfo": "11.4.1",
"@react-navigation/bottom-tabs": "^7.15.9",
"@react-navigation/native": "^7.2.2",
"@react-navigation/native-stack": "^7.14.10",
"@rn-primitives/portal": "^1.4.0",
"@rn-primitives/separator": "^1.4.0",
"@rn-primitives/slot": "^1.4.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"expo": "~54.0.33",
"expo-camera": "~17.0.10",
"expo-constants": "~18.0.13",
"expo-crypto": "~15.0.9",
"expo-dev-client": "~6.0.21",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
"expo-image-manipulator": "^14.0.8",
"expo-linear-gradient": "~15.0.8",
"expo-localization": "~17.0.8",
"expo-location": "~19.0.8",
"expo-navigation-bar": "~5.0.10",
"expo-network": "~8.0.8",
"expo-secure-store": "^55.0.13",
"expo-status-bar": "~3.0.9",
"i18next": "^26.0.1",
"jpeg-js": "^0.4.4",
"lottie-react-native": "^7.3.6",
"lucide-react-native": "^1.7.0",
"nativewind": "^4.2.3",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-i18next": "^17.0.1",
"react-lucid": "^0.0.1",
"react-native": "0.81.5",
"react-native-fast-tflite": "^3.0.1",
"react-native-gesture-handler": "~2.28.0",
"react-native-nitro-modules": "^0.35.6",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "^15.12.1",
"react-native-web": "^0.21.2",
"react-native-webview": "~13.15.0",
"react-native-worklets": "0.5.1",
"sonner-native": "^0.24.0",
"tailwind-merge": "^3.5.0",
"tailwindcss": "3.4.17",
"zod": "^4.4.1"
},
"devDependencies": {
"@types/react": "~19.1.0",
"tailwindcss-animate": "^1.0.7",
"typescript": "~5.9.2"
},
"private": true,
"pnpm": {
"onlyBuiltDependencies": [
"@expo/config-plugins",
"expo",
"expo-modules-core"
]
}
}