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:
co-authored by
Claude Opus 5.5
parent
5fbd13ba12
commit
f960a119e8
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user