System Overview
The Incidents App is built as a monorepo containing two distinct but interconnected applications:
Mobile App React Native app built with Expo, supporting iOS, Android, and Web platforms
Web Dashboard Next.js admin dashboard with advanced analytics and management features
Both applications share a common backend powered by Supabase , providing authentication, real-time database, and storage capabilities.
Monorepo Structure
incidents-app/
├── mobile/ # React Native + Expo mobile application
│ ├── app/ # File-based routing (Expo Router)
│ │ ├── (admin)/ # Admin-only routes
│ │ ├── (empleado)/ # Employee routes
│ │ ├── (guest)/ # Guest user routes
│ │ ├── (guestScan)/ # QR scanning for guests
│ │ └── (auth)/ # Authentication flows
│ ├── assets/ # Images, fonts, and static files
│ ├── components/ # Reusable UI components
│ ├── hooks/ # Custom React hooks
│ └── src/
│ ├── services/ # API and service layer
│ │ └── supabase.ts
│ └── context/ # React context providers
│
└── web/ # Next.js web dashboard
├── app/ # App Router structure
│ ├── (dashboard)/ # Protected dashboard routes
│ ├── globals.css # Global styles
│ ├── layout.tsx # Root layout
│ └── page.tsx # Login page
├── components/ # React components
├── lib/ # Utility functions and clients
│ └── supabase.ts # Supabase browser client
├── hooks/ # Custom hooks
├── middleware.ts # Next.js middleware for auth
└── public/ # Static assets
The monorepo structure allows shared configuration and dependencies while maintaining separation between mobile and web codebases.
Mobile App Architecture
Technology Stack
The mobile app leverages modern React Native technologies:
{
"dependencies" : {
"expo" : "~54.0.32" ,
"expo-router" : "~6.0.22" ,
"react-native" : "0.81.5" ,
"@supabase/supabase-js" : "^2.91.0" ,
"expo-secure-store" : "~15.0.8" ,
"react-native-reanimated" : "~4.1.1" ,
"lucide-react-native" : "^0.563.0"
}
}
File-Based Routing
The mobile app uses Expo Router for file-based navigation with route groups:
app/_layout.tsx
app/index.tsx
import { Stack } from "expo-router" ;
import { useFonts } from "expo-font" ;
import {
Poppins_400Regular ,
Poppins_500Medium ,
Poppins_600SemiBold ,
Poppins_700Bold ,
} from "@expo-google-fonts/poppins" ;
export default function RootLayout () {
const [ loaded ] = useFonts ({
DtmF: require ( "../assets/fonts/DebiUsarEsteFontRegular.ttf" ),
PoppinsRegular: Poppins_400Regular ,
PoppinsMedium: Poppins_500Medium ,
PoppinsSemiBold: Poppins_600SemiBold ,
PoppinsBold: Poppins_700Bold ,
});
if ( ! loaded ) return null ;
return < Stack screenOptions ={{ headerShown : false }} />;
}
Route Groups
The app uses parentheses () to create route groups without affecting the URL structure:
(admin)/ - Admin dashboard, user management, session creation
(empleado)/ - Employee incident management
(guest)/ - Guest incident tracking
(guestScan)/ - QR code scanning for quick incident reporting
(auth)/ - Login and authentication flows
Route groups in Expo Router allow logical organization without impacting the navigation hierarchy.
Supabase Integration (Mobile)
The mobile app uses Expo Secure Store for persistent authentication:
mobile/src/services/supabase.ts
import { createClient } from '@supabase/supabase-js'
import * as SecureStore from 'expo-secure-store'
const supabaseUrl = process . env . EXPO_PUBLIC_SUPABASE_URL as string
const supabaseAnonKey = process . env . EXPO_PUBLIC_SUPABASE_ANON_KEY as string
const ExpoSecureStoreAdapter = {
getItem: SecureStore . getItemAsync ,
setItem: SecureStore . setItemAsync ,
removeItem: SecureStore . deleteItemAsync
}
export const supabase = createClient (
supabaseUrl ,
supabaseAnonKey ,
{
auth: {
storage: ExpoSecureStoreAdapter ,
persistSession: true ,
autoRefreshToken: true ,
detectSessionInUrl: false
}
}
)
Key Features:
Secure token storage using device keychain/keystore
Automatic session refresh
Persistent authentication across app restarts
No session detection from URLs (mobile-specific)
Web Dashboard Architecture
Technology Stack
The web dashboard uses Next.js 16 with the App Router:
{
"dependencies" : {
"next" : "16.1.6" ,
"react" : "19.2.3" ,
"@supabase/ssr" : "^0.8.0" ,
"@supabase/supabase-js" : "^2.95.3" ,
"@tanstack/react-table" : "^8.21.3" ,
"recharts" : "2.15.4" ,
"lucide-react" : "^0.575.0" ,
"next-themes" : "^0.4.6" ,
"tailwindcss" : "^4"
}
}
App Router Structure
import type { Metadata } from "next" ;
import "./globals.css" ;
export const metadata : Metadata = {
title: "Amanera - Incidents Dashboard" ,
description: "Admin dashboard for incident management" ,
};
export default function RootLayout ({
children ,
} : Readonly <{
children : React . ReactNode ;
}>) {
return (
< html lang = "en" >
< body >{ children } </ body >
</ html >
);
}
Supabase Integration (Web)
The web app uses Supabase SSR for server-side authentication:
import { createBrowserClient } from "@supabase/ssr" ;
const supabaseUrl = process . env . NEXT_PUBLIC_SUPABASE_URL ;
const supabaseAnonKey = process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ;
if ( ! supabaseUrl || ! supabaseAnonKey ) {
throw new Error ( "Missing Supabase environment variables" );
}
export const supabase = createBrowserClient ( supabaseUrl , supabaseAnonKey );
The web client throws an error if environment variables are missing, ensuring configuration issues are caught early.
Middleware for Authentication
Next.js middleware protects dashboard routes:
import { NextRequest , NextResponse } from "next/server" ;
/**
* Lightweight middleware that checks if a Supabase auth cookie is present
* before allowing access to /dashboard routes.
* Full role validation happens server-side in the dashboard page itself.
*/
export function middleware ( request : NextRequest ) {
const { pathname } = request . nextUrl ;
if ( pathname . startsWith ( "/dashboard" )) {
// Supabase persists the session in a cookie named "sb-<project>-auth-token"
const hasSession = Array . from ( request . cookies . getAll ()). some (
( c ) => c . name . startsWith ( "sb-" ) && c . name . endsWith ( "-auth-token" )
);
if ( ! hasSession ) {
return NextResponse . redirect ( new URL ( "/" , request . url ));
}
}
return NextResponse . next ();
}
export const config = {
matcher: [ "/dashboard/:path*" ],
};
Two-Layer Security:
Middleware : Fast cookie-based check (edge runtime)
Page Level : Full role validation with database queries (server component)
Authentication Flow
The system implements a unified authentication strategy across both platforms:
Initial Check
On app startup, check for existing session:
Mobile: Query Secure Store
Web: Check HTTP-only cookies
Session Validation
If session exists, validate with Supabase and fetch user profile: const { data } = await supabase . auth . getSession ();
const { data : profile } = await supabase
. from ( "profiles" )
. select ( "role" )
. eq ( "id" , data . session . user . id )
. single ();
Role-Based Routing
Route user to appropriate interface based on role:
admin : Full access to admin features
empleado : Employee incident management
guest : Limited guest access via QR code
Authorization Guard
For protected actions, verify role server-side before executing: const { data : profile , error : profileError } = await supabase
. from ( "profiles" )
. select ( "role" )
. eq ( "id" , data . user . id )
. single ();
if ( profileError || profile ?. role !== "admin" ) {
await supabase . auth . signOut ();
throw new Error ( "Acceso denegado. Solo administradores pueden ingresar." );
}
Data Flow Diagram
Key Configuration Files
Mobile Configuration
{
"expo" : {
"name" : "incidents-app" ,
"slug" : "incidents-app" ,
"version" : "1.0.0" ,
"scheme" : "fluxomobile" ,
"newArchEnabled" : true ,
"plugins" : [
"expo-router" ,
"react-native-bottom-tabs" ,
"expo-secure-store" ,
"expo-barcode-scanner"
],
"experiments" : {
"typedRoutes" : true ,
"reactCompiler" : true
}
}
}
Notable Features:
React Native New Architecture enabled
Typed routes for type-safe navigation
React Compiler for performance optimization
Web Configuration
import type { NextConfig } from "next" ;
const nextConfig : NextConfig = {
// Configuration options
};
export default nextConfig ;
State Management
Both applications use minimal state management approaches:
React Context : For global auth state
Local State : Component-level with useState and useReducer
Server State : Direct Supabase queries (no caching layer needed due to real-time subscriptions)
The architecture favors simplicity and leverages Supabase’s real-time features instead of complex state management libraries.
Styling Approaches
Mobile
React Native StyleSheet
Inline styles
Custom theme system
Expo Linear Gradient
Web
Tailwind CSS v4
CSS Modules
shadcn/ui components
Dark mode support
Mobile Optimizations
Reanimated : Smooth 60fps animations running on UI thread
FlashList : High-performance list rendering
Image Optimization : Expo Image for lazy loading and caching
Code Splitting : Route-based with Expo Router
Web Optimizations
Server Components : Default in Next.js App Router
Streaming : Progressive page rendering
Static Generation : Where applicable
Edge Middleware : Fast authentication checks
Security Architecture
Security Layers:
Environment Variables : Never expose secrets client-side
Row Level Security : Supabase RLS policies on all tables
Middleware Guards : Pre-render authentication checks
Server Validation : Never trust client data
Secure Storage : Keychain/Keystore for mobile, HTTP-only cookies for web
Deployment Architecture
The recommended deployment strategy:
Mobile : Expo Application Services (EAS) for build and distribution
Web : Vercel or similar Next.js-optimized platform
Backend : Supabase hosted or self-hosted instance
Assets : Supabase Storage or CDN
Next Steps
Now that you understand the architecture:
Set up your development environment with the Quick Start
Configure Supabase database tables and RLS policies
Customize the UI components for your brand
Deploy to production platforms
Review the Expo Router and Next.js App Router documentation for deeper understanding of the routing systems.