A premium mobile reading experience built using React Native + Expo + Expo Router.
- React Native + Expo (TypeScript)
- Expo Router (file-based routing)
- Redux Toolkit + Redux Persist
- createAsyncThunk for API calls
- Axios with interceptors & auth handling
- Zod + React Hook Form
- Theming with Context API (Dark/Light mode)
- Toast notifications for user feedback
- Expo Linear Gradient (gradients & animations)
- React Native Reanimated (smooth animations)
- React Native Vector Icons (tab icons)
- React Native WebView (checkout flow)
- Offline support (expo-file-system + SQLite)
- Secure storage (expo-secure-store)
Powered by Redux Toolkit
- Async actions with
createAsyncThunk - Auto-persisted via
redux-persist(AsyncStorage) - Selectors via
/src/redux/selectors/ - Memoized performance with
createSelector()
See /app and /src directories.
- Memoized Selectors: All state access uses
createSelector()for performance - Centralized Location:
/src/redux/selectors/with index.ts barrel export - Component Usage: Import selectors, never inline
state.auth.loading - Naming Convention:
select[State][Property](e.g.,selectAuthLoading) - Computed State: Derived selectors like
selectIsAuthReadyfor complex logic
API instance lives in /src/axios/EchoInstance.ts
- JWT Auth Headers: Automatic token attachment via
attachAuthToken() - 401 Invalidation: Global interceptor handles token clearance and retry protection
- AsyncStorage Integration:
attachAuthTokenToAsyncStorage()for persistence - Base Configuration: Centralized
APIInswith configured baseURL - Clean Architecture: Single source of truth for all HTTP requests
Global form system using React Hook Form + Zod
- FormProvider: Reusable wrapper component with schema validation
- TextField: Theme-aware input component with error display
- Zod Validation: Type-safe schema validation for all forms
- Theme Integration: All form components use useTheme() colors
- No Inline Forms: Always use FormProvider and TextField components
Redux async thunks for complete auth flow
- loginUser: Login with email/password, attaches JWT to Axios headers
- signupUser: User registration with email/username/password/name
- forgotPassword: Request password reset email
- confirmEmail: Verify email with OTP code
- resetPassword: Set new password after OTP verification
- getUserData: Fetch user profile data
- State Persistence: Auth state persisted via redux-persist (AsyncStorage)
- Error Handling: Consistent error messages with rejectWithValue
- App Startup:
_layout.tsxreads token from Redux withselectToken - Auto-Attachment:
attachAuthToken()called in useEffect on mount - Provider Hierarchy: Provider β PersistGate β AppContent β ThemeProvider β Stack
- Never Unhandled: Token initialization happens before any screens load
Current state structure with persistence configuration:
RootState = {
auth: AuthState, // β
Persisted (auth data, profile updates, user data)
orders: OrdersState, // Order management
// Future modules will be added here following MODULE_CREATION_TEMPLATE.md:
// wallet: WalletState, // β
Persisted (if needed)
// search: SearchState, // Search history and filters
// notifications: NotificationState, // Push notifications
}
// AuthState Structure:
interface AuthState {
data: AuthData | null; // Main auth data (token, user)
isLoading: boolean; // Global loading state
error: string | null; // Global error state
profileUpdate: ProfileUpdateData; // Profile update operations
userData: UserData; // User data operations
}Persistence Whitelist: Only auth currently persisted. New modules added only if explicitly needed for offline functionality.
- App Initialization
- Auth Slice Setup (Redux + AsyncThunks + Persistence)
- Global State Selectors (Memoized + Centralized)
- Axios Layer Setup (EchoInstance + 401 Handling + Auth Flow)
- Token Boot Flow (Auto-attach on app startup)
- Redux Module Templates (Cursor AI Training Complete)
- State Management Documentation (Redux Toolkit + Best Practices)
- β Modern Theme System (Dark/Light mode with admin panel colors)
- β Animated UI Components (ButtonSelectorGroup, PostCard, SearchBar, CustomButton)
- β Tab Navigation Layout (Home, Explore, Library with vector icons)
- β Phase: Login (Zod Validation + React Hook Form + User Info Display)
- β Form System (React Hook Form + Zod + Reusable Components)
- β Auth Actions (Redux Async Thunks with Redux Persist)
- β Authentication Flow (Login, Signup, Password Reset, Email Verification)
- β Home Feed (Gradient buttons, featured content, category browsing)
- β Explore Screen (Search, filtering, animated content discovery)
- β Library Screen (Saved/downloaded content management with stats)
- Auth Screen Redesign (Animated forms with modern styling)
- Enhanced Authentication (Biometric login, social auth)
- Downloads & Offline Support
- Kids Mode
- Article/Magazine/Digest View
- Profile & Settings
login.tsx: Sign in form using Zod + React Hook Form validationsignup.tsx: Register new user with email/password/confirmPasswordverifyEmail.tsx: OTP verification for email confirmationforgotPassword.tsx: Start password reset flowresetPassword.tsx: Set new password after OTP verification
- Modern UI: Theme-aware design with consistent spacing
- Form Validation: Zod schemas with React Hook Form integration
- Accessibility: Screen reader support with proper labels
- Error Handling: Redux state management with user feedback
- Loading States: Disabled buttons and loading indicators
ButtonSelectorGroup: Animated category selector with gradient variantsPostCard: Feature-rich content cards with gradient overlays and animationsSearchBar: Advanced search with filter integration and focus animationsCustomButton: Enhanced button component with gradient support and spring animations
- Theme: Modern admin panel colors with dark/light mode support
- Gradients: Three preset gradient combinations (Primary, Secondary, Accent)
- Animations: Reanimated-powered smooth transitions and micro-interactions
- Typography: Consistent H1/Body components with theme integration
- Accessibility: Full screen reader support and proper ARIA labels
- Responsive Design: Adaptive layouts for different screen sizes
- Performance: Memoized selectors and optimized animations
- Dark Mode: Seamless theme switching with proper color contrast
- Vector Icons: Ionicons integration for consistent iconography
- Cards & Shadows: Premium feel with elevation and shadow effects
index.tsx(Home): Featured content, gradient category buttons, theme toggleexplore.tsx: Search functionality, category filtering, animated resultslibrary.tsx: Saved/downloaded content management with statistics dashboard
- Bottom Tab Bar: Clean design with vector icons and active states
- Theme Integration: Consistent colors and elevation across all tabs
- Performance: Smooth tab switching with proper memory management
- Accessibility: Screen reader friendly navigation labels
- Mock Content: Realistic magazine, article, and digest content
- Image Integration: Unsplash images for visual content demonstration
- Category System: Proper content categorization and filtering
- Search Functionality: Full-text search across titles, descriptions, and authors
Complete authentication state management with async thunks:
loginUser: Authenticate user with email/passwordsignupUser: Register new user accountconfirmEmail: Verify email with OTP codeforgotPassword: Request password reset emailresetPassword: Set new password after verificationupdatePassword: Update user passwordlogout: Clear auth state and navigate to authclearError: Clear error state
interface AuthState {
data: AuthData | null; // Main auth data (token, user)
isLoading: boolean; // Global loading state
error: string | null; // Global error state
profileUpdate: ProfileUpdateData; // Profile update operations
userData: UserData; // User data operations
}All selectors use createSelector for performance optimization:
selectToken: Get authentication token from stateselectUserData: Get user data from userData operationsselectAuthLoading: Get global loading stateselectAuthError: Get global error stateselectUserId: Get user ID from auth dataselectUserEmail: Get user email from auth dataselectIsAuthenticated: Boolean indicating if user is authenticated
import { useSelector } from 'react-redux';
import {
selectToken,
selectUserData,
selectAuthLoading,
selectAuthError
} from '../redux/slices/selectState';
const token = useSelector(selectToken);
const isLoading = useSelector(selectAuthLoading);Type-safe validation using Zod for all authentication forms:
loginSchema: Email and password validationsignupSchema: Email, password, and confirmPassword with matching validationotpSchema: OTP code validation (6 digits)forgotPasswordSchema: Email validation for password resetresetPasswordSchema: New password and confirmPassword with matching validation
- Type Safety: Full TypeScript integration
- Error Messages: User-friendly validation messages
- Password Matching: Automatic password confirmation validation
- Email Validation: Proper email format validation
- Required Fields: Clear indication of required inputs
import { loginSchema, signupSchema } from '../form/schemas/authSchema';
// In FormProvider
<FormProvider
schema={loginSchema}
defaultValues={{ email: '', password: '' }}
onSubmit={handleSubmit}
>When developing EchoReads features, ALWAYS follow these patterns and AUTOMATICALLY update documentation.
When user requests: "Create [ModuleName] module" or "Add [feature] to Redux"
- Read:
MODULE_CREATION_TEMPLATE.md(see below) - Execute: Create slice, actions, selectors files
- Integrate: Update store.ts and selectors/index.ts
- Document: Update README.md store shape
- "Should [moduleName] state be persisted for offline use?"
- Store integration (app will break)
- Selector exports (components can't import)
- README.md store shape update (required by RULES.md)
1. src/redux/slices/[moduleName]Slice.ts # State + reducers
2. src/redux/actions/[moduleName]Actions.ts # Async thunks
3. src/redux/selectors/[moduleName]Selectors.ts # Memoized selectors
4. UPDATE: src/redux/store.ts # Add to combineReducers
5. UPDATE: src/redux/selectors/index.ts # Export selectors
6. UPDATE: README.md # Store shape sectionWhen creating a module, respond with:
Creating [ModuleName] Redux module:
β
Files Created:
- slices/[moduleName]Slice.ts (state + reducers)
- actions/[moduleName]Actions.ts (async thunks with echoInstance)
- selectors/[moduleName]Selectors.ts (memoized selectors)
β
Integration:
- Updated store.ts with [moduleName]Reducer
- Updated selectors/index.ts exports
- Updated README.md store shape
β Persistence: Should [moduleName] state be persisted for offline use?
β
Ready: Components can now import selectors from 'src/redux/selectors'
- β Check if MODULE_CREATION_TEMPLATE.md exists
- β Verify store.ts has proper structure
- β Confirm APIIns import path is correct (from EchoInstance.ts)
- β Verify no TypeScript errors
- β Check store.ts imports new reducer
- β Confirm selectors/index.ts exports new selectors
- β Validate README.md store shape updated
Every Redux module must have:
- Proper TypeScript: Full type safety
- Error Handling: try/catch + rejectWithValue
- Memoization: createSelector for all selectors
- API Integration: Use APIIns from EchoInstance, never direct axios
- Toast Feedback: Success/error messages
- Documentation: README.md store shape updated
- Create Redux modules without templates
- Skip store.ts integration
- Forget selector exports
- Miss documentation updates
- Use direct axios calls
- Skip error handling
- Create unmemoized selectors
When creating a new module (e.g., Wallet, Search, Notifications), ALWAYS follow this exact pattern:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { [moduleName]Actions } from '../actions/[moduleName]Actions';
interface [ModuleName]State {
data: any[];
loading: boolean;
error: string | null;
// Add module-specific state properties
}
const initialState: [ModuleName]State = {
data: [],
loading: false,
error: null,
};
const [moduleName]Slice = createSlice({
name: '[moduleName]',
initialState,
reducers: {
// Synchronous reducers
clearError: (state) => {
state.error = null;
},
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
},
extraReducers: (builder) => {
// Add async thunk handlers here
// builder.addCase([moduleName]Actions.fetchData.pending, (state) => {
// state.loading = true;
// state.error = null;
// })
},
});
export const { clearError, setLoading } = [moduleName]Slice.actions;
export default [moduleName]Slice.reducer;import { createAsyncThunk } from '@reduxjs/toolkit';
import APIIns from '../../axios/EchoInstance';
// Fetch data async thunk
export const fetch[ModuleName]Data = createAsyncThunk(
'[moduleName]/fetchData',
async (params: any, { rejectWithValue }) => {
try {
const response = await APIIns.get('/[moduleName]', { params });
// β
REQUIRED: Toast feedback on success
// Toast.show('Data loaded successfully!', Toast.LONG);
console.log('Success: [ModuleName] data loaded');
return response.data;
} catch (error: any) {
const message = error.response?.data?.message || '[ModuleName] fetch failed';
// β
REQUIRED: Toast feedback on error
// Toast.show(message, Toast.LONG);
console.error('Error:', message);
// β
REQUIRED: rejectWithValue for proper error handling
return rejectWithValue(message);
}
}
);
// Add more async thunks as needed for the moduleimport { createSelector } from '@reduxjs/toolkit';
import { RootState } from '../store';
// Base selector
const select[ModuleName]State = (state: RootState) => state.[moduleName];
// Simple selectors
export const select[ModuleName]Data = createSelector(
[select[ModuleName]State],
([moduleName]) => [moduleName].data
);
export const select[ModuleName]Loading = createSelector(
[select[ModuleName]State],
([moduleName]) => [moduleName].loading
);
export const select[ModuleName]Error = createSelector(
[select[ModuleName]State],
([moduleName]) => [moduleName].error
);
// Computed selectors
export const selectHas[ModuleName]Data = createSelector(
[select[ModuleName]Data],
(data) => data.length > 0
);
export const select[ModuleName]Status = createSelector(
[select[ModuleName]Loading, select[ModuleName]Error],
(loading, error) => {
if (loading) return 'loading';
if (error) return 'error';
return 'success';
}
);// Add import
import [moduleName]Reducer from './slices/[moduleName]Slice';
// Update rootReducer
const rootReducer = combineReducers({
auth: authReducer,
orders: ordersReducer,
[moduleName]: [moduleName]Reducer, // ADD THIS LINE
});
// Update persistConfig if persistence needed
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth', '[moduleName]'], // ADD MODULE IF PERSISTENCE NEEDED
};// Add export
export * from './[moduleName]Selectors';### Redux Store Shape
- **auth**: Authentication state (persisted)
- **listing**: Magazines and articles
- **[moduleName]**: [Module description] (persisted if needed)- β Don't forget to add slice to store.ts
- β Don't forget to export selectors from index.ts
- β Don't skip documentation updates
- β Don't persist state unless necessary
- β Don't use direct API calls (always use APIIns from EchoInstance)
- β Don't skip Toast feedback in actions
- β Don't put API calls in slice files (only in actions)
- β Don't skip try/catch blocks in async thunks
- β Don't forget rejectWithValue() for error handling
- β Don't create unmemoized expensive selectors
- Slice created with proper TypeScript types
- Actions use createAsyncThunk with echoInstance
- Selectors use createSelector for memoization
- Store.ts updated with new reducer
- Selectors exported from index.ts
- Persistence added to whitelist if needed
- README.md updated with store shape
- RULES.md updated if new patterns introduced
- All files follow naming conventions
- Error handling and Toast feedback included
EchoReads has standardized on /src/axios/EchoInstance.ts as the ONLY allowed Axios configuration.
β New (Required):
import APIIns from '../axios/EchoInstance';
import { attachAuthToken } from '../axios/EchoInstance';β New:
const response = await APIIns.get('/users');
const data = await APIIns.post('/auth/login', credentials);β New:
import { attachAuthToken, attachAuthTokenToAsyncStorage } from '../axios/EchoInstance';
// Token clearing is now handled automatically by 401 interceptor- β Simpler API - Focused on core functionality
- β 401 Handling - Automatic token clearance with retry protection
- β Cleaner Code - Less complexity, more maintainable
- β Single Source - One place for all HTTP configuration
All existing code should be migrated to use EchoInstance.ts. The apiClient.ts file is now deprecated and will be removed from code.
Per RULES.md Section 5, only EchoInstance.ts is allowed for HTTP requests. Any direct axios usage or custom HTTP clients are prohibited.
The new EchoInstance.ts requires axios which is not currently installed.
Please run this command to install the required dependency:
npm install axiosOR
yarn add axiosOnce axios is installed, the TypeScript errors in src/axios/EchoInstance.ts will be resolved and the API layer will be ready to use.
This will add to your dependencies:
{
"dependencies": {
"axios": "^1.x.x"
}
}Note: This file will be automatically updated by Cursor as modules and phases are completed.
β Do not make changes to this file manually unless explicitly updating a phase or module. β Cursor, treat this file as dynamic documentation. Update it automatically after each completed module or feature.
src/redux/
βββ store.ts # Main store configuration with persistence
βββ actions/ # Async thunks for API calls
β βββ authActions.ts
βββ slices/ # State + reducers
β βββ authSlice.ts
β βββ ordersSlice.ts
βββ selectors/ # Memoized state selectors
β βββ index.ts # Barrel exports
β βββ authSelectors.ts
β βββ README.md
βββ utils/ # Helper functions
β βββ asyncThunkHelper.ts
β
βββ MODULE_CREATION_TEMPLATE.md # π€ Code templates for new modules
βββ CURSOR_TRAINING.md # π€ AI training instructions
βββ README.md # This file
import { useSelector } from 'react-redux';
import { selectAuthLoading, selectUserName } from '../redux/selectors';
const MyComponent = () => {
const isLoading = useSelector(selectAuthLoading);
const userName = useSelector(selectUserName);
return <Text>{userName}</Text>;
};import { useDispatch } from 'react-redux';
import { loginUser } from '../redux/actions/authActions';
const dispatch = useDispatch();
dispatch(loginUser({ email, password }));import APIIns from '../axios/EchoInstance';
// In async thunks only
const response = await APIIns.get('/users');
const data = await APIIns.post('/auth/login', credentials);For Cursor AI: Follow MODULE_CREATION_TEMPLATE.md exactly.
For Humans: Run through this checklist:
- β
Create
slices/[moduleName]Slice.ts - β
Create
actions/[moduleName]Actions.ts - β
Create
selectors/[moduleName]Selectors.ts - β
Update
store.tswith new reducer - β
Update
selectors/index.tsexports - β
Update
README.mdstore shape - β Add to persistence whitelist if needed
RootState = {
auth: {
isAuthenticated: boolean;
user: User | null;
token: string | null;
loading: boolean;
error: string | null;
isEmailVerified: boolean;
},
listing: {
magazines: Magazine[];
articles: Article[];
loading: boolean;
},
// Future modules: wallet, search, notifications, etc.
}- Persisted:
auth(for login persistence) - Non-Persisted: All other slices (performance optimization)
- Storage: AsyncStorage via redux-persist
- Use
createSelector()for all selectors - Import selectors from
'../redux/selectors' - Use
APIInsfrom EchoInstance.ts in all async thunks - Include error handling and Toast feedback
- Follow naming conventions:
select[Module][Property]
- Use inline selectors:
state => state.auth.loading - Call API directly without async thunks
- Use any Axios instance except APIIns from EchoInstance.ts
- Skip store integration when creating modules
- Forget to export selectors from index.ts
- Persist state unless needed for offline functionality
import APIIns from '../axios/EchoInstance';
// In async thunks
const response = await APIIns.post('/auth/login', credentials);
const users = await APIIns.get('/users');
const updated = await APIIns.put('/profile', userData);// DON'T DO THIS
import axios from 'axios';
const response = await axios.post('https://api.com/login', data);- Tokens auto-attached to requests via
Authorization: Bearer <token> attachAuthToken(token)for manual token settingattachAuthTokenToAsyncStorage()loads token from storage
- Automatically clears AsyncStorage on 401 responses
- Retry protection via
_retryflag - Removes auth token from future requests
- Production:
https://api.echoreads.com - Configurable via
API_BASE_URL
- Console logs for debugging API calls
- Error logging with status codes
import { useSelector } from 'react-redux';
import {
selectAuthLoading,
selectIsAuthenticated,
selectUserName
} from '../redux/selectors';
const MyComponent = () => {
const isLoading = useSelector(selectAuthLoading);
const isAuth = useSelector(selectIsAuthenticated);
const userName = useSelector(selectUserName);
return <Text>{userName}</Text>;
};// DON'T DO THIS
const isLoading = useSelector(state => state.auth.loading);
const user = useSelector(state => state.auth.user);selectToken- JWT tokenselectUserId- Current user IDselectUser- Full user objectselectAuthLoading- Loading stateselectAuthError- Error messageselectIsAuthenticated- Boolean auth statusselectIsEmailVerified- Email verification statusselectUserEmail- User's emailselectUserName- User's display nameselectHasAuthToken- Boolean token checkselectIsAuthReady- Computed ready stateselectAuthStatus- Combined auth statusselectHasAuthError- Boolean error checkselectAuthErrorMessage- Error message with fallback
All markdown documentation is centralized at project root for easier maintenance.