Mobile Client
Stack
| Technology | Purpose |
|---|---|
| Expo SDK | Managed workflow, build tooling |
| React Native | Cross-platform UI framework |
| Expo Router | File-based navigation |
| Expo SecureStore | Secure token storage |
| NativeWind | Tailwind CSS for React Native |
| TypeScript | Type-safe development |
Running the mobile app
# From the repo root
pnpm --filter mobile dev
# Or from apps/mobile directly
npx expo start -c -w
Press a to open in Android emulator, or scan the QR code with Expo Go.
File-based routing (Expo Router)
Navigation is defined by the file system under app/:
app/
├── _layout.tsx # Root layout — auth guard
├── login.tsx # Login screen
├── modal.tsx # Modal screen
├── auth/ # Auth callback (deep link handler)
└── (tabs)/
├── _layout.tsx # Tab bar definition
├── index.tsx # Dashboard
├── equipment.tsx # Equipment catalog
├── ledger.tsx # Requisition ledger
├── projects.tsx # Project list
├── user.tsx # User profile
├── admin.tsx # Admin panel (technician)
├── statistics.tsx # Statistics
└── users.tsx # User management
The root _layout.tsx wraps everything in the AuthProvider and implements the auth guard — unauthenticated users are redirected to /login.
Auth context
context/AuthContext.tsx
Provides AuthContext to the entire app:
interface AuthContextType {
token: string | null;
login: (token: string) => Promise<void>;
logout: () => Promise<void>;
}
On app startup, the context checks SecureStore for a stored token and hydrates the auth state.
login(token)— writes token to SecureStore and updates state.logout()— deletes token from SecureStore and clears state.
Secure token storage
JWT tokens are stored using expo-secure-store, which uses:
- Android: Android Keystore
- iOS: iOS Keychain
This prevents the token from being readable by other apps.
API wrapper
lib/
The API client reads the API base URL from app constants and attaches the JWT token from SecureStore as a Bearer token in the Authorization header:
const response = await fetch(`${API_BASE_URL}/api/projects`, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
Deep link authentication
The app scheme detimakerlab is registered in app.json. After SSO:
- The API redirects to
detimakerlab://auth?token=<JWT>. - The OS intercepts the deep link.
- The
app/auth/screen captures thetokenparameter. login(token)is called to store it.- The user is navigated to the main tab bar.
Environment variables
Set in apps/mobile/.env:
| Variable | Description |
|---|---|
EXPO_PUBLIC_API_URL | Backend API base URL |
Adding a new screen
- Create a file in
app/(tabs)/for a new tab, or inapp/for a full-screen route. - Implement the screen component.
- If it requires auth, the root
_layout.tsxauth guard already covers it. - If it is role-restricted, add a role check inside the screen using
AuthContext. - Update the tab bar in
app/(tabs)/_layout.tsxif adding a visible tab.