Skip to content

Zelta Chat Overview

Zelta Chat is the official mobile application for the Zelta Chat customer engagement platform, enabling support teams to manage customer conversations on the go.

Problem Solved:

  • Remote support teams need access to customer conversations outside desktop environments
  • Real-time response to customer queries improves engagement and satisfaction
  • Support agents require mobile access to canned responses, team collaboration, and conversation management

Target Users:

  • Customer support agents
  • Team leads and managers
  • Sales representatives using Zelta Chat for customer engagement

Non-Goals:

  • Full feature parity with Zelta Chat web application
  • Admin configuration and account setup (web-only features)
  • Advanced reporting and analytics (read-only access available)

The application follows a layered architecture with clear separation of concerns:

┌─────────────────────────────────────────────┐
│ Presentation Layer │
│ (Screens, Components, Navigation) │
├─────────────────────────────────────────────┤
│ State Management │
│ (Redux Toolkit + Persist) │
├─────────────────────────────────────────────┤
│ Service Layer │
│ (API, WebSocket, Notifications, Auth) │
├─────────────────────────────────────────────┤
│ External Services │
│ (Zelta Chat API, Firebase, Sentry) │
└─────────────────────────────────────────────┘

Data Flow:

User Action → Dispatch Redux Action → Thunk/Service Call → API Request
↓ ↓
UI Update ← Redux State Update ← Reducer ← Response Processing
WebSocket ← Real-time Updates ← Action Cable → Server Events
  • /src/ - Main application source code
  • /assets/ - Static assets (app icons, splash screens)
  • /__mocks__/ - Jest mocks for third-party libraries
  • /patches/ - PNPM patch files for dependencies
  • App.tsx - Application entry point with Sentry wrapper
  • app.config.ts - Expo configuration
  • babel.config.js - Babel transpiler configuration
  • metro.config.js - Metro bundler configuration
  • tsconfig.json - TypeScript compiler configuration
/src/
├── app.tsx # Root component with Redux Provider
├── assets/ # Fonts, images, local resources
├── components-next/ # Next-gen UI component library
├── constants/ # App-wide constants and permissions
├── context/ # React Context providers
├── hooks/ # Custom React hooks
├── i18n/ # Internationalization (42 languages)
├── navigation/ # Navigation configuration
├── screens/ # Screen components
├── services/ # API and external service integrations
├── store/ # Redux state management
├── svg-icons/ # SVG icon components
├── theme/ # Theming system (Tailwind config)
├── types/ # TypeScript type definitions
└── utils/ # Utility functions

The Redux store is organized by domain:

  • auth/ - Authentication and user session
  • settings/ - App settings and configuration
  • conversation/ - Conversation management (multiple slices)
  • contact/ - Contact management
  • inbox/ - Inbox management
  • label/ - Label/tag management
  • notification/ - Push notification handling
  • team/ - Team and agent management
  • canned-response/ - Quick reply templates
  • macro/ - Automated workflow actions
  • dashboard-app/ - Dashboard widget integrations
  • React Native 0.76.9 - Cross-platform mobile framework
  • Expo ~52.0 - Development and build tooling
  • TypeScript 5.1+ - Type safety and developer experience
  • Redux Toolkit 2.5+ - Predictable state container
  • Redux Persist - State persistence across sessions
  • React Redux - React bindings for Redux
  • React Navigation 6.1+ - Navigation framework
    • Native Stack Navigator - Screen transitions
    • Bottom Tabs Navigator - Tab-based navigation
  • Tailwind CSS + TWRNC - Utility-first styling
  • React Native Reanimated 3.16+ - High-performance animations
  • React Native Gesture Handler - Touch gesture handling
  • Expo Image - Optimized image loading
  • @gorhom/bottom-sheet - Bottom sheet modals
  • @kesha-antonov/react-native-action-cable - WebSocket connections
  • @react-native-firebase/messaging - Push notifications
  • @notifee/react-native - Local notification management
  • expo-av - Audio/video playback
  • react-native-audio-recorder-player - Audio recording
  • ffmpeg-kit-react-native - Media processing
  • react-native-image-picker - Image selection
  • react-native-document-picker - File selection
  • Axios - HTTP client
  • Camelcase-keys / Snakecase-keys - API key transformation
  • Reactotron - Debugging and inspection
  • Sentry - Error tracking and monitoring
  • Jest - Unit testing
  • ESLint + Prettier - Code quality
  • i18n-js - Translation management (42 languages supported)
Terminal window
# Start development server with dev client
pnpm start
# Start with production mode
pnpm start:production
# Run on iOS simulator
pnpm ios
# Run on Android emulator
pnpm android
# Clean cache and dependencies
pnpm clean
Terminal window
# Type checking
pnpm type:check
# Linting
pnpm lint
pnpm lint:fix
# Clean up unused imports
pnpm imports:cleanup
# Run tests
pnpm test
Terminal window
# Generate native projects
pnpm generate # Clean generation
pnpm generate:soft # Preserve changes
# Local builds
pnpm build:ios:local
pnpm build:android:local
pnpm build:all:local
# EAS builds
pnpm build:ios
pnpm build:android
pnpm build:all
# Submissions
pnpm submit:ios
pnpm submit:android
pnpm submit:all
Terminal window
# Generate stories
pnpm storybook-generate
# Start Storybook
pnpm start:storybook
pnpm storybook:ios
pnpm storybook:android

To add a new feature to Zelta Chat, follow this pattern:

Create a new directory in /src/store/{feature-name}/:

// /src/store/{feature-name}/{feature-name}Slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface FeatureState {
data: any[];
loading: boolean;
error: string | null;
}
const initialState: FeatureState = {
data: [],
loading: false,
error: null,
};
const featureSlice = createSlice({
name: 'featureName',
initialState,
reducers: {
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
setData: (state, action: PayloadAction<any[]>) => {
state.data = action.payload;
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
},
});
export default featureSlice.reducer;
export const { setLoading, setData, setError } = featureSlice.actions;
// /src/store/{feature-name}/{feature-name}Service.ts
import { apiService } from '@/services/APIService';
export const featureService = {
async fetchData() {
const response = await apiService.get('endpoint');
return response.data;
},
};
// /src/store/{feature-name}/{feature-name}Actions.ts
import { createAsyncThunk } from '@reduxjs/toolkit';
import { featureService } from './featureService';
export const fetchFeatureData = createAsyncThunk(
'feature/fetchData',
async (_, { rejectWithValue }) => {
try {
return await featureService.fetchData();
} catch (error) {
return rejectWithValue(error);
}
}
);
/src/store/reducers.ts
import featureSlice from '@/store/feature-name/featureNameSlice';
export const appReducer = combineReducers({
// ... existing reducers
featureName: featureSlice,
});
/src/screens/feature-name/FeatureScreen.tsx
import React from 'react';
import { View } from 'react-native';
import { useAppDispatch, useAppSelector } from '@/hooks';
export const FeatureScreen = () => {
const dispatch = useAppDispatch();
const data = useAppSelector(state => state.featureName.data);
// Component implementation
};

Update navigation stack in /src/navigation/stack/ or add to tab navigator in /src/navigation/tabs/AppTabs.tsx.

  • Unit tests for reducers: /src/store/{feature-name}/specs/
  • Service tests for API integration
  • Component tests for UI logic
  • Integration tests for feature workflows

For reusable UI components, add to /src/components-next/:

/src/components-next/my-component/MyComponent.tsx
interface MyComponentProps {
title: string;
onPress?: () => void;
}
export const MyComponent: React.FC<MyComponentProps> = ({ title, onPress }) => {
// Implementation
};
// /src/components-next/my-component/index.ts
export { MyComponent } from './MyComponent';

Register in /src/components-next/index.ts:

export * from './my-component';
  • Token-based authentication using Zelta Chat API
  • Secure token storage using AsyncStorage (encrypted on device)
  • Automatic token refresh and session management
  • Logout on 401 responses
  • All API communication over HTTPS
  • Sensitive data encrypted at rest via device-level encryption
  • No local caching of sensitive customer data
  • Redux Persist excludes sensitive fields
  • Camera access for image/video uploads
  • Photo library access for media selection
  • Microphone access for voice messages
  • Push notification permissions for real-time alerts
  • Storage permissions for file downloads (Android)
  • Minimal data collection
  • No third-party analytics in production builds (development only)
  • Sentry error reporting (configurable)
  • Compliance with GDPR and data protection regulations

The app uses Expo Application Services (EAS) for builds and deployments:

Environments:

  • Development - Internal testing with Reactotron debugging
  • Preview - Beta testing via TestFlight (iOS) and Internal Testing (Android)
  • Production - App Store and Google Play releases

Build Configuration: eas.json

{
"build": {
"production": {
"android": { "buildType": "app-bundle" },
"ios": { "buildConfiguration": "Release" }
}
}
}
  1. Version Bump - Update version in app.config.ts and package.json
  2. Changelog - Document changes for release notes
  3. Build - Run EAS build for target platform(s)
  4. Test - Beta testing via TestFlight/Internal Testing
  5. Submit - Submit to App Store Connect / Google Play Console
  6. Monitor - Track crashes and errors via Sentry
  • iOS: 13.4+ (configured in app.config.ts)
  • Android: SDK 24+ (Android 6.0+)
  • Zelta Chat Server: 3.13.0+

Required environment variables (.env file):

Terminal window
EXPO_PUBLIC_APP_SLUG=Zelta Chat-mobile
EXPO_PUBLIC_PROJECT_ID=<eas-project-id>
EXPO_PUBLIC_SENTRY_DSN=<sentry-dsn>
EXPO_PUBLIC_SENTRY_PROJECT_NAME=<project>
EXPO_PUBLIC_SENTRY_ORG_NAME=<org>
EXPO_PUBLIC_IOS_GOOGLE_SERVICES_FILE=./GoogleService-Info.plist
EXPO_PUBLIC_ANDROID_GOOGLE_SERVICES_FILE=./google-services.json
EXPO_PUBLIC_APP_HOST=<your-domain>