Add Positive Pay, statement guide v1.0, onboarding, and switch every feature on

- Positive Pay on the 12 Oct /pps contract (account number, not account ID):
  OTP enrollment, cheque submit, saved cheques, detail and stop payment.
  Full flow passed on the phone against mobile-banking-backend.lincpay.
- Statement & Mini Statement guide v1.0 (14 Oct): mini statement JSON on
  Home and the Statement screen; dated PDF export with the password rule.
- Onboarding slides and splash screen, Inter font, shared Icon and
  TypewriterText components, nearby branches via OpenStreetMap.
- Every feature switched on; the 14 Oct switch set is kept in
  featureConfig.stage-2026-10-14.ts. Loans run on demo data until the
  backend has /loans routes.
- npm run android wraps run-android for shells spawned from VS Code.
- OpenProject work packages updated to 14-10-2026 status, with screenshots
  of Positive Pay, statement, loans and cards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Abin Toppo
2026-10-14 18:18:40 +05:30
co-authored by Claude Opus 5.5
parent 5fbd13ba12
commit f960a119e8
510 changed files with 20268 additions and 6483 deletions
@@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, StyleSheet, Pressable, Alert, Platform, KeyboardAvoidingView } from 'react-native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeInRight, FadeOutLeft } from 'react-native-reanimated';
import { Input, Button, DatePicker, KeyboardAwareScrollView } from '../../../components';
@@ -31,7 +31,7 @@ const StepIndicator = ({ steps, currentStepIndex }: { steps: any[], currentStepI
}
]}>
{i < currentStepIndex ? (
<Ionicons name="checkmark" size={14} color={theme.colors.white} />
<Icon name="checkmark" size={14} color={theme.colors.white} />
) : (
<Text style={{ color: isPassed ? theme.colors.white : theme.colors.textMuted, fontSize: 12, fontFamily: fontFamily.bold }}>{i + 1}</Text>
)}
@@ -94,9 +94,9 @@ export default function AccountOpeningScreen({ navigation }: { navigation: Nativ
{/* Local header */}
<View style={styles.localHeader}>
<Pressable onPress={() => navigation.goBack()} hitSlop={12} style={styles.backBtn}>
<Ionicons name="chevron-back" size={24} color={theme.colors.textPrimary} />
<Icon name="chevron-back" size={24} color={theme.colors.textPrimary} />
</Pressable>
<Text style={[styles.localHeaderTitle, { color: theme.colors.textPrimary }]}>NEW ACCOUNT</Text>
<Text style={[styles.localHeaderTitle, { color: theme.colors.textPrimary }]}>New account</Text>
</View>
<StepIndicator steps={steps} currentStepIndex={currentStepIndex} />
@@ -121,8 +121,8 @@ export default function AccountOpeningScreen({ navigation }: { navigation: Nativ
]}
onPress={() => setAccountType(type.id)}
>
<View style={[styles.typeIcon, { backgroundColor: `${theme.colors.primary}15` }]}>
<Ionicons name={type.icon as any} size={28} color={theme.colors.primary} />
<View style={[styles.typeIcon, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}>
<Icon name={type.icon as any} size={28} color={theme.colors.primary} />
</View>
<View style={{ flex: 1 }}>
<Text style={[styles.typeLabel, { color: theme.colors.textPrimary }]}>{type.label}</Text>
@@ -245,8 +245,8 @@ export default function AccountOpeningScreen({ navigation }: { navigation: Nativ
)}
</View>
<View style={[styles.warningBox, { backgroundColor: `${theme.colors.info}10`, borderColor: `${theme.colors.info}30` }]}>
<Ionicons name="cash-outline" size={20} color={theme.colors.info} />
<View style={[styles.warningBox, { backgroundColor: theme.tint(theme.colors.info, 0.06), borderColor: theme.tint(theme.colors.info, 0.19) }]}>
<Icon name="cash-outline" size={20} color={theme.colors.info} />
<Text style={[styles.warningText, { color: theme.colors.info }]}>
Please collect the initial deposit amount from the customer in cash before proceeding.
</Text>
@@ -400,9 +400,8 @@ const styles = StyleSheet.create({
marginTop: 8,
},
stepLabel: {
fontSize: 10,
fontSize: 11,
fontFamily: fontFamily.semibold,
textTransform: 'uppercase',
letterSpacing: 0.3,
width: 54,
textAlign: 'center',
@@ -478,8 +477,7 @@ const styles = StyleSheet.create({
sectionDividerText: {
fontSize: 12,
fontFamily: fontFamily.bold,
textTransform: 'uppercase',
letterSpacing: 0.5,
letterSpacing: 0.1,
paddingHorizontal: 12,
marginTop: -8,
},
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeInUp } from 'react-native-reanimated';
import { ScreenShell, Input, Button, KeyboardAwareScrollView } from '../../../components';
@@ -21,8 +21,8 @@ const FormSection = ({ title, icon, children }: { title: string, icon: string, c
return (
<Animated.View entering={FadeInUp} style={[styles.section, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<View style={[styles.sectionHeader, { borderBottomColor: theme.colors.cardBorder }]}>
<View style={[styles.sectionIcon, { backgroundColor: `${theme.colors.primary}15` }]}>
<Ionicons name={icon as any} size={18} color={theme.colors.primary} />
<View style={[styles.sectionIcon, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}>
<Icon name={icon as any} size={18} color={theme.colors.primary} />
</View>
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>{title}</Text>
</View>
@@ -103,7 +103,7 @@ export default function AddEditCustomerScreen({ route, navigation }: Props) {
variant="secondary"
onPress={handleFetchClient}
loading={isLookingUp}
leftAccessory={<Ionicons name="search-outline" size={18} color={theme.colors.textPrimary} />}
leftAccessory={<Icon name="search-outline" size={18} color={theme.colors.textPrimary} />}
/>
</FormSection>
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, RefreshControl, Pressable } from 'r
import { CompositeNavigationProp } from '@react-navigation/native';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeIn } from 'react-native-reanimated';
import { ScreenShell } from '../../../components';
@@ -99,8 +99,8 @@ export default function AgentDashboardScreen({ navigation }: { navigation: Agent
<Text style={[styles.greeting, { color: theme.colors.textSecondary }]}>{getGreeting()},</Text>
<Text style={[styles.name, { color: theme.colors.textPrimary }]}>{profile?.firstName || 'Agent'}</Text>
</View>
<View style={[styles.avatarFrame, { backgroundColor: `${theme.colors.primary}15` }]}>
<Ionicons name="person" size={20} color={theme.colors.primary} />
<View style={[styles.avatarFrame, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}>
<Icon name="person" size={20} color={theme.colors.primary} />
</View>
</View>
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { View, StyleSheet, Text, Pressable, ScrollView } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeIn, FadeInRight, FadeInLeft } from 'react-native-reanimated';
import { ScreenShell } from '../../../components';
@@ -47,7 +47,7 @@ export default function AgentLoansScreen({ route, navigation }: Props) {
]}
onPress={() => setActiveTab(tab.key)}
>
<Ionicons
<Icon
name={tab.icon as any}
size={16}
color={isActive ? theme.colors.white : theme.colors.textMuted}
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Alert } from 'react-nati
import { CompositeNavigationProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeInUp } from 'react-native-reanimated';
import { ScreenShell, Button } from '../../../components';
@@ -46,12 +46,12 @@ export default function AgentProfileScreen({ navigation }: { navigation: Navigat
onPress={onPress}
>
<View style={styles.settingsRowLeft}>
<View style={[styles.settingsIconFrame, { backgroundColor: destructive ? `${theme.colors.danger}15` : `${theme.colors.primary}15` }]}>
<Ionicons name={icon as any} size={20} color={destructive ? theme.colors.danger : theme.colors.primary} />
<View style={[styles.settingsIconFrame, { backgroundColor: destructive ? theme.tint(theme.colors.danger, 0.08) : theme.tint(theme.colors.primary, 0.08) }]}>
<Icon name={icon as any} size={20} color={destructive ? theme.colors.danger : theme.colors.primary} />
</View>
<Text style={[styles.settingsLabel, { color: destructive ? theme.colors.danger : theme.colors.textPrimary }]}>{label}</Text>
</View>
<Ionicons name="chevron-forward" size={20} color={theme.colors.textMuted} />
<Icon name="chevron-forward" size={20} color={theme.colors.textMuted} />
</Pressable>
);
@@ -60,12 +60,12 @@ export default function AgentProfileScreen({ navigation }: { navigation: Navigat
<ScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
<Animated.View entering={FadeInUp} style={[styles.profileCard, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<View style={[styles.avatarBig, { backgroundColor: `${theme.colors.primary}15` }]}>
<View style={[styles.avatarBig, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}>
<Text style={[styles.avatarText, { color: theme.colors.primary }]}>{profile?.firstName?.charAt(0) || 'A'}</Text>
</View>
<Text style={[styles.name, { color: theme.colors.textPrimary }]}>{profile?.firstName} {profile?.lastName}</Text>
<Text style={[styles.role, { color: theme.colors.textMuted }]}>Field Agent • {profile?.email}</Text>
<View style={[styles.badge, { backgroundColor: `${theme.colors.success}15` }]}>
<View style={[styles.badge, { backgroundColor: theme.tint(theme.colors.success, 0.08) }]}>
<View style={[styles.statusDot, { backgroundColor: theme.colors.success }]} />
<Text style={[styles.badgeText, { color: theme.colors.success }]}>Active Agent</Text>
</View>
@@ -184,8 +184,7 @@ const styles = StyleSheet.create({
sectionTitle: {
fontSize: 13,
fontFamily: fontFamily.semibold,
textTransform: 'uppercase',
letterSpacing: 0.5,
letterSpacing: 0.1,
marginBottom: 8,
marginLeft: 8,
},
@@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, StyleSheet, Share } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import { ScreenShell, Button } from '../../../components';
import { useToast } from '../../../hooks/useToast';
@@ -49,7 +49,7 @@ export default function AgentRouteScreen(_props: Props) {
<ScreenShell title="My Route" subtitle="Today's collection route" contentContainerStyle={styles.content}>
{/* Map preview */}
<View style={[styles.mapPlaceholder, { borderColor: theme.colors.cardBorder, backgroundColor: theme.colors.surfaceMuted }]}>
<Ionicons name="map-outline" size={48} color={theme.colors.primary} />
<Icon name="map-outline" size={48} color={theme.colors.primary} />
<Text style={[styles.mapText, { color: theme.colors.textSecondary }]}>Route overview · 5 stops · 8.4 km</Text>
<View style={styles.pinRow}>
{STOPS.map((stop) => (
@@ -81,8 +81,8 @@ export default function AgentRouteScreen(_props: Props) {
<View style={[styles.stopsCard, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
{STOPS.map((stop, index) => (
<View key={stop.id} style={[styles.stopRow, index !== STOPS.length - 1 && { borderBottomWidth: 1, borderBottomColor: theme.colors.cardBorder }]}>
<View style={[styles.stopDot, { backgroundColor: `${statusColor(stop.status)}20` }]}>
<Ionicons
<View style={[styles.stopDot, { backgroundColor: theme.tint(statusColor(stop.status), 0.13) }]}>
<Icon
name={stop.status === 'done' ? 'checkmark' : 'location'}
size={14}
color={statusColor(stop.status)}
@@ -104,7 +104,7 @@ export default function AgentRouteScreen(_props: Props) {
fullWidth={false}
style={styles.actionBtn}
onPress={() => toast('Opening maps', 'info', 'Launching turn-by-turn navigation for your route.')}
leftAccessory={<Ionicons name="navigate-outline" size={18} color={theme.colors.white} />}
leftAccessory={<Icon name="navigate-outline" size={18} color={theme.colors.white} />}
/>
<Button
title="Share Route"
@@ -112,7 +112,7 @@ export default function AgentRouteScreen(_props: Props) {
fullWidth={false}
style={styles.actionBtn}
onPress={handleShare}
leftAccessory={<Ionicons name="share-social-outline" size={18} color={theme.colors.textPrimary} />}
leftAccessory={<Icon name="share-social-outline" size={18} color={theme.colors.textPrimary} />}
/>
</View>
<View style={{ height: 32 }} />
@@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, StyleSheet, ScrollView, Pressable } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeIn, SlideInUp } from 'react-native-reanimated';
import { ScreenShell, Button } from '../../../components';
@@ -51,8 +51,8 @@ export default function CollectionReceiptScreen({ route, navigation }: Props) {
<Animated.View entering={SlideInUp.springify()} style={[styles.receiptCard, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<View style={styles.successHeader}>
<View style={[styles.successIcon, { backgroundColor: `${theme.colors.success}15` }]}>
<Ionicons name="checkmark" size={32} color={theme.colors.success} />
<View style={[styles.successIcon, { backgroundColor: theme.tint(theme.colors.success, 0.08) }]}>
<Icon name="checkmark" size={32} color={theme.colors.success} />
</View>
<Text style={[styles.successTitle, { color: theme.colors.success }]}>Payment Successful</Text>
<Text style={[styles.successAmount, { color: theme.colors.textPrimary }]}>{formatFullCurrency(receipt.amount)}</Text>
@@ -81,12 +81,12 @@ export default function CollectionReceiptScreen({ route, navigation }: Props) {
<Animated.View entering={FadeIn.delay(300)} style={styles.actionsRow}>
<Pressable style={[styles.actionBtn, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<Ionicons name="share-outline" size={20} color={theme.colors.primary} />
<Icon name="share-outline" size={20} color={theme.colors.primary} />
<Text style={[styles.actionBtnText, { color: theme.colors.primary }]}>Share PDF</Text>
</Pressable>
<Pressable style={[styles.actionBtn, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<Ionicons name="print-outline" size={20} color={theme.colors.primary} />
<Icon name="print-outline" size={20} color={theme.colors.primary} />
<Text style={[styles.actionBtnText, { color: theme.colors.primary }]}>Print</Text>
</Pressable>
</Animated.View>
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { View, StyleSheet, FlatList, RefreshControl, Text } from 'react-native';
import Animated, { FadeInUp } from 'react-native-reanimated';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import { useTheme, fontFamily } from '../../../theme';
import { TAB_BAR_HEIGHT } from '../../../utils/layoutConstants';
@@ -51,7 +51,7 @@ export default function CommissionDashboardScreen() {
<Text style={styles.totalValue}>{formatAgentCurrency(summary.totalEarned)}</Text>
</View>
<View style={styles.iconCircle}>
<Ionicons name="star" size={24} color={theme.colors.primary} />
<Icon name="star" size={24} color={theme.colors.primary} />
</View>
</View>
<View style={styles.totalDivider} />
@@ -72,7 +72,7 @@ export default function CommissionDashboardScreen() {
<Animated.View entering={FadeInUp.delay(index * 50)} style={[styles.historyCard, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<View style={styles.historyHeader}>
<Text style={[styles.historyMonth, { color: theme.colors.textPrimary }]}>{item.month} {item.year}</Text>
<View style={[styles.statusBadge, { backgroundColor: item.status === 'paid' ? `${theme.colors.success}15` : `${theme.colors.warning}15` }]}>
<View style={[styles.statusBadge, { backgroundColor: item.status === 'paid' ? theme.tint(theme.colors.success, 0.08) : theme.tint(theme.colors.warning, 0.08) }]}>
<Text style={[styles.statusText, { color: item.status === 'paid' ? theme.colors.success : theme.colors.warning }]}>
{item.status.toUpperCase()}
</Text>
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { View, Text, StyleSheet, Pressable, Platform, KeyboardAvoidingView } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeInRight, FadeOutLeft, FadeIn } from 'react-native-reanimated';
import { ScreenShell, Input, Button, KeyboardAwareScrollView } from '../../../components';
@@ -41,7 +41,7 @@ const StepIndicator = ({ step }: { step: number }) => {
},
]}>
{isCompleted ? (
<Ionicons name="checkmark" size={14} color={theme.colors.white} />
<Icon name="checkmark" size={14} color={theme.colors.white} />
) : (
<Text style={{
color: isActive ? theme.colors.white : theme.colors.textMuted,
@@ -210,7 +210,7 @@ export default function CreateLoanRequestScreen({ route, navigation }: Props) {
onPress={() => updateField('category', cat.id as any)}
>
<View style={[styles.catIcon, { backgroundColor: surface }]}>
<Ionicons name={cat.icon as any} size={22} color={tone} />
<Icon name={cat.icon as any} size={22} color={tone} />
</View>
<Text style={[
styles.catLabel,
@@ -220,7 +220,7 @@ export default function CreateLoanRequestScreen({ route, navigation }: Props) {
<View style={[styles.catCheck, { backgroundColor: tone }]}>
{/* On a saturated fill, so this takes the on-fill
tone — white in light mode, dark ink in dark. */}
<Ionicons
<Icon
name="checkmark"
size={12}
color={theme.colors.textOnAccentFill}
@@ -269,7 +269,7 @@ export default function CreateLoanRequestScreen({ route, navigation }: Props) {
]}
>
<View style={[styles.bannerIcon, { backgroundColor: selectedTone.surface }]}>
<Ionicons name={selectedCategory.icon as any} size={20} color={selectedTone.tone} />
<Icon name={selectedCategory.icon as any} size={20} color={selectedTone.tone} />
</View>
<View style={{ flex: 1 }}>
<Text style={[styles.bannerTitle, { color: selectedTone.tone }]}>{selectedCategory.label}</Text>
@@ -335,7 +335,7 @@ export default function CreateLoanRequestScreen({ route, navigation }: Props) {
{/* Header badge */}
{selectedCategory && (
<View style={[styles.reviewBadge, { backgroundColor: selectedTone.surface }]}>
<Ionicons name={selectedCategory.icon as any} size={18} color={selectedTone.tone} />
<Icon name={selectedCategory.icon as any} size={18} color={selectedTone.tone} />
<Text style={[styles.reviewBadgeText, { color: selectedTone.tone }]}>
{selectedCategory.label}
</Text>
@@ -359,8 +359,8 @@ export default function CreateLoanRequestScreen({ route, navigation }: Props) {
</View>
{/* Documents info */}
<View style={[styles.docsCard, { backgroundColor: `${theme.colors.info}08`, borderColor: `${theme.colors.info}25` }]}>
<Ionicons name="document-attach-outline" size={20} color={theme.colors.info} />
<View style={[styles.docsCard, { backgroundColor: theme.tint(theme.colors.info, 0.03), borderColor: theme.tint(theme.colors.info, 0.15) }]}>
<Icon name="document-attach-outline" size={20} color={theme.colors.info} />
<View style={{ flex: 1 }}>
<Text style={[styles.docsTitle, { color: theme.colors.info }]}>Documents can be added later</Text>
<Text style={[styles.docsText, { color: theme.colors.textMuted }]}>
@@ -371,7 +371,7 @@ export default function CreateLoanRequestScreen({ route, navigation }: Props) {
{/* Consent */}
<View style={[styles.consentBox, { backgroundColor: theme.colors.surfaceMuted }]}>
<Ionicons name="shield-checkmark-outline" size={18} color={theme.colors.success} />
<Icon name="shield-checkmark-outline" size={18} color={theme.colors.success} />
<Text style={[styles.consentText, { color: theme.colors.textMuted }]}>
By submitting, you confirm the above details are accurate and the customer has given consent for this application.
</Text>
@@ -414,9 +414,8 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
stepLabel: {
fontSize: 10,
textTransform: 'uppercase',
letterSpacing: 0.5,
fontSize: 11,
letterSpacing: 0.1,
},
stepLine: {
flex: 1,
@@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, StyleSheet, ScrollView, Pressable, Alert } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeInUp } from 'react-native-reanimated';
import { ScreenShell, Button } from '../../../components';
@@ -68,7 +68,7 @@ export default function CustomerDetailScreen({ route, navigation }: Props) {
const InfoRow = ({ label, value, icon, masked = false }: { label: string, value: string, icon?: string, masked?: boolean }) => (
<View style={styles.infoRow}>
<View style={styles.infoLabelArea}>
{icon && <Ionicons name={icon as any} size={16} color={theme.colors.textMuted} style={{ marginRight: 6 }} />}
{icon && <Icon name={icon as any} size={16} color={theme.colors.textMuted} style={{ marginRight: 6 }} />}
<Text style={[styles.infoLabel, { color: theme.colors.textMuted }]}>{label}</Text>
</View>
<Text style={[styles.infoValue, { color: theme.colors.textPrimary }]}>
@@ -84,7 +84,7 @@ export default function CustomerDetailScreen({ route, navigation }: Props) {
{/* Profile Header */}
<Animated.View entering={FadeInUp.delay(100)} style={styles.headerCard}>
<View style={styles.profileTopRow}>
<View style={[styles.avatarBig, { backgroundColor: `${theme.colors.primary}15` }]}>
<View style={[styles.avatarBig, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}>
<Text style={[styles.avatarBigText, { color: theme.colors.primary }]}>
{customer.name.charAt(0).toUpperCase()}
</Text>
@@ -92,7 +92,7 @@ export default function CustomerDetailScreen({ route, navigation }: Props) {
<View style={styles.headerInfo}>
<Text style={[styles.headerName, { color: theme.colors.textPrimary }]}>{customer.name}</Text>
<Text style={[styles.headerPhone, { color: theme.colors.textSecondary }]}>{maskPhone(customer.phone)}</Text>
<View style={[styles.statusChip, { backgroundColor: customer.status === 'active' ? `${theme.colors.success}15` : `${theme.colors.danger}15` }]}>
<View style={[styles.statusChip, { backgroundColor: customer.status === 'active' ? theme.tint(theme.colors.success, 0.08) : theme.tint(theme.colors.danger, 0.08) }]}>
<Text style={[styles.statusText, { color: customer.status === 'active' ? theme.colors.success : theme.colors.danger }]}>
{customer.status.toUpperCase()}
</Text>
@@ -102,23 +102,23 @@ export default function CustomerDetailScreen({ route, navigation }: Props) {
<View style={styles.actionButtonsRow}>
<Pressable style={styles.iconBtn} onPress={() => {/* Implement call */}}>
<View style={[styles.iconFrame, { backgroundColor: `${theme.colors.info}15` }]}>
<Ionicons name="call" size={20} color={theme.colors.info} />
<View style={[styles.iconFrame, { backgroundColor: theme.tint(theme.colors.info, 0.08) }]}>
<Icon name="call" size={20} color={theme.colors.info} />
</View>
<Text style={[styles.iconLabel, { color: theme.colors.info }]}>Call</Text>
</Pressable>
<Pressable style={styles.iconBtn} onPress={() => {/* Implement SMS */}}>
<View style={[styles.iconFrame, { backgroundColor: `${theme.colors.success}15` }]}>
<Ionicons name="chatbubble" size={20} color={theme.colors.success} />
<View style={[styles.iconFrame, { backgroundColor: theme.tint(theme.colors.success, 0.08) }]}>
<Icon name="chatbubble" size={20} color={theme.colors.success} />
</View>
<Text style={[styles.iconLabel, { color: theme.colors.success }]}>SMS</Text>
</Pressable>
<FeatureGate feature={FeatureKey.AGENT_CUSTOMERS_EDIT}>
<Pressable style={styles.iconBtn} onPress={() => navigation.navigate('AddEditCustomer', { customerId: customer.id })}>
<View style={[styles.iconFrame, { backgroundColor: `${theme.colors.primary}15` }]}>
<Ionicons name="create" size={20} color={theme.colors.primary} />
<View style={[styles.iconFrame, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}>
<Icon name="create" size={20} color={theme.colors.primary} />
</View>
<Text style={[styles.iconLabel, { color: theme.colors.primary }]}>Edit</Text>
</Pressable>
@@ -189,7 +189,7 @@ export default function CustomerDetailScreen({ route, navigation }: Props) {
variant="ghost"
loading={isDeleting}
onPress={handleDeleteCustomer}
leftAccessory={<Ionicons name="trash-outline" size={18} color={theme.colors.danger} />}
leftAccessory={<Icon name="trash-outline" size={18} color={theme.colors.danger} />}
style={{ borderColor: theme.colors.danger, borderWidth: 1 }}
/>
</View>
@@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeInUp } from 'react-native-reanimated';
import { ScreenShell } from '../../../components';
@@ -46,8 +46,8 @@ export default function CustomerInstallmentHistoryScreen({ route, navigation }:
const renderItem = ({ item, index }: { item: any, index: number }) => (
<Animated.View entering={FadeInUp.delay(index * 50)} style={[styles.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<View style={styles.leftSide}>
<View style={[styles.iconFrame, { backgroundColor: item.status === 'paid' ? `${theme.colors.success}15` : `${theme.colors.warning}15` }]}>
<Ionicons
<View style={[styles.iconFrame, { backgroundColor: item.status === 'paid' ? theme.tint(theme.colors.success, 0.08) : theme.tint(theme.colors.warning, 0.08) }]}>
<Icon
name={item.status === 'paid' ? 'checkmark-circle' : 'time'}
size={20}
color={item.status === 'paid' ? theme.colors.success : theme.colors.warning}
@@ -3,7 +3,7 @@ import { View, StyleSheet, FlatList, RefreshControl, TextInput, Pressable, Text
import { CompositeNavigationProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeIn } from 'react-native-reanimated';
import { ScreenShell } from '../../../components';
@@ -67,7 +67,7 @@ export default function CustomerListScreen({ navigation }: { navigation: Navigat
{/* Search Bar */}
<View style={styles.headerArea}>
<View style={[styles.searchBar, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<Ionicons name="search-outline" size={20} color={theme.colors.textMuted} />
<Icon name="search-outline" size={20} color={theme.colors.textMuted} />
<TextInput
style={[styles.searchInput, { color: theme.colors.textPrimary }]}
placeholder="Search by name or phone..."
@@ -77,7 +77,7 @@ export default function CustomerListScreen({ navigation }: { navigation: Navigat
/>
{searchQuery.length > 0 && (
<Pressable onPress={() => handleSearch('')} style={styles.clearBtn}>
<Ionicons name="close-circle" size={16} color={theme.colors.textMuted} />
<Icon name="close-circle" size={16} color={theme.colors.textMuted} />
</Pressable>
)}
</View>
@@ -167,7 +167,7 @@ export default function CustomerListScreen({ navigation }: { navigation: Navigat
style={[styles.fab, { backgroundColor: theme.colors.primary }]}
onPress={() => navigation.navigate('AddEditCustomer', {})}
>
<Ionicons name="add" size={24} color={theme.colors.white} />
<Icon name="add" size={24} color={theme.colors.white} />
</Pressable>
</FeatureGate>
</ScreenShell>
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, FlatList, RefreshControl, Pressable } from 'rea
import { CompositeNavigationProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeIn, SlideInDown } from 'react-native-reanimated';
import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
@@ -75,13 +75,13 @@ const CollectionForm = ({ selectedEntry, navigation, bottomSheetRef }: { selecte
style={[
styles.modeChip,
{
backgroundColor: mode === m.id ? `${theme.colors.primary}15` : theme.colors.surface,
backgroundColor: mode === m.id ? theme.tint(theme.colors.primary, 0.08) : theme.colors.surface,
borderColor: mode === m.id ? theme.colors.primary : theme.colors.cardBorder,
}
]}
onPress={() => setMode(m.id)}
>
<Ionicons name={m.icon as any} size={16} color={mode === m.id ? theme.colors.primary : theme.colors.textMuted} />
<Icon name={m.icon as any} size={16} color={mode === m.id ? theme.colors.primary : theme.colors.textMuted} />
<Text style={[styles.modeText, { color: mode === m.id ? theme.colors.primary : theme.colors.textMuted }]}>{m.label}</Text>
</Pressable>
))}
@@ -164,7 +164,7 @@ export default function DailyCollectionScreen({ navigation }: { navigation: Navi
{offlineQueue.length > 0 && (
<View style={[styles.offlineBanner, { backgroundColor: theme.colors.warning }]}>
<Ionicons name="cloud-offline" size={16} color="#fff" />
<Icon name="cloud-offline" size={16} color="#fff" />
<Text style={styles.offlineText}>{offlineQueue.length} entries waiting to sync</Text>
</View>
)}
@@ -244,7 +244,7 @@ export default function DailyCollectionScreen({ navigation }: { navigation: Navi
onPress={() => handleEntryPress(item)}
>
<View style={styles.entryLeft}>
<View style={[styles.avatarBox, { backgroundColor: `${theme.colors.primary}15` }]}>
<View style={[styles.avatarBox, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}>
<Text style={[styles.avatarText, { color: theme.colors.primary }]}>{item.customerName.charAt(0)}</Text>
</View>
<View>
@@ -257,7 +257,7 @@ export default function DailyCollectionScreen({ navigation }: { navigation: Navi
{isCollected || isPartial ? (
<>
<Text style={[styles.entryAmount, { color: theme.colors.success }]}>{formatAgentCurrency(item.collectedAmount)}</Text>
<View style={[styles.statusBadge, { backgroundColor: `${theme.colors.success}15` }]}>
<View style={[styles.statusBadge, { backgroundColor: theme.tint(theme.colors.success, 0.08) }]}>
<Text style={[styles.statusText, { color: theme.colors.success }]}>Collected</Text>
</View>
</>
@@ -280,6 +280,13 @@ export default function DailyCollectionScreen({ navigation }: { navigation: Navi
index={-1}
snapPoints={['50%', '65%']}
enablePanDownToClose
// The amount field sits in this sheet. Android's window shrinks for
// the keyboard (adjustResize, restored on 15+ by MainActivity), and the
// sheet has to be told so — its default assumes it must lift itself,
// which on a resized window lifts it twice and off the top.
android_keyboardInputMode="adjustResize"
keyboardBehavior="interactive"
keyboardBlurBehavior="restore"
backgroundStyle={{ backgroundColor: theme.colors.surface }}
handleIndicatorStyle={{ backgroundColor: theme.colors.cardBorder }}
>
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { View, Text, StyleSheet, FlatList, Pressable, RefreshControl } from 'react-native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { SlideInRight } from 'react-native-reanimated';
import { useTheme, fontFamily } from '../../../theme';
@@ -32,10 +32,10 @@ export default function LoanRequestListScreen({ navigation }: { navigation: Nati
<View style={styles.headerRow}>
<Text style={[styles.title, { color: theme.colors.textPrimary }]}>Recent Requests</Text>
<Pressable
style={[styles.newBtn, { backgroundColor: `${theme.colors.primary}15` }]}
style={[styles.newBtn, { backgroundColor: theme.tint(theme.colors.primary, 0.08) }]}
onPress={() => navigation.navigate('CreateLoanRequest', {})}
>
<Ionicons name="add" size={16} color={theme.colors.primary} />
<Icon name="add" size={16} color={theme.colors.primary} />
<Text style={[styles.newBtnText, { color: theme.colors.primary }]}>New Request</Text>
</Pressable>
</View>
@@ -70,7 +70,7 @@ export default function LoanRequestListScreen({ navigation }: { navigation: Nati
<Text style={[styles.customerName, { color: theme.colors.textPrimary }]}>{item.customerName}</Text>
<Text style={[styles.category, { color: theme.colors.textMuted }]}>{item.category} Loan</Text>
</View>
<View style={[styles.statusBadge, { backgroundColor: `${statusColor}15` }]}>
<View style={[styles.statusBadge, { backgroundColor: theme.tint(statusColor, 0.08) }]}>
<Text style={[styles.statusText, { color: statusColor }]}>{item.status.replace('_', ' ').toUpperCase()}</Text>
</View>
</View>
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { View, StyleSheet, FlatList, RefreshControl, Text } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { Icon } from '../../../components/Icon';
import Animated, { FadeInUp } from 'react-native-reanimated';
import { ScreenShell } from '../../../components';
@@ -59,11 +59,11 @@ export default function SmsHistoryScreen({ navigation }: Props) {
<Animated.View entering={FadeInUp.delay(index * 50)} style={[styles.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
<View style={styles.cardHeader}>
<View style={styles.headerLeft}>
<Ionicons name="chatbubble-ellipses" size={16} color={theme.colors.primary} />
<Icon name="chatbubble-ellipses" size={16} color={theme.colors.primary} />
<Text style={[styles.phone, { color: theme.colors.textPrimary }]}>{item.customerPhone}</Text>
</View>
<View style={styles.headerRight}>
<Ionicons name={statusIcon} size={14} color={statusColor} />
<Icon name={statusIcon} size={14} color={statusColor} />
<Text style={[styles.statusText, { color: statusColor }]}>{item.status.toUpperCase()}</Text>
</View>
</View>