From 015b528127cfe9e9f32c0d5b3d40f915d043f7b1 Mon Sep 17 00:00:00 2001 From: "r.mubarakzyanov" Date: Wed, 26 Aug 2026 23:01:07 +0300 Subject: [PATCH] initial --- .gitignore | 32 + README.md | 178 + app.json | 105 + app/(auth)/_layout.tsx | 10 + app/(auth)/forgot.tsx | 62 + app/(auth)/login.tsx | 191 + app/(auth)/register.tsx | 120 + app/(auth)/reset.tsx | 88 + app/(auth)/verify.tsx | 94 + app/(tabs)/_layout.tsx | 129 + app/(tabs)/expiring.tsx | 113 + app/(tabs)/index.tsx | 108 + app/(tabs)/profile.tsx | 260 + app/(tabs)/search.tsx | 106 + app/_layout.tsx | 89 + app/document/[id].tsx | 298 + app/document/edit/[id].tsx | 283 + app/documents/[propertyId].tsx | 120 + app/index.tsx | 6 + app/profile/edit.tsx | 172 + app/profile/password.tsx | 99 + app/property/[id].tsx | 227 + app/property/add.tsx | 6 + app/property/edit/[id].tsx | 8 + app/property/location.tsx | 279 + app/upload.tsx | 422 + assets/adaptive-icon.png | Bin 0 -> 6529 bytes assets/favicon.png | Bin 0 -> 289 bytes assets/icon.png | Bin 0 -> 7389 bytes assets/splash-icon.png | Bin 0 -> 2651 bytes babel.config.js | 10 + package-lock.json | 12214 +++++++++++++++++++++++ package.json | 62 + patches/expo-localization+16.0.1.patch | 13 + src/api/config.ts | 50 + src/api/http.ts | 118 + src/api/index.ts | 11 + src/api/mock.ts | 394 + src/api/real.ts | 371 + src/api/types.ts | 186 + src/auth/AuthContext.tsx | 94 + src/auth/tokenStore.ts | 15 + src/components/AppBar.tsx | 63 + src/components/AuthShell.tsx | 109 + src/components/CodeField.tsx | 54 + src/components/DateField.tsx | 89 + src/components/OfflineBanner.tsx | 47 + src/components/PdfView.tsx | 61 + src/components/PropertyCover.tsx | 104 + src/components/PropertyForm.tsx | 430 + src/components/domain.tsx | 168 + src/components/ui.tsx | 394 + src/i18n/en.ts | 357 + src/i18n/index.tsx | 116 + src/i18n/it.ts | 360 + src/lib/allDocuments.ts | 18 + src/lib/documentFile.ts | 109 + src/lib/format.ts | 43 + src/lib/network.ts | 30 + src/lib/pendingLocation.ts | 25 + src/lib/permissions.ts | 57 + src/lib/pickImage.ts | 69 + src/lib/queryClient.ts | 38 + src/scanner/scanToPdf.ts | 108 + src/theme/index.ts | 77 + src/theme/tokens.ts | 116 + tsconfig.json | 16 + 67 files changed, 20201 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app.json create mode 100644 app/(auth)/_layout.tsx create mode 100644 app/(auth)/forgot.tsx create mode 100644 app/(auth)/login.tsx create mode 100644 app/(auth)/register.tsx create mode 100644 app/(auth)/reset.tsx create mode 100644 app/(auth)/verify.tsx create mode 100644 app/(tabs)/_layout.tsx create mode 100644 app/(tabs)/expiring.tsx create mode 100644 app/(tabs)/index.tsx create mode 100644 app/(tabs)/profile.tsx create mode 100644 app/(tabs)/search.tsx create mode 100644 app/_layout.tsx create mode 100644 app/document/[id].tsx create mode 100644 app/document/edit/[id].tsx create mode 100644 app/documents/[propertyId].tsx create mode 100644 app/index.tsx create mode 100644 app/profile/edit.tsx create mode 100644 app/profile/password.tsx create mode 100644 app/property/[id].tsx create mode 100644 app/property/add.tsx create mode 100644 app/property/edit/[id].tsx create mode 100644 app/property/location.tsx create mode 100644 app/upload.tsx create mode 100644 assets/adaptive-icon.png create mode 100644 assets/favicon.png create mode 100644 assets/icon.png create mode 100644 assets/splash-icon.png create mode 100644 babel.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 patches/expo-localization+16.0.1.patch create mode 100644 src/api/config.ts create mode 100644 src/api/http.ts create mode 100644 src/api/index.ts create mode 100644 src/api/mock.ts create mode 100644 src/api/real.ts create mode 100644 src/api/types.ts create mode 100644 src/auth/AuthContext.tsx create mode 100644 src/auth/tokenStore.ts create mode 100644 src/components/AppBar.tsx create mode 100644 src/components/AuthShell.tsx create mode 100644 src/components/CodeField.tsx create mode 100644 src/components/DateField.tsx create mode 100644 src/components/OfflineBanner.tsx create mode 100644 src/components/PdfView.tsx create mode 100644 src/components/PropertyCover.tsx create mode 100644 src/components/PropertyForm.tsx create mode 100644 src/components/domain.tsx create mode 100644 src/components/ui.tsx create mode 100644 src/i18n/en.ts create mode 100644 src/i18n/index.tsx create mode 100644 src/i18n/it.ts create mode 100644 src/lib/allDocuments.ts create mode 100644 src/lib/documentFile.ts create mode 100644 src/lib/format.ts create mode 100644 src/lib/network.ts create mode 100644 src/lib/pendingLocation.ts create mode 100644 src/lib/permissions.ts create mode 100644 src/lib/pickImage.ts create mode 100644 src/lib/queryClient.ts create mode 100644 src/scanner/scanToPdf.ts create mode 100644 src/theme/index.ts create mode 100644 src/theme/tokens.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c25011f --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# dependencies +node_modules/ + +# expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# native +/ios +/android +*.orig.* + +# metro +.metro-health-check* + +# misc +.DS_Store +*.log +npm-debug.* +yarn-debug.* +yarn-error.* + +# local env +.env*.local + +# ide +.idea/ + +# internal docs +docs/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..58bd706 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# CasaDoc — mobile app + +Mobile client for the CasaDoc property-document platform, for iOS and Android +from a single codebase. + +**Stack:** Expo SDK 52 · React Native 0.76 · expo-router · TypeScript · +React Query · expo-secure-store · i18n-js. + +The app can run against the PHP backend or against in-memory demo data; the +choice is one constant in `src/api/config.ts`. + +--- + +## Features + +**Account** + +- Login with email and password, bearer token kept in SecureStore, auto-login + on next launch. +- Registration with e-mail code verification. +- Password recovery (request a code, set a new password) and password change + from inside the app. +- Profile editing, avatar, account deletion. +- Interface in Italian and English, following the device language, with a + manual override in the profile. + +**Properties** + +- List with cover image, address and document counters. +- Detail screen with document categories as folders and recent documents. +- Create and edit, including a map picker for the address (react-native-maps). + +**Documents** + +- Per-property list, filtered by category and by status. +- Search across every document of every property. +- Expiry tracking: valid, expiring and expired badges, warning banners, and a + dedicated tab listing everything that needs attention. +- Detail screen with an inline preview for images and a card that hands PDFs to + the phone's own viewer; sharing and deletion. +- Editing of name, category and expiry date. + +**Adding a document** + +- From the file picker, from the photo library, or with the built-in scanner. +- The scanner uses the native document scanner (ML Kit on Android, VisionKit on + iOS) with edge detection and perspective correction, captures several pages + and combines them into one PDF (`src/scanner/scanToPdf.ts`). + +**Offline** + +- Responses are cached and persisted, so a previously opened screen still shows + its content without a connection; a banner appears while the device is + offline and the data refreshes as soon as it is back. + +--- + +## Getting started + +```bash +npm install +``` + +The scanner, the maps and the file viewer are native modules, so the app needs +a development build rather than Expo Go: + +```bash +npx expo run:ios # or: npx expo run:android +npm start # subsequent runs +``` + +With EAS instead of a local build: + +```bash +npx eas build --profile development --platform ios # or android +``` + +`npm run prebuild` regenerates the native projects (`expo prebuild --clean`); +`ios/` and `android/` are not kept in the repository because everything native +is declared by config plugins in `app.json`, and `npm install` applies +`patches/` through `patch-package`. + +When the phone is connected over USB, `npm run reverse` forwards the Metro and +backend ports to it. + +--- + +## Pointing the app at a backend + +Everything goes through `src/api/config.ts`: + +```ts +export const USE_MOCK = false; // true -> in-memory demo data, no server +``` + +- `USE_MOCK = true` uses `src/api/mock.ts`: every flow works offline on demo + data, and any credentials are accepted at login. +- `USE_MOCK = false` uses `src/api/real.ts`, mapped to the endpoints in + `openapi.yaml`. + +The address of the server comes from two places: `DEV_HOST` in the same file +for development builds, and `expo.extra.apiUrl` in `app.json` for release +builds. A release build must use `https`, because both platforms block plain +HTTP traffic outside development. + +The screens import `api` from `@/api` and never know which of the two is +active. + +### How documents map to the backend + +The screens work with a flat model: a property owns documents, each with a +category and an optional expiry date. The backend nests the same data one level +deeper, as page (slug) → section → document requirement → uploaded files. +`src/api/real.ts` holds that mapping. Reads flatten the structure for display; +writes stay faithful, because the upload screen has the user pick a real +requirement and sends its id. + +Two consequences are worth knowing: + +- A property's documents are gathered with one request per category page, since + the endpoint serves a single page at a time. The properties list does not pay + that cost — it uses the counters `homes.php` returns. +- There is no endpoint for a single file's metadata, so document screens read + from the list they were opened from. + +--- + +## Project structure + +``` +app/ expo-router routes + _layout.tsx providers, auth gate + (auth)/ login, register, verify, forgot, reset + (tabs)/ properties · search · expiring · profile + property/[id].tsx property detail + property/add.tsx new property + property/edit/[id].tsx edit property + property/location.tsx map picker for the address + documents/[propertyId].tsx documents of a property + document/[id].tsx document detail and viewer + document/edit/[id].tsx edit a document + profile/edit.tsx profile + profile/password.tsx change password + upload.tsx add a document: file, gallery or scan + +src/ + api/ config.ts (backend switch), http.ts, types.ts, mock.ts, real.ts + auth/ AuthContext, token in SecureStore + i18n/ en.ts, it.ts, locale detection and override + scanner/ scanToPdf.ts + theme/ design tokens (colours, spacing, radii, typography) + components/ ui.tsx, domain.tsx, forms, AppBar, PdfView, OfflineBanner + lib/ formatting, permissions, files, network, query client +``` + +--- + +## Scripts + +| | | +|---|---| +| `npm start` | Metro for a development build | +| `npm run ios` / `npm run android` | build and install on device or simulator | +| `npm run prebuild` | regenerate `ios/` and `android/` | +| `npm run typecheck` | TypeScript, no emit | +| `npm run lint` | ESLint | +| `npm run reverse` | forward ports 8081 and 8080 to a USB-connected Android device | + +--- + +## Notes + +- Design tokens live in `src/theme/tokens.ts` and are the single source for + colours, spacing, radii and typography. Typography currently maps to the + system font; loading Space Grotesk and Inter through `expo-font` is a change + in that one file. +- Sharing between owners, ownership transfer and reports exist in the backend + contract but are outside the scope of this app. diff --git a/app.json b/app.json new file mode 100644 index 0000000..b1a8949 --- /dev/null +++ b/app.json @@ -0,0 +1,105 @@ +{ + "expo": { + "name": "CasaDoc", + "slug": "casadoc", + "version": "1.0.0", + "orientation": "portrait", + "scheme": "casadoc", + "userInterfaceStyle": "light", + "newArchEnabled": true, + "ios": { + "supportsTablet": true, + "bundleIdentifier": "io.cesoft.casadoc", + "infoPlist": { + "NSCameraUsageDescription": "CasaDoc uses the camera to scan property documents into PDF.", + "NSPhotoLibraryUsageDescription": "CasaDoc needs access to your photos to attach images to documents.", + "NSLocationWhenInUseUsageDescription": "CasaDoc uses your location to place a property on the map.", + "ITSAppUsesNonExemptEncryption": false, + "NSAppTransportSecurity": { + "NSExceptionDomains": { + "192.168.88.82": { + "NSExceptionAllowsInsecureHTTPLoads": true, + "NSIncludesSubdomains": true + } + } + } + }, + "buildNumber": "2" + }, + "android": { + "package": "io.cesoft.casadoc", + "permissions": [ + "android.permission.ACCESS_COARSE_LOCATION", + "android.permission.ACCESS_FINE_LOCATION", + "android.permission.CAMERA" + ], + "config": { + "googleMaps": { + "apiKey": "AIzaSyADtQRKgCpJNnQCP8QvBeKDcm0TrTPpsGY" + } + }, + "versionCode": 2, + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#237687" + }, + "blockedPermissions": [ + "android.permission.RECORD_AUDIO", + "android.permission.SYSTEM_ALERT_WINDOW", + "android.permission.WRITE_EXTERNAL_STORAGE" + ] + }, + "plugins": [ + "expo-router", + "expo-secure-store", + [ + "expo-image-picker", + { + "photosPermission": "CasaDoc needs access to your photos to attach images to documents.", + "cameraPermission": "CasaDoc uses the camera to scan property documents into PDF." + } + ], + [ + "react-native-document-scanner-plugin", + { + "cameraPermission": "CasaDoc uses the camera to scan property documents into PDF." + } + ], + "expo-asset", + "expo-font", + [ + "expo-build-properties", + { + "android": { + "ndkVersion": "27.1.12297006", + "targetSdkVersion": 35, + "compileSdkVersion": 35 + } + } + ], + "expo-localization", + "@react-native-community/datetimepicker", + [ + "expo-location", + { + "locationWhenInUsePermission": "CasaDoc uses your location to place a property on the map." + } + ] + ], + "experiments": { + "typedRoutes": true + }, + "icon": "./assets/icon.png", + "splash": { + "image": "./assets/splash-icon.png", + "resizeMode": "contain", + "backgroundColor": "#237687" + }, + "web": { + "favicon": "./assets/favicon.png" + }, + "extra": { + "apiUrl": "https://casadoc.cesoft.io/public/userportal/api" + } + } +} diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx new file mode 100644 index 0000000..bde2d12 --- /dev/null +++ b/app/(auth)/_layout.tsx @@ -0,0 +1,10 @@ +import { Stack } from 'expo-router'; +import { colors } from '@/theme'; + +export default function AuthLayout() { + return ( + + ); +} diff --git a/app/(auth)/forgot.tsx b/app/(auth)/forgot.tsx new file mode 100644 index 0000000..54300b4 --- /dev/null +++ b/app/(auth)/forgot.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { useRouter } from 'expo-router'; +import { api } from '@/api'; +import { useT } from '@/i18n'; +import { AuthShell } from '@/components/AuthShell'; +import { Button, Field, TextField } from '@/components/ui'; +import { spacing } from '@/theme'; + +export default function ForgotPasswordScreen() { + const router = useRouter(); + const t = useT(); + + const [email, setEmail] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit() { + const mail = email.trim(); + if (!mail || !mail.includes('@')) return setError(t('register.badEmail')); + + setError(null); + setLoading(true); + try { + await api.auth.forgotPassword(mail); + } catch (e: any) { + setError(e?.message ?? t('common.tryAgain')); + setLoading(false); + return; + } + setLoading(false); + // The endpoint succeeds even for unknown addresses, and the screen must + // not reveal whether the account exists. + router.replace(`/reset?email=${encodeURIComponent(mail)}`); + } + + return ( + + + + + +