Fix form keyboard issues and sticky footers in agent screens
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import React from 'react';
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import { Card } from '../../../components';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { AwarenessArticle } from '../types';
|
||||
|
||||
interface AwarenessCardProps {
|
||||
article: AwarenessArticle;
|
||||
isBookmarked: boolean;
|
||||
onToggleBookmark: () => void;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
export const AwarenessCard = ({
|
||||
article,
|
||||
isBookmarked,
|
||||
onToggleBookmark,
|
||||
onPress,
|
||||
}: AwarenessCardProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Pressable onPress={onPress}>
|
||||
<Card style={styles.card}>
|
||||
<View style={styles.header}>
|
||||
<View style={[styles.iconFrame, { backgroundColor: `${theme.colors.primary}12` }]}>
|
||||
<Ionicons name={article.icon as any} size={22} color={theme.colors.primary} />
|
||||
</View>
|
||||
<View style={styles.headerInfo}>
|
||||
<View style={styles.titleRow}>
|
||||
<Text style={[styles.category, { color: theme.colors.primary }]}>
|
||||
{article.category}
|
||||
</Text>
|
||||
{article.isUrgent && (
|
||||
<View style={[styles.urgentBadge, { backgroundColor: `${theme.colors.error}15` }]}>
|
||||
<Text style={[styles.urgentText, { color: theme.colors.error }]}>URGENT</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.title, { color: theme.colors.textPrimary }]} numberOfLines={1}>
|
||||
{article.title}
|
||||
</Text>
|
||||
</View>
|
||||
<Pressable hitSlop={10} onPress={onToggleBookmark} style={styles.bookmarkBtn}>
|
||||
<Ionicons
|
||||
name={isBookmarked ? 'bookmark' : 'bookmark-outline'}
|
||||
size={22}
|
||||
color={isBookmarked ? theme.colors.primary : theme.colors.textMuted}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.description, { color: theme.colors.textSecondary }]} numberOfLines={2}>
|
||||
{article.description}
|
||||
</Text>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<View style={styles.tags}>
|
||||
{article.tags.slice(0, 2).map((tag) => (
|
||||
<View key={tag} style={[styles.tag, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.tagText, { color: theme.colors.textSecondary }]}>#{tag}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.readMore}>
|
||||
<Text style={[styles.readMoreText, { color: theme.colors.primary }]}>Read More</Text>
|
||||
<Ionicons name="chevron-forward" size={14} color={theme.colors.primary} />
|
||||
</View>
|
||||
</View>
|
||||
</Card>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
gap: 12,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
alignItems: 'center',
|
||||
},
|
||||
iconFrame: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 14,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
headerInfo: {
|
||||
flex: 1,
|
||||
gap: 2,
|
||||
},
|
||||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
category: {
|
||||
fontSize: 11,
|
||||
fontWeight: '800',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
urgentBadge: {
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
},
|
||||
urgentText: {
|
||||
fontSize: 9,
|
||||
fontWeight: '900',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
},
|
||||
bookmarkBtn: {
|
||||
padding: 4,
|
||||
},
|
||||
description: {
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
},
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 4,
|
||||
},
|
||||
tags: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
tag: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 8,
|
||||
},
|
||||
tagText: {
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
},
|
||||
readMore: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
readMoreText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import { AwarenessArticle } from '../types';
|
||||
|
||||
export const awarenessArticles: AwarenessArticle[] = [
|
||||
{
|
||||
id: 'art_001',
|
||||
title: 'Recognizing UPI QR Code Scams',
|
||||
description: 'Learn how scammers use fake QR codes to steal money from your account.',
|
||||
content: `UPI QR codes are meant for making payments, not receiving them. Scammers often send QR codes to victims claiming they will receive money by scanning it.
|
||||
|
||||
Key Safety Tips:
|
||||
1. Scanning a QR code always means money will be deducted from YOUR account.
|
||||
2. You NEVER need to scan a QR code or enter your UPI PIN to RECEIVE money.
|
||||
3. If someone asks you to scan a QR code to "collect prize money," it's a scam.
|
||||
4. Always verify the merchant name displayed in the UPI app before confirming a payment.`,
|
||||
category: 'NPCI',
|
||||
tags: ['UPI', 'Scam'],
|
||||
icon: 'qr-code-outline',
|
||||
publishDate: '2026-04-10',
|
||||
isUrgent: true,
|
||||
},
|
||||
{
|
||||
id: 'art_002',
|
||||
title: 'Protecting Your OTP',
|
||||
description: 'Why you should never share your One Time Password with anyone.',
|
||||
content: `One Time Passwords (OTPs) are the final barrier protecting your transactions. No bank official will ever ask for your OTP over the phone or via email.
|
||||
|
||||
Safe Practices:
|
||||
- Never share OTP, CVV, or PIN with anyone, including bank staff.
|
||||
- Ensure your mobile number is updated with the bank to receive alerts.
|
||||
- Do not click on links in SMS that ask for your OTP.
|
||||
- Use biometric authentication where available for added security.`,
|
||||
category: 'RBI',
|
||||
tags: ['OTP', 'Scam'],
|
||||
icon: 'shield-checkmark-outline',
|
||||
publishDate: '2026-04-08',
|
||||
},
|
||||
{
|
||||
id: 'art_003',
|
||||
title: 'Fake Call/Vishing Alerts',
|
||||
description: 'How to handle calls from individuals pretending to be bank employees.',
|
||||
content: `Vishing (voice phishing) is a common method where scammers call you pretending to be from your bank's KYC or security department.
|
||||
|
||||
How to identify Vishing:
|
||||
1. Sense of Urgency: They claim your account will be blocked immediately.
|
||||
2. Asking for Sensitive Info: They ask for PIN, OTP, or passwords.
|
||||
3. Call from unknown mobile numbers: Banks usually call from official numbers.
|
||||
|
||||
What to do:
|
||||
- Hang up immediately if you feel suspicious.
|
||||
- Call your bank's official customer care number to verify.
|
||||
- Report the number on the Cyber Crime portal.`,
|
||||
category: 'Fraud Alert',
|
||||
tags: ['Phishing', 'Scam'],
|
||||
icon: 'call-outline',
|
||||
publishDate: '2026-04-12',
|
||||
isUrgent: true,
|
||||
},
|
||||
{
|
||||
id: 'art_004',
|
||||
title: 'Safe Online Banking Habits',
|
||||
description: 'Essential practices for secure net banking and mobile app usage.',
|
||||
content: `Securing your digital footprint is vital in modern banking. Follow these RBI-recommended practices.
|
||||
|
||||
Checklist:
|
||||
- Use strong, unique passwords for banking apps.
|
||||
- Enable Two-Factor Authentication (2FA).
|
||||
- Avoid using public Wi-Fi for banking transactions.
|
||||
- Always log out after your session.
|
||||
- Keep your banking app updated to the latest version.`,
|
||||
category: 'Best Practices',
|
||||
tags: ['Net Banking', 'Scam'],
|
||||
icon: 'globe-outline',
|
||||
publishDate: '2026-04-05',
|
||||
},
|
||||
{
|
||||
id: 'art_005',
|
||||
title: 'NPCI: UPI Safety Shield',
|
||||
description: 'NPCI’s official guide to safe digital payments.',
|
||||
content: `NPCI encourages users to be "UPI Chalak" (UPI Smart).
|
||||
|
||||
Security Features:
|
||||
- PIN Entry: Required only for sending money or checking balance.
|
||||
- Spam Filter: UPI apps now highlight suspicious payment requests.
|
||||
- Transaction Limits: Set daily limits for UPI transactions in your bank app.
|
||||
- Block & Report: Use the "Report" feature for fraudulent IDs.`,
|
||||
category: 'NPCI',
|
||||
tags: ['UPI'],
|
||||
icon: 'flash-outline',
|
||||
publishDate: '2026-04-01',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { awarenessArticles } from '../constants/mockData';
|
||||
import { AwarenessArticle, AwarenessFilter } from '../types';
|
||||
|
||||
const BOOKMARKS_KEY = '@awareness_bookmarks';
|
||||
|
||||
export const useAwareness = () => {
|
||||
const [articles] = useState<AwarenessArticle[]>(awarenessArticles);
|
||||
const [bookmarks, setBookmarks] = useState<string[]>([]);
|
||||
const [filter, setFilter] = useState<AwarenessFilter>({
|
||||
category: 'All',
|
||||
tag: 'All',
|
||||
searchQuery: '',
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Load bookmarks on mount
|
||||
useEffect(() => {
|
||||
const loadBookmarks = async () => {
|
||||
try {
|
||||
const saved = await AsyncStorage.getItem(BOOKMARKS_KEY);
|
||||
if (saved) {
|
||||
setBookmarks(JSON.parse(saved));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load bookmarks', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadBookmarks();
|
||||
}, []);
|
||||
|
||||
// Save bookmarks when changed
|
||||
const toggleBookmark = useCallback(async (id: string) => {
|
||||
try {
|
||||
const newBookmarks = bookmarks.includes(id)
|
||||
? bookmarks.filter((b) => b !== id)
|
||||
: [...bookmarks, id];
|
||||
|
||||
setBookmarks(newBookmarks);
|
||||
await AsyncStorage.setItem(BOOKMARKS_KEY, JSON.stringify(newBookmarks));
|
||||
} catch (error) {
|
||||
console.error('Failed to update bookmarks', error);
|
||||
}
|
||||
}, [bookmarks]);
|
||||
|
||||
const filteredArticles = useMemo(() => {
|
||||
return articles.filter((article) => {
|
||||
const matchesCategory = filter.category === 'All' || article.category === filter.category;
|
||||
const matchesTag = filter.tag === 'All' || article.tags.includes(filter.tag as any);
|
||||
const matchesSearch =
|
||||
article.title.toLowerCase().includes(filter.searchQuery.toLowerCase()) ||
|
||||
article.description.toLowerCase().includes(filter.searchQuery.toLowerCase()) ||
|
||||
article.content.toLowerCase().includes(filter.searchQuery.toLowerCase());
|
||||
|
||||
return matchesCategory && matchesTag && matchesSearch;
|
||||
});
|
||||
}, [articles, filter]);
|
||||
|
||||
const urgentArticles = useMemo(() => {
|
||||
return articles.filter((a) => a.isUrgent);
|
||||
}, [articles]);
|
||||
|
||||
const bookmarkedArticles = useMemo(() => {
|
||||
return articles.filter((a) => bookmarks.includes(a.id));
|
||||
}, [articles, bookmarks]);
|
||||
|
||||
return {
|
||||
articles: filteredArticles,
|
||||
urgentArticles,
|
||||
bookmarkedArticles,
|
||||
bookmarks,
|
||||
toggleBookmark,
|
||||
isLoading,
|
||||
filter,
|
||||
setFilter,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,246 @@
|
||||
import React from 'react';
|
||||
import { Share, StyleSheet, Text, View, Pressable } from 'react-native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
|
||||
import { Card, ScreenShell } from '../../../components';
|
||||
import { ServicesStackParamList } from '../../../navigation/types';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useAwareness } from '../hooks/useAwareness';
|
||||
|
||||
type Props = NativeStackScreenProps<ServicesStackParamList, 'AwarenessDetail'>;
|
||||
|
||||
export default function AwarenessDetailScreen({ route, navigation }: Props) {
|
||||
const { articleId } = route.params;
|
||||
const theme = useTheme();
|
||||
const { articles, bookmarks, toggleBookmark } = useAwareness();
|
||||
|
||||
const article = articles.find((a) => a.id === articleId);
|
||||
const isBookmarked = bookmarks.includes(articleId);
|
||||
|
||||
if (!article) {
|
||||
return (
|
||||
<ScreenShell title="Not Found">
|
||||
<View style={styles.errorContainer}>
|
||||
<Text style={{ color: theme.colors.textPrimary }}>Article not found.</Text>
|
||||
</View>
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
const handleShare = async () => {
|
||||
try {
|
||||
await Share.share({
|
||||
message: `${article.title}\n\n${article.description}\n\nStay safe with BankCBS!`,
|
||||
title: article.title,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sharing article', error);
|
||||
}
|
||||
};
|
||||
|
||||
const rightContent = (
|
||||
<View style={styles.headerActions}>
|
||||
<Pressable onPress={handleShare} style={[styles.actionBtn, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Ionicons name="share-social-outline" size={20} color={theme.colors.textPrimary} />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={() => toggleBookmark(articleId)}
|
||||
style={[styles.actionBtn, { backgroundColor: isBookmarked ? `${theme.colors.primary}15` : theme.colors.surfaceMuted }]}
|
||||
>
|
||||
<Ionicons
|
||||
name={isBookmarked ? 'bookmark' : 'bookmark-outline'}
|
||||
size={20}
|
||||
color={isBookmarked ? theme.colors.primary : theme.colors.textPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenShell
|
||||
title="Article"
|
||||
subtitle={article.category}
|
||||
rightContent={rightContent}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.metaRow}>
|
||||
<View style={[styles.categoryBadge, { backgroundColor: `${theme.colors.primary}12` }]}>
|
||||
<Text style={[styles.categoryText, { color: theme.colors.primary }]}>{article.category}</Text>
|
||||
</View>
|
||||
<Text style={[styles.dateText, { color: theme.colors.textMuted }]}>{article.publishDate}</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.title, { color: theme.colors.textPrimary }]}>{article.title}</Text>
|
||||
|
||||
<View style={styles.tagsRow}>
|
||||
{article.tags.map(tag => (
|
||||
<View key={tag} style={[styles.tag, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.tagText, { color: theme.colors.textSecondary }]}>#{tag}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<Card variant="glass" style={styles.introCard}>
|
||||
<Text style={[styles.description, { color: theme.colors.textSecondary }]}>
|
||||
{article.description}
|
||||
</Text>
|
||||
</Card>
|
||||
|
||||
<View style={styles.contentSection}>
|
||||
<Text style={[styles.contentText, { color: theme.colors.textPrimary }]}>
|
||||
{article.content}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.safetyCta}>
|
||||
<View style={[styles.ctaIcon, { backgroundColor: `${theme.colors.success}15` }]}>
|
||||
<Ionicons name="shield-checkmark-outline" size={24} color={theme.colors.success} />
|
||||
</View>
|
||||
<View style={styles.ctaContent}>
|
||||
<Text style={[styles.ctaTitle, { color: theme.colors.textPrimary }]}>Stay Protected</Text>
|
||||
<Text style={[styles.ctaSubtitle, { color: theme.colors.textSecondary }]}>
|
||||
BankCBS never asks for your OTP, PIN, or CVV. Report any suspicious activity immediately.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.footerActions}>
|
||||
<Pressable
|
||||
onPress={() => navigation.navigate('ServiceRequestsHub')}
|
||||
style={[styles.reportBtn, { borderColor: theme.colors.cardBorder }]}
|
||||
>
|
||||
<Ionicons name="megaphone-outline" size={18} color={theme.colors.error} />
|
||||
<Text style={[styles.reportText, { color: theme.colors.error }]}>Report Fraudulent Activity</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ height: 20 }} />
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 40,
|
||||
},
|
||||
headerActions: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
actionBtn: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
metaRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
categoryBadge: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 8,
|
||||
},
|
||||
categoryText: {
|
||||
fontSize: 11,
|
||||
fontWeight: '800',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
dateText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: '800',
|
||||
letterSpacing: -0.5,
|
||||
lineHeight: 30,
|
||||
marginBottom: 12,
|
||||
},
|
||||
tagsRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
marginBottom: 20,
|
||||
},
|
||||
tag: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 10,
|
||||
},
|
||||
tagText: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
},
|
||||
introCard: {
|
||||
padding: 16,
|
||||
marginBottom: 24,
|
||||
},
|
||||
description: {
|
||||
fontSize: 15,
|
||||
lineHeight: 22,
|
||||
fontWeight: '500',
|
||||
},
|
||||
contentSection: {
|
||||
marginBottom: 32,
|
||||
},
|
||||
contentText: {
|
||||
fontSize: 16,
|
||||
lineHeight: 26,
|
||||
},
|
||||
safetyCta: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: 'rgba(52, 211, 153, 0.05)',
|
||||
padding: 20,
|
||||
borderRadius: 20,
|
||||
gap: 16,
|
||||
marginBottom: 32,
|
||||
},
|
||||
ctaIcon: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
ctaContent: {
|
||||
flex: 1,
|
||||
gap: 4,
|
||||
},
|
||||
ctaTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
},
|
||||
ctaSubtitle: {
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
},
|
||||
footerActions: {
|
||||
gap: 12,
|
||||
},
|
||||
reportBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 10,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 16,
|
||||
borderWidth: 1,
|
||||
borderStyle: 'dashed',
|
||||
},
|
||||
reportText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
},
|
||||
errorContainer: {
|
||||
padding: 20,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,215 @@
|
||||
import React from 'react';
|
||||
import { FlatList, ScrollView, StyleSheet, Text, View, Pressable } from 'react-native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
|
||||
import { Input, ScreenShell } from '../../../components';
|
||||
import { ServicesStackParamList } from '../../../navigation/types';
|
||||
import { useTheme } 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 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={<Ionicons 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}>
|
||||
<Ionicons 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.colors.error}08`, borderColor: `${theme.colors.error}20` }]}
|
||||
>
|
||||
<View style={[styles.urgentIcon, { backgroundColor: `${theme.colors.error}15` }]}>
|
||||
<Ionicons 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}>
|
||||
<Ionicons 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,
|
||||
fontWeight: '700',
|
||||
},
|
||||
listContent: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 100,
|
||||
},
|
||||
urgentSection: {
|
||||
marginBottom: 24,
|
||||
gap: 12,
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
marginBottom: 12,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
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,
|
||||
fontWeight: '700',
|
||||
},
|
||||
urgentCardDesc: {
|
||||
fontSize: 12,
|
||||
},
|
||||
emptyContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 60,
|
||||
gap: 12,
|
||||
},
|
||||
emptyTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
},
|
||||
emptySubtitle: {
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 40,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
export type AwarenessCategory = 'RBI' | 'NPCI' | 'Fraud Alert' | 'Best Practices';
|
||||
export type AwarenessTag = 'UPI' | 'Cards' | 'Net Banking' | 'OTP' | 'Phishing' | 'Scam';
|
||||
|
||||
export interface AwarenessArticle {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
category: AwarenessCategory;
|
||||
tags: AwarenessTag[];
|
||||
icon: string;
|
||||
imageUrl?: string;
|
||||
publishDate: string;
|
||||
isUrgent?: boolean;
|
||||
}
|
||||
|
||||
export type AwarenessFilter = {
|
||||
category: AwarenessCategory | 'All';
|
||||
tag: AwarenessTag | 'All';
|
||||
searchQuery: string;
|
||||
};
|
||||
Reference in New Issue
Block a user