Overview
The Auth Dashboard follows a feature-based architecture with clear separation of concerns. The application is built with React, TypeScript, and modern tooling to ensure maintainability and scalability.
Project Structure
The codebase is organized into logical domains, making it easy to locate and modify functionality:
src/
├── app/ # Application-level routing and guards
│ ├── router.tsx # Main router configuration
│ ├── ProtectedRoute.tsx # Authentication guard for private routes
│ └── PublicRoute.tsx # Guard for public-only routes (e.g., login)
│
├── features/ # Feature modules (domain-driven)
│ ├── auth/ # Authentication feature
│ │ ├── authStore.tsx # Zustand store for auth state
│ │ ├── authService.ts # API calls for authentication
│ │ ├── useAuth.ts # Custom hook
│ │ └── types.ts # TypeScript interfaces
│ │
│ └── users/ # User management feature
│ ├── usersStore.ts # Zustand store for users state
│ ├── usersService.ts # API calls for users
│ ├── types.ts # TypeScript interfaces
│ └── components/ # Feature-specific components
│
├── pages/ # Page components (route views)
│ ├── Login.tsx
│ └── dashboard/
│ ├── Dashboard.tsx
│ ├── Users.tsx
│ ├── UserDetails.tsx
│ └── Settings.tsx
│
├── shared/ # Shared resources across features
│ ├── components/ # Reusable UI components
│ ├── layout/ # Layout components
│ └── store/ # Global stores (toast, settings)
│
├── services/ # Core services
│ └── api.ts # Axios instance with interceptors
│
└── i18n/ # Internationalization
└── index.ts
Architecture Principles
Feature-Based Organization
Each feature is self-contained with its own store, services, types, and components:
features/auth/authStore.tsx
features/auth/authService.ts
import { create } from "zustand" ;
import { persist } from "zustand/middleware" ;
import { loginRequest } from "./authService" ;
import type { AuthState } from "./types" ;
export const useAuthStore = create < AuthState >()(\ n persist (
( set ) => ({
user: null ,
loading: false ,
login : async ( username , password ) => {
try {
set ({ loading: true });
const data = await loginRequest ({ username , password });
set ({ user: data , loading: false });
localStorage . setItem ( "token" , data . token );
} catch ( error ) {
set ({ loading: false });
console . error ( error );
alert ( "Credenciales incorrectas" );
}
},
logout : () => {
localStorage . removeItem ( "token" );
set ({ user: null });
},
}),
{ name: "auth-storage" }
)
);
Separation of Concerns
Store State management and business logic using Zustand
Service API calls and data transformation logic
Types TypeScript interfaces and type definitions
Components UI components specific to the feature
Layered Architecture
The application follows a clear layered architecture:
Layer Responsibilities
Pages Layer (src/pages/)
Route-level components
Page composition and layout
Minimal business logic
Connects features together
Features Layer (src/features/)
Domain-specific logic
State management per feature
API integration for the feature
Feature-specific components
Services Layer (src/services/)
HTTP client configuration
Global interceptors
Shared utilities
Shared Layer (src/shared/)
Reusable UI components
Layout components
Global state (toast, settings)
State Management Strategy
The application uses Zustand for state management with the persist middleware for data persistence.
Each feature has its own store:
Auth Store (features/auth/authStore.tsx): User authentication state
Users Store (features/users/usersStore.ts): User management state
Toast Store (shared/store/useToastStore.ts): Global notifications
Settings Store (shared/store/useSettingsStore.ts): App preferences
Example store structure from features/users/usersStore.ts:18-66:
export const useUsersStore = create < UsersState >()(\ n persist (
( set , get ) => ({
users: [],
loading: false ,
error: null ,
deletingId: null ,
fetchUsers : async () => {
if ( get (). users . length > 0 ) return ;
try {
set ({ loading: true , error: null });
const usersFromApi = await getUsers ();
set ({ users: usersFromApi , loading: false });
} catch {
set ({ error: "Error fetching users" , loading: false });
}
},
addUser : ( user ) =>
set (( state ) => ({
users: [ user , ... state . users ],
})),
deleteUser : ( id ) => {
set ({ deletingId: id });
set (( state ) => ({
users: state . users . filter (( user ) => user . id !== id ),
deletingId: null ,
}));
},
updateUser : ( updatedUser : User ) =>
set (( state ) => ({
users: state . users . map (( user ) =>
user . id === updatedUser . id ? updatedUser : user
),
})),
}),
{ name: "users-storage" }
)
);
Routing Architecture
The router configuration uses nested routes with layout components. See Routing for detailed information.
API Integration
Centralized API configuration with axios interceptors for authentication. See API Integration for details.
Component Library
Shared components are built with composition in mind. See Components for the component library.
Best Practices
Feature Module Guidelines
Keep features isolated and self-contained
Each feature should have its own types, store, service, and components
Don’t import from other features directly
Use shared components for common UI patterns
State Management Guidelines
One store per feature domain
Use persist middleware for data that should survive page refreshes
Keep stores focused on a single responsibility
Implement optimistic updates where appropriate
Define types in separate types.ts files
Use interfaces for object shapes
Leverage type inference where possible
Export types for reuse across the feature
Feature-specific components go in features/{name}/components/
Reusable components go in shared/components/
Use composition over prop drilling
Keep components focused on a single responsibility
Adding a New Feature
To add a new feature to the application:
Create feature folder
Create a new folder under src/features/ with the feature name: mkdir -p src/features/my-feature/components
Define types
Create types.ts with TypeScript interfaces: export interface MyFeatureData {
id : number ;
name : string ;
}
export interface MyFeatureState {
data : MyFeatureData [];
loading : boolean ;
fetchData : () => Promise < void >;
}
Create service
Create myFeatureService.ts for API calls: import { api } from "../../services/api" ;
import type { MyFeatureData } from "./types" ;
export const getMyFeatureData = async () : Promise < MyFeatureData []> => {
const { data } = await api . get ( "/my-feature" );
return data ;
};
Create store
Create myFeatureStore.ts with Zustand: import { create } from "zustand" ;
import { getMyFeatureData } from "./myFeatureService" ;
import type { MyFeatureState } from "./types" ;
export const useMyFeatureStore = create < MyFeatureState >(( set ) => ({
data: [],
loading: false ,
fetchData : async () => {
set ({ loading: true });
const data = await getMyFeatureData ();
set ({ data , loading: false });
},
}));
Add routes
Add routes in src/app/router.tsx and create page components in src/pages/
Next Steps
State Management Learn about Zustand stores and state patterns
Routing Understand the routing structure and guards
API Integration Configure API calls and interceptors
Components Explore the shared component library