This document provides a comprehensive overview of the TradeMaster Transactions (TMT) application architecture, including the React structure, Redux state management, routing patterns, authentication flow, and key architectural decisions.
Technology Stack
React 18 Modern React with hooks and concurrent features
Redux Toolkit State management with Redux Persist
Firebase Authentication, Firestore, Cloud Functions, Storage
Material-UI 5 Component library and theming
Vite Fast build tool and dev server
React Router 6 Client-side routing
Core Dependencies
{
"dependencies" : {
"react" : "^18.3.1" ,
"react-dom" : "^18.3.1" ,
"@reduxjs/toolkit" : "1.8.3" ,
"redux-persist" : "^6.0.0" ,
"firebase" : "^9.5.0" ,
"@mui/material" : "5.10.16" ,
"react-router-dom" : "6.3.0" ,
"formik" : "2.2.9" ,
"yup" : "^0.32.11" ,
"axios" : "0.27.2" ,
"@casl/ability" : "^6.7.1"
}
}
Application Entry Point
The application bootstraps through a clear initialization chain:
import { Provider } from "react-redux" ;
import ReactDOM from "react-dom/client" ;
import { BrowserRouter } from "react-router-dom" ;
import { PersistGate } from 'redux-persist/integration/react' ;
import { AuthProvider } from "src/guards/firebase/FirebaseContext" ;
import store , { persistor } from "./store/Store" ;
import App from "./App" ;
ReactDOM . createRoot ( document . getElementById ( "root" )). render (
< Provider store = { store } >
< Suspense fallback = { < Spinner /> } >
< PersistGate loading = { null } persistor = { persistor } >
< BrowserRouter >
< ToastContainer />
< AuthProvider >
< App />
</ AuthProvider >
</ BrowserRouter >
</ PersistGate >
</ Suspense >
</ Provider >
);
Initialization Flow:
Redux Provider
Wraps the entire app with Redux store access
PersistGate
Rehydrates persisted state from localStorage before rendering
BrowserRouter
Enables client-side routing
AuthProvider
Initializes Firebase authentication listener
App Component
Renders the main application with routing and theme
Project Structure
src/
├── App.jsx # Root component with routing
├── main.jsx # Application entry point
├── components/
│ ├── apps/ # Feature-specific components
│ │ ├── events/ # Event management components
│ │ ├── Portals/ # Portal configuration
│ │ ├── contracts/ # Contract forms and displays
│ │ ├── bank-documents/ # Financial documents
│ │ └── ... # Other feature modules
│ ├── dashboards/ # Dashboard widgets
│ ├── forms/ # Reusable form elements
│ └── shared/ # Shared UI components
├── views/
│ ├── authentication/ # Login, register, forgot password
│ ├── dashboard/ # Dashboard pages
│ ├── events/ # Event management views
│ ├── Users/ # User management (staff, clients)
│ ├── Tickets/ # Ticket views and search
│ ├── offices/ # Box office management
│ └── ... # Other view pages
├── store/
│ ├── Store.js # Redux store configuration
│ ├── apps/ # Feature slices
│ │ ├── auth/ # Authentication slice
│ │ ├── events/ # Events slice
│ │ ├── tickets/ # Tickets slice
│ │ ├── Users/ # User management slices
│ │ └── ... # 26+ feature slices
│ ├── customizer/ # UI customization state
│ └── setup/ # Platform setup configuration
├── guards/
│ ├── firebase/ # Firebase configuration & exports
│ ├── authGuard/ # Route protection HOCs
│ └── contexts/ # CASL ability definitions
├── routes/
│ └── Router.js # Route configuration
├── layouts/
│ ├── full/ # Main layout with sidebar
│ └── blank/ # Minimal layout for auth
├── theme/ # MUI theme configuration
└── utils/ # Utility functions and hooks
Redux State Management
Store Configuration
The Redux store is configured with Redux Toolkit and Redux Persist:
import { configureStore } from '@reduxjs/toolkit' ;
import { persistReducer , persistStore } from 'redux-persist' ;
import storage from 'redux-persist/lib/storage' ;
const persistConfig = {
key: 'root' ,
storage ,
whitelist: [ 'auth' , 'customizer' , 'setup' ] // Only persist these slices
};
const rootReducer = combineReducers ({
customizer: CustomizerReducer ,
setup: SetupReducer ,
staff: StaffReducer ,
collaborators: CollaboratorsReducer ,
clients: ClientsReducer ,
customers: CustomerReducer ,
contracts: ContractReducer ,
addendums: AddendumReducer ,
tickets: TicketReducer ,
queryTickets: QueryTicketReducer ,
auth: authReducer ,
eventVenues: EventsVenuesReduces ,
events: EventsReduces ,
credentials: CredentialsReduces ,
Transactions: TransactionsReducer ,
offices: OfficesReducer ,
officeEvents: OfficeEventReducer ,
officeTransactions: OfficeTransactionReducer ,
paymentsMethod: PaymentsMethodsReducer ,
payouts: PayoutsReducer ,
custodyAccounts: CustodyAccountsReducer ,
orders: OrdersReducer ,
officeCollaborators: ActiveOfficeCollaboratorsReducer ,
contractsLegal: ContractsLegalReducer ,
ordersPayout: OrdersPayoutReducer ,
portals: PortalsReducer ,
documents: DocumentsReducer ,
marketing: MarketingReducer ,
});
const persistedReducer = persistReducer ( persistConfig , rootReducer );
export const store = configureStore ({
reducer: persistedReducer ,
middleware : ( getDefaultMiddleware ) =>
getDefaultMiddleware ({
serializableCheck: {
ignoredActions: [ FLUSH , REHYDRATE , PAUSE , PERSIST , PURGE , REGISTER ],
},
}),
});
export const persistor = persistStore ( store );
State Structure
The application manages 26+ Redux slices , each handling a specific domain:
Complete Redux State Tree
interface RootState {
// UI State (Persisted)
customizer : {
activeMode : 'light' | 'dark' ;
activeTheme : string ;
isCollapse : boolean ;
isMobileSidebar : boolean ;
isHorizontal : boolean ;
};
// Authentication (Persisted)
auth : {
isAuthenticated : boolean ;
user : {
uid : string ;
email : string ;
name : string ;
account_type : 'Administrador' | 'Cliente' | 'Coordinador' | 'Contador' | 'Soporte' ;
avatar : string | null ;
phone : string ;
address : object ;
} | null ;
};
// Platform Configuration (Persisted)
setup : {
Setup : {
event_type : { data : string [] };
// Other platform settings
};
};
// Feature States (Not Persisted)
events : {
Events : Event [];
ActiveEvents : Event [];
ClientEvents : Event [];
EventsSearch : string ;
};
tickets : {
Tickets : Ticket [];
// Ticket state
};
// ... 20+ other feature slices
}
Example Slice: Events
src/store/apps/events/EventsSlice.js:1-43
import { createSlice } from '@reduxjs/toolkit' ;
import { Firestore } from '../../../guards/firebase/Firebase' ;
import moment from 'moment' ;
const initialState = {
Events: [],
ActiveEvents: [],
ClientEvents: [],
EventsSearch: '' ,
error: ''
};
export const EventsSlice = createSlice ({
name: 'Events' ,
initialState ,
reducers: {
hasError ( state , action ) {
state . error = action . payload ;
},
getEvents : ( state , action ) => {
state . Events = action . payload ;
},
getActiveEvents : ( state , action ) => {
state . ActiveEvents = action . payload ;
},
getClientEvents : ( state , action ) => {
state . ClientEvents = action . payload ;
},
SearchEvents : ( state , action ) => {
state . EventsSearch = action . payload ;
}
},
});
export const {
hasError ,
getEvents ,
getActiveEvents ,
getClientEvents ,
SearchEvents ,
} = EventsSlice . actions ;
Async Thunk for Data Fetching:
src/store/apps/events/EventsSlice.js:45-81
export const fetchEvents = ( id ) => async ( dispatch ) => {
try {
const querySnapshot = id
? await Firestore . collection ( 'events' ). where ( "venue_id" , "==" , id ). get ()
: await Firestore . collection ( 'events' ). get ();
const EventsArray = querySnapshot . docs . map (( doc ) => {
const data = doc . data ();
const convertTimestamp = ( timestamp ) => {
if ( timestamp && timestamp . seconds ) {
const date = new Date ( timestamp . seconds * 1000 + timestamp . nanoseconds / 1000000 );
return moment ( date ). format ( 'DD/MM/YYYY - hh:mm:ss a' );
}
return null ;
};
const date = {
created: convertTimestamp ( data . date ?. created ),
updated: convertTimestamp ( data . date ?. updated ),
};
const date_end = convertTimestamp ( data . date_end );
const date_start = convertTimestamp ( data . date_start );
return {
id: doc . id ,
... data ,
date ,
date_end ,
date_start
};
});
dispatch ( getEvents ( EventsArray ));
} catch ( error ) {
dispatch ( hasError ( error ));
}
};
All slices follow a similar pattern: initial state, synchronous reducers, and async thunks for Firestore operations.
Routing Architecture
React Router 6 provides declarative routing with nested routes and route guards.
Route Configuration
src/routes/Router.js:116-246
import { Navigate } from 'react-router-dom' ;
import AuthGuard from 'src/guards/authGuard/AuthGuard' ;
import GuestGuard from 'src/guards/authGuard/GuestGaurd' ;
import PermissionGuard from '../guards/authGuard/PermissionGuard' ;
const Router = [
{
path: '/' ,
element : (
< AuthGuard >
< FullLayout />
</ AuthGuard >
),
children: [
{ path: '/' , element: < Navigate to = "/dashboards/modern" /> },
{ path: '/dashboards/modern' , element: < ModernDash /> },
// Events with permission guards
{
path: '/eventos' ,
element : (
< PermissionGuard action = "view" subject = "ViewEvents" >
< EventsList />
</ PermissionGuard >
)
},
{
path: '/eventos-crear' ,
element : (
< PermissionGuard action = "view" subject = "ViewEventsCreate" >
< NewEvents />
</ PermissionGuard >
)
},
// Tickets
{
path: '/tickets' ,
element : (
< PermissionGuard action = "view" subject = "ViewTickets" >
< TicketTable />
</ PermissionGuard >
)
},
// ... 50+ more routes
],
},
{
path: '/auth' ,
element : (
< GuestGuard >
< BlankLayout />
</ GuestGuard >
),
children: [
{ path: '/auth/login' , element: < Login /> },
{ path: '/auth/forgot-password' , element: < ForgotPassword /> },
{ path: '/auth/register' , element: < Register /> },
],
},
{
path: '/auth' ,
element: < BlankLayout /> ,
children: [
{ path: '404' , element: < Error /> },
{ path: 'permissions' , element: < PermissionsValidator /> },
],
},
];
export default Router ;
Route Guards
1. AuthGuard - Protects authenticated routes:
src/guards/authGuard/AuthGuard.js:5-19
import { useNavigate } from 'react-router-dom' ;
import useAuth from './UseAuth' ;
import { useEffect } from 'react' ;
const AuthGuard = ({ children }) => {
const { isAuthenticated } = useAuth ();
const navigate = useNavigate ();
useEffect (() => {
if ( ! isAuthenticated ) {
navigate ( '/auth/login' , { replace: true });
}
}, [ isAuthenticated , navigate ]);
return children ;
};
2. PermissionGuard - Enforces CASL ability checks:
src/guards/authGuard/PermissionGuard.js:1-18
import React from "react" ;
import { useNavigate } from 'react-router-dom' ;
import { AbilityContext } from "../contexts/AbilityContext" ;
const PermissionGuard = ({ children , action , subject }) => {
const ability = React . useContext ( AbilityContext );
const navigate = useNavigate ();
React . useEffect (() => {
if ( ! ability . can ( action , subject )) {
navigate ( "/auth/permissions" , { replace: true });
}
}, [ navigate ]);
return children ;
};
Always wrap protected routes with PermissionGuard to prevent unauthorized access to features.
Authentication & Authorization
Firebase Authentication
Authentication is managed through Firebase with custom authorization logic:
src/guards/firebase/Firebase.js:10-17
import firebase from 'firebase/compat/app' ;
import 'firebase/compat/auth' ;
import 'firebase/compat/firestore' ;
import 'firebase/compat/storage' ;
import { getFunctions , httpsCallable } from 'firebase/functions' ;
const firebaseConfig = {
apiKey: "-" ,
authDomain: "-" ,
projectId: "-" ,
storageBucket: "-" ,
messagingSenderId: "-" ,
appId: "-"
};
const app = firebase . initializeApp ( firebaseConfig );
const Firestore = firebase . firestore ();
const Storage = getStorage ( app );
const functions = getFunctions ( app );
Auth Context and State Management
src/guards/firebase/FirebaseContext.js:22-72
export const AuthProvider = ({ children }) => {
const dispatch = useDispatch ();
const auth = useSelector (( state ) => state . auth );
useEffect (() => {
const unsubscribe = firebase . auth (). onAuthStateChanged ( async ( user ) => {
if ( user ) {
// Fetch user data from Firestore
const querySnapshot = await Firestore
. collection ( 'u_clients' )
. doc ( user . uid )
. get ();
// Check if user is active
if ( querySnapshot . data ()?. status !== true ) {
logout ();
} else {
// Get user's IP address
const queryIP = await fetch ( 'https://ipapi.co/json/' );
const data = await queryIP . json ();
// Update last access timestamp
await Firestore . collection ( 'u_clients' ). doc ( user . uid ). update ({
"date.last_access" : firebase . firestore . FieldValue . serverTimestamp (),
last_IP: data . ip
});
// Fetch platform setup configuration
dispatch ( fetchSetup ());
// Update auth state
dispatch ( authStateChanged ({
isAuthenticated: true ,
user: {
uid: user . uid ,
avatar: querySnapshot . data ()?. image ?? null ,
account_type: querySnapshot . data ()?. account_type ,
phone: querySnapshot . data ()?. phone ?? "" ,
address: querySnapshot . data ()?. address ?? {},
email: user . email ,
country_calling_code: data . country_calling_code ,
name: querySnapshot . data ()?. name ,
... querySnapshot . data ()
},
}));
}
} else {
dispatch ( authStateChanged ({
isAuthenticated: false ,
user: null ,
}));
}
});
return () => unsubscribe ();
}, [ dispatch ]);
// Auth methods
const signin = ( email , password ) =>
firebase . auth (). signInWithEmailAndPassword ( email , password );
const logout = () => firebase . auth (). signOut ();
return (
< AuthContext.Provider value = { { ... auth , signin , logout } } >
{ children }
</ AuthContext.Provider >
);
};
Permission System (CASL)
Permissions are defined using CASL (Code Access Security Layer):
src/guards/contexts/DefineAbilities.js:1-237
import { AbilityBuilder , Ability } from '@casl/ability' ;
export function defineAbilitiesFor ( user ) {
const { can , cannot , build } = new AbilityBuilder ( Ability );
if ( ! user ) return build ();
const commonPermissions = {
'Coordinador' : {
can: [
[ 'create' , 'usersClients' ],
[ 'edit' , 'usersClients' ],
[ 'create' , 'events' ],
[ 'view' , 'ViewEvents' ],
[ 'view' , 'ViewEventsCreate' ],
[ 'view' , 'ViewTickets' ],
// ... more permissions
],
cannot: [
[ 'create' , 'usersStaff' ],
[ 'view' , 'ViewPayouts' ],
[ 'change' , 'eventsStatus' ],
// ... restricted actions
]
},
'Contador' : {
can: [
[ 'view' , 'ViewClients' ],
[ 'view' , 'ViewContracts' ],
[ 'view' , 'ViewEvents' ],
// ... accountant permissions
],
cannot: [
[ 'create' , 'events' ],
[ 'view' , 'ViewEventsCreate' ],
// ... restricted actions
]
},
'Soporte' : {
can: [
[ 'view' , 'ViewEvents' ],
[ 'view' , 'ViewTicketsDetail' ],
// ... support permissions (mostly read-only)
],
cannot: [
[ 'create' , 'events' ],
[ 'edit' , 'events' ],
// ... restricted actions
]
}
};
if ( user . account_type === 'Administrador' || user . account_type === 'Cliente' ) {
can ( 'manage' , 'all' ); // Full access
} else if ( commonPermissions [ user . account_type ]) {
const permissions = commonPermissions [ user . account_type ];
permissions . can ?. forEach (([ action , subject ]) => can ( action , subject ));
permissions . cannot ?. forEach (([ action , subject ]) => cannot ( action , subject ));
}
return build ();
};
Usage in App Component:
import { AbilityContext } from './guards/contexts/AbilityContext' ;
import { defineAbilitiesFor } from './guards/contexts/DefineAbilities' ;
function App () {
const { user } = useAuth ();
const ability = defineAbilitiesFor ( user );
const routing = useRoutes ( Router );
const theme = ThemeSettings ();
React . useEffect (() => {
ability . update ( defineAbilitiesFor ( user ). rules );
}, [ user ]);
return (
< ThemeProvider theme = { theme } >
< AbilityContext.Provider value = { ability } >
< RTL direction = { customizer . activeDir } >
< CssBaseline />
< ScrollToTop > { routing } </ ScrollToTop >
</ RTL >
</ AbilityContext.Provider >
</ ThemeProvider >
);
}
CASL abilities are updated dynamically when the user object changes, ensuring permissions are always in sync.
Firebase Integration
Cloud Functions
The application leverages Firebase Cloud Functions for serverless operations:
src/guards/firebase/Firebase.js:54-85
const functions = getFunctions ( app );
// User Management
export const validateUserPlatform = httpsCallable ( functions , 'validate_user_platform' );
export const create_staff = httpsCallable ( functions , 'create_staff' );
export const create_client = httpsCallable ( functions , 'create_client' );
export const create_collaborator = httpsCallable ( functions , 'create_collaborator' );
// Ticket Operations
export const tickets_generate = httpsCallable ( functions , 'tickets_generate' );
export const tickets_list_event = httpsCallable ( functions , 'tickets_list_event' );
export const tickets_individual = httpsCallable ( functions , 'tickets_individual' );
// Office Management
export const office_pin_set = httpsCallable ( functions , 'office_pin_set' );
// Analytics & Reporting
export const sold_by_office = httpsCallable ( functions , 'sold_by_office' );
export const number_tickets_vs_sold = httpsCallable ( functions , 'number_tickets_vs_sold' );
export const money_distribution = httpsCallable ( functions , 'money_distribution' );
export const offices_vs_offices_active = httpsCallable ( functions , 'offices_vs_offices_active' );
export const general_platform_data = httpsCallable ( functions , 'general_platform_data' );
export const types_of_collaborators = httpsCallable ( functions , 'types_of_collaborators' );
export const sold_by_usbs = httpsCallable ( functions , 'sold_by_usbs' );
export const total_sales = httpsCallable ( functions , 'total_sales' );
// Offline Tickets
export const offline_tickets_list_event_sales = httpsCallable ( functions , 'offline_tickets_list_event_sales' );
export const offline_office_tickets_list_sales = httpsCallable ( functions , 'offline_office_tickets_list_sales' );
export const offline_office_tickets = httpsCallable ( functions , 'offline_office_tickets' );
export const offline_office_tickets_unassign = httpsCallable ( functions , 'offline_office_tickets_unassign' );
// Reconciliation
export const conciliation_data_banco = httpsCallable ( functions , 'conciliation_data_banco' );
export const conciliation_data_cia = httpsCallable ( functions , 'conciliation_data_cia' );
export const conciliation_data = httpsCallable ( functions , 'conciliation_data' );
export const noconciliation_data_b = httpsCallable ( functions , 'noconciliation_data_b' );
export const noconciliation_data_cia = httpsCallable ( functions , 'noconciliation_data_cia' );
export const conciliation_load_bank_manual = httpsCallable ( functions , 'conciliation_load_bank_manual' );
export const conciliation_load_cia_manual = httpsCallable ( functions , 'conciliation_load_cia_manual' );
// Notifications
export const send_email = httpsCallable ( functions , 'send_email' );
Firestore Collections
Key Firestore collections used throughout the application:
Collections Schema
Firestore Query Example
// User Management
u_clients // Client accounts (authenticated users)
u_staff // Staff members
u_collaborators // Event collaborators
u_customers // Ticket purchasers
// Event Management
events // Event records
event_venues // Venue information
credentials // Event credentials/access passes
// Sales & Transactions
tickets // Individual tickets
orders // Customer orders
transactions // Payment transactions
offices // Box office locations
portals // Online sales portals
// Financial
contracts // Client contracts
contracts_legal // Legal contracts
payouts // Payment distributions
custody_accounts // Custody account records
payment_methods // Payment method configurations
orders_payout // Payout orders
// Documents & Reconciliation
documents // Financial documents
bank_documents // Bank transaction records
company_documents // Company transaction records
// Marketing
marketing // Marketing campaigns
Firebase Storage
File uploads are handled through Firebase Storage:
import { ref , uploadBytes , getDownloadURL } from "firebase/storage" ;
import { Storage } from 'src/guards/firebase/Firebase' ;
// Upload event image
const uploadEventImage = async ( file , eventId ) => {
const storageRef = ref ( Storage , `events/ ${ eventId } /image.jpg` );
await uploadBytes ( storageRef , file );
const downloadURL = await getDownloadURL ( storageRef );
return downloadURL ;
};
Component Architecture
Layout System
The application uses a two-layout system:
1. Full Layout - Main authenticated layout with sidebar:
src/layouts/full/FullLayout.js:26-78
const FullLayout = () => {
const customizer = useSelector (( state ) => state . customizer );
const theme = useTheme ();
return (
< MainWrapper >
{ /* Sidebar (vertical or hidden for horizontal) */ }
{ customizer . isHorizontal ? '' : < Sidebar /> }
< PageWrapper >
{ /* Header */ }
{ customizer . isHorizontal ? < HorizontalHeader /> : < Header /> }
{ /* Navigation (horizontal mode) */ }
{ customizer . isHorizontal ? < Navigation /> : '' }
{ /* Page Content */ }
< Container
sx = { {
maxWidth: customizer . isLayout === 'boxed' ? 'lg' : '100%!important' ,
} }
>
< Box sx = { { minHeight: 'calc(100vh - 170px)' } } >
< Outlet /> { /* Nested routes render here */ }
</ Box >
</ Container >
</ PageWrapper >
</ MainWrapper >
);
};
2. Blank Layout - Minimal layout for authentication pages:
const BlankLayout = () => {
return (
< Box sx = { { minHeight: '100vh' , display: 'flex' } } >
< Outlet />
</ Box >
);
};
View → Component Pattern
The application follows a clear separation between views (page containers) and components (reusable logic):
View Layer (src/views/events/new-events/NewEvents.js):
src/views/events/new-events/NewEvents.js:20-39
const NewEventVenues = () => {
return (
< PageContainer title = "Crear Evento" description = "this is Crear Evento page" >
< Breadcrumb title = "Crear Evento" items = { BCrumb } />
< ParentCard title = 'Crear Evento' >
< Grid container spacing = { 3 } >
< Grid item xs = { 12 } >
< NewEventVenueForm />
</ Grid >
</ Grid >
</ ParentCard >
</ PageContainer >
);
};
Component Layer (src/components/apps/events/NewEventForm.js):
const NewEventForm = () => {
const [ clients , setClients ] = useState ([]);
const [ contracts , setContracts ] = useState ([]);
const [ eventsVenues , setEventsVenues ] = useState ([]);
const formik = useFormik ({
initialValues: { /* ... */ },
validationSchema: EventSchema ,
onSubmit : async ( values ) => {
// Handle form submission
await Firestore . collection ( 'events' ). add ( values );
}
});
return (
< Form >
{ /* Form fields */ }
</ Form >
);
};
This pattern keeps views focused on layout and navigation, while components handle business logic and data operations.
Forms are built using Formik with Yup validation:
import { useFormik , FormikProvider } from 'formik' ;
import * as Yup from 'yup' ;
const ValidationSchema = Yup . object (). shape ({
email: Yup . string ()
. email ( 'El correo electrónico es invalido' )
. required ( 'Correo electrónico es requerido' ),
password: Yup . string ()
. min ( 6 , 'La contraseña debe tener al menos 6 caracteres' )
. required ( 'Se requiere contraseña' ),
});
const formik = useFormik ({
initialValues: {
email: '' ,
password: '' ,
},
validationSchema: ValidationSchema ,
onSubmit : async ( values ) => {
// Handle submission
},
});
return (
< FormikProvider value = { formik } >
< Form onSubmit = { formik . handleSubmit } >
< CustomTextField
id = "email"
{ ... formik . getFieldProps ( 'email' ) }
error = { Boolean ( formik . touched . email && formik . errors . email ) }
helperText = { formik . touched . email && formik . errors . email }
/>
</ Form >
</ FormikProvider >
);
Theme Customization
Material-UI theming is managed through Redux:
src/store/customizer/CustomizerSlice.js:4-19
const initialState = {
activeDir: 'ltr' ,
activeMode: 'light' ,
activeTheme: 'BLUE_THEME' ,
SidebarWidth: 270 ,
MiniSidebarWidth: 87 ,
TopbarHeight: 70 ,
isLayout: 'boxed' ,
isCollapse: false ,
isSidebarHover: false ,
isMobileSidebar: false ,
isHorizontal: false ,
isLanguage: 'en' ,
isCardShadow: true ,
borderRadius: 7 ,
};
Code Splitting
Routes use React.lazy for code splitting:
src/routes/Router.js:1-14
import React , { lazy } from 'react' ;
import Loadable from '../layouts/full/shared/loadable/Loadable' ;
const ModernDash = Loadable ( lazy (() => import ( '../views/dashboard/Modern' )));
const EventsList = Loadable ( lazy (() => import ( '../views/events/list-events/EventsList' )));
const NewEvents = Loadable ( lazy (() => import ( '../views/events/new-events/NewEvents' )));
// ... all routes are lazy loaded
Redux Persist
Only essential state is persisted:
const persistConfig = {
key: 'root' ,
storage ,
whitelist: [ 'auth' , 'customizer' , 'setup' ] // Selective persistence
};
Firestore Queries
Queries use specific indexes and filters:
// Good: Filtered query
const activeEvents = await Firestore
. collection ( 'events' )
. where ( 'status' , '==' , 'Activo' )
. where ( 'client_id' , '==' , userId )
. get ();
// Avoid: Fetching all documents
// const allEvents = await Firestore.collection('events').get();
Error Handling
Toast Notifications
Errors are displayed using react-toastify:
import { toast } from 'react-toastify' ;
try {
await createEvent ( data );
toast . success ( 'Evento creado exitosamente' );
} catch ( error ) {
toast . error ( 'Error al crear evento: ' + error . message , {
position: "bottom-center" ,
autoClose: 7000 ,
hideProgressBar: false ,
closeOnClick: true ,
pauseOnHover: true ,
draggable: true ,
theme: "light" ,
});
}
Authentication Errors
src/views/authentication/authForms/AuthLogin.js:98-111
const getErrorMessage = ( errorCode ) => {
switch ( errorCode ) {
case "auth/invalid-email" :
return "La dirección de correo electrónico no es válida." ;
case "auth/invalid-login-credentials" :
return "La contraseña es incorrecta" ;
case "auth/invalid-credential" :
return "La contraseña o el correo es incorrecto." ;
case "auth/too-many-requests" :
return "Has hecho muchas solicitudes, espera un momento." ;
default :
return "Ocurrió un error al intentar iniciar sesión." ;
}
};
Build & Deployment
Vite Configuration
import { defineConfig } from 'vite' ;
import react from '@vitejs/plugin-react' ;
export default defineConfig ({
plugins: [ react ()] ,
resolve: {
alias: {
'src' : '/src'
}
} ,
server: {
port: 3000
} ,
build: {
outDir: 'dist' ,
sourcemap: true
}
}) ;
NPM Scripts
{
"scripts" : {
"dev" : "vite" ,
"build" : "vite build" ,
"lint" : "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0" ,
"preview" : "vite preview"
}
}
Key Architectural Decisions
Firebase as Backend
Using Firebase eliminates the need for custom backend infrastructure. Firestore provides real-time data sync, Cloud Functions handle serverless logic, and Authentication manages user sessions.
Redux with Persistence
Redux Toolkit simplifies state management, while Redux Persist maintains authentication and UI preferences across sessions. Only critical state is persisted to optimize performance.
CASL for Permissions
CASL provides declarative, role-based access control that’s easy to maintain and audit. Permissions are defined centrally and enforced at both route and component levels.
Material-UI Components
MUI provides a consistent, accessible design system. Custom theme wrappers maintain brand consistency while leveraging MUI’s extensive component library.
Formik + Yup Validation
Formik handles complex form state, while Yup provides schema-based validation. This combination reduces boilerplate and ensures consistent validation across the app.
Code Splitting by Route
Lazy loading routes reduces initial bundle size. Each major feature loads on-demand, improving first contentful paint and time-to-interactive metrics.
Development Workflow
Start Dev Server npm run dev
# Vite dev server on http://localhost:3000
Build for Production npm run build
# Output to /dist directory
Lint Code npm run lint
# ESLint checks with React rules
Preview Build npm run preview
# Test production build locally
Next Steps
Quickstart Guide Learn how to create your first event
API Reference Explore Firebase Cloud Functions API
State Management Deep dive into Redux slices
Permission System Configure CASL abilities for custom roles