Files
CBS-Mobile-App/src/modules/awareness/screens/AwarenessListScreen.tsx
T
Abin ToppoandClaude Opus 5.5 f960a119e8 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>
2026-10-14 18:18:40 +05:30

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,
},
});