- 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>
219 lines
7.2 KiB
TypeScript
219 lines
7.2 KiB
TypeScript
import React from 'react';
|
|
import { FlatList, ScrollView, StyleSheet, Text, View, Pressable } from 'react-native';
|
|
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
import { Icon } from '../../../components/Icon';
|
|
|
|
import { Input, ScreenShell } from '../../../components';
|
|
import { ServicesStackParamList } from '../../../navigation/types';
|
|
import { useTheme, fontFamily } from '../../../theme';
|
|
import { AwarenessCard } from '../components/AwarenessCard';
|
|
import { useAwareness } from '../hooks/useAwareness';
|
|
import { AwarenessCategory } from '../types';
|
|
|
|
type Props = NativeStackScreenProps<ServicesStackParamList, 'AwarenessList'>;
|
|
|
|
const CATEGORIES: (AwarenessCategory | 'All')[] = ['All', 'RBI', 'NPCI', 'Fraud Alert', 'Best Practices'];
|
|
|
|
export default function AwarenessListScreen({ navigation }: Props) {
|
|
const theme = useTheme();
|
|
const { articles, filter, setFilter, bookmarks, toggleBookmark, urgentArticles } = useAwareness();
|
|
|
|
const handleSearch = (query: string) => {
|
|
setFilter({ ...filter, searchQuery: query });
|
|
};
|
|
|
|
const handleCategoryPress = (category: AwarenessCategory | 'All') => {
|
|
setFilter({ ...filter, category });
|
|
};
|
|
|
|
return (
|
|
<ScreenShell
|
|
// The body is a FlatList, so it owns the scrolling.
|
|
scrollable={false}
|
|
title="Security aware" subtitle="Learn to protect your digital banking">
|
|
<View style={styles.searchContainer}>
|
|
<Input
|
|
placeholder="Search safety tips, fraud alerts..."
|
|
value={filter.searchQuery}
|
|
onChangeText={handleSearch}
|
|
leftAccessory={<Icon name="search-outline" size={20} color={theme.colors.textMuted} />}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.filterWrapper}>
|
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.filterContainer}>
|
|
{CATEGORIES.map((cat) => {
|
|
const isActive = filter.category === cat;
|
|
return (
|
|
<Pressable
|
|
key={cat}
|
|
onPress={() => handleCategoryPress(cat)}
|
|
style={[
|
|
styles.filterChip,
|
|
{
|
|
backgroundColor: isActive ? theme.colors.primary : theme.colors.surfaceMuted,
|
|
borderColor: isActive ? theme.colors.primary : theme.colors.cardBorder,
|
|
},
|
|
]}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.filterText,
|
|
{ color: isActive ? theme.colors.white : theme.colors.textSecondary },
|
|
]}
|
|
>
|
|
{cat}
|
|
</Text>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</ScrollView>
|
|
</View>
|
|
|
|
<FlatList
|
|
data={articles}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={({ item }) => (
|
|
<AwarenessCard
|
|
article={item}
|
|
isBookmarked={bookmarks.includes(item.id)}
|
|
onToggleBookmark={() => toggleBookmark(item.id)}
|
|
onPress={() => navigation.navigate('AwarenessDetail', { articleId: item.id })}
|
|
/>
|
|
)}
|
|
ListHeaderComponent={
|
|
<>
|
|
{urgentArticles.length > 0 && filter.category === 'All' && filter.searchQuery === '' && (
|
|
<View style={styles.urgentSection}>
|
|
<View style={styles.sectionHeader}>
|
|
<Icon name="alert-circle" size={18} color={theme.colors.error} />
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
Critical Security Alerts
|
|
</Text>
|
|
</View>
|
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.urgentCarousel}>
|
|
{urgentArticles.map((article) => (
|
|
<Pressable
|
|
key={article.id}
|
|
onPress={() => navigation.navigate('AwarenessDetail', { articleId: article.id })}
|
|
style={[styles.urgentCard, { backgroundColor: theme.tint(theme.colors.error, 0.03), borderColor: theme.tint(theme.colors.error, 0.13) }]}
|
|
>
|
|
<View style={[styles.urgentIcon, { backgroundColor: theme.tint(theme.colors.error, 0.08) }]}>
|
|
<Icon name={article.icon as any} size={20} color={theme.colors.error} />
|
|
</View>
|
|
<Text style={[styles.urgentCardTitle, { color: theme.colors.textPrimary }]} numberOfLines={1}>
|
|
{article.title}
|
|
</Text>
|
|
<Text style={[styles.urgentCardDesc, { color: theme.colors.textSecondary }]} numberOfLines={1}>
|
|
{article.description}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</ScrollView>
|
|
</View>
|
|
)}
|
|
<View style={styles.sectionHeader}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
{filter.category === 'All' ? 'Latest Awareness Articles' : `${filter.category} Guidelines`}
|
|
</Text>
|
|
</View>
|
|
</>
|
|
}
|
|
contentContainerStyle={styles.listContent}
|
|
ListEmptyComponent={
|
|
<View style={styles.emptyContainer}>
|
|
<Icon name="search-outline" size={48} color={theme.colors.textMuted} />
|
|
<Text style={[styles.emptyTitle, { color: theme.colors.textPrimary }]}>No results found</Text>
|
|
<Text style={[styles.emptySubtitle, { color: theme.colors.textSecondary }]}>
|
|
Try adjusting your search or filters to find what you're looking for.
|
|
</Text>
|
|
</View>
|
|
}
|
|
scrollEnabled={false} // Since it's inside ScreenShell's ScrollView
|
|
/>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
searchContainer: {
|
|
paddingHorizontal: 16,
|
|
marginBottom: 16,
|
|
},
|
|
filterWrapper: {
|
|
marginBottom: 20,
|
|
},
|
|
filterContainer: {
|
|
paddingHorizontal: 16,
|
|
gap: 10,
|
|
},
|
|
filterChip: {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 8,
|
|
borderRadius: 20,
|
|
borderWidth: 1,
|
|
},
|
|
filterText: {
|
|
fontSize: 13,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
listContent: {
|
|
paddingHorizontal: 16,
|
|
paddingBottom: 100,
|
|
},
|
|
urgentSection: {
|
|
marginBottom: 24,
|
|
gap: 12,
|
|
},
|
|
sectionHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
marginBottom: 12,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 16,
|
|
fontFamily: fontFamily.bold,
|
|
letterSpacing: -0.3,
|
|
},
|
|
urgentCarousel: {
|
|
gap: 12,
|
|
},
|
|
urgentCard: {
|
|
width: 260,
|
|
padding: 16,
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
gap: 8,
|
|
},
|
|
urgentIcon: {
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 10,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
urgentCardTitle: {
|
|
fontSize: 15,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
urgentCardDesc: {
|
|
fontSize: 12,
|
|
},
|
|
emptyContainer: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 60,
|
|
gap: 12,
|
|
},
|
|
emptyTitle: {
|
|
fontSize: 18,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
emptySubtitle: {
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
paddingHorizontal: 40,
|
|
},
|
|
});
|