Files
CBS-Mobile-App/src/modules/services/screens/ServicesHubScreen.tsx
T
Abin ToppoandClaude Opus 4.8 58af541536 Add global search, in-app + push notifications, and fix card/beneficiary/scroll issues
- Global feature search for the user app, wired to the home search icon
- Notifications screen: swipe-to-delete, vanish animation, unread badge,
  and Notifee-backed foreground + background (scheduled) OS notifications
- Fix Add Beneficiary save (single source of truth via beneficiaries slice)
- Fix Cards tab scroll freeze and bottom blank space (use ScreenShell scroll)
- User-friendly toast copy across the app
- Android build tuning: parallel/caching/JVM G1 args, mobile-only ABIs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 16:36:27 +05:30

261 lines
7.3 KiB
TypeScript

import React, { useMemo } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import type { CompositeScreenProps } from '@react-navigation/native';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import Ionicons from '@react-native-vector-icons/ionicons';
import { useAppSelector } from '../../../app/hooks';
import { Card, ScreenShell } from '../../../components';
import { useToast } from '../../../hooks/useToast';
import { ServiceGridItem } from '../../home/components/ServiceGridItem';
import { ServiceStatusPill } from '../components/ServiceStatusPill';
import { selectServiceRequests } from '../store/serviceSelectors';
import type { MainTabParamList, ServicesStackParamList } from '../../../navigation/types';
import { useTheme } from '../../../theme';
type Props = CompositeScreenProps<
NativeStackScreenProps<ServicesStackParamList, 'ServicesHub'>,
BottomTabScreenProps<MainTabParamList, 'Services'>
>;
export default function ServicesHubScreen({ navigation }: Props) {
const theme = useTheme();
const toast = useToast();
const serviceRequests = useAppSelector(selectServiceRequests);
const latestRequest = serviceRequests[0] ?? null;
const openRequestsCount = serviceRequests.filter(
(request) => request.status !== 'resolved',
).length;
const serviceOptions = useMemo(
() => [
{
id: 'branch_locator',
title: 'Find Branch & ATMs',
icon: 'map-outline',
iconColor: '#10B981',
onPress: () => navigation.navigate('BranchLocator'),
},
{
id: 'all_deposits',
title: 'Deposits',
icon: 'wallet-outline',
iconColor: '#124577',
onPress: () => navigation.navigate('DepositsOverview'),
},
{
id: 'cheque_services',
title: 'Cheque Services',
icon: 'book-outline',
iconColor: '#3B82F6',
onPress: () => navigation.navigate('ChequeServicesHub'),
},
{
id: 'certificates',
title: 'Certificates',
icon: 'document-text-outline',
iconColor: '#F59E0B',
onPress: () => navigation.navigate('CertificatesHub'),
},
{
id: 'requests',
title: 'Service Requests',
icon: 'git-pull-request-outline',
iconColor: '#EF4444',
onPress: () => navigation.navigate('ServiceRequestsHub'),
},
{
id: 'calculator',
title: 'FD/RD Calc',
icon: 'calculator-outline',
iconColor: '#6366F1',
onPress: () => navigation.navigate('DepositCalculator'),
},
{
id: 'support',
title: 'Support Chat',
icon: 'chatbubble-ellipses-outline',
iconColor: '#0EA5E9',
onPress: () =>
toast(
'Support chat coming soon',
'info',
'This feature will be available in the next update.',
),
},
{
id: 'help',
title: 'Help Center',
icon: 'help-circle-outline',
iconColor: '#8B5CF6',
onPress: () =>
toast('Help center coming soon', 'info', 'This feature will be available shortly.'),
},
{
id: 'cyber_security',
title: 'Cyber Security',
icon: 'shield-half-outline',
iconColor: '#F59E0B',
onPress: () => navigation.navigate('AwarenessList'),
},
{
id: 'protection_schemes',
title: 'Protection & Schemes',
icon: 'umbrella-outline',
iconColor: '#EC4899',
onPress: () => navigation.navigate('FinancialProtectionMain'),
},
],
[navigation, toast]
);
return (
<ScreenShell title="Service desk" subtitle="All service options in one hub">
{/* <View style={styles.sectionHeader}>
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
Service options
</Text>
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
Choose a service card to continue
</Text>
</View> */}
<View style={styles.grid}>
{serviceOptions.map((option) => (
<ServiceGridItem
key={option.id}
id={option.id}
title={option.title}
icon={option.icon}
iconColor={option.iconColor}
onPress={option.onPress}
/>
))}
</View>
<Card variant="glass" style={styles.heroCard}>
<View style={styles.heroHeader}>
<View
style={[
styles.heroIcon,
{ backgroundColor: `${theme.colors.primary}12` },
]}
>
<Ionicons name="grid-outline" size={18} color={theme.colors.primary} />
</View>
<View style={styles.heroCopy}>
<Text style={[styles.heroTitle, { color: theme.colors.textPrimary }]}>
Services hub
</Text>
<Text style={[styles.heroSubtitle, { color: theme.colors.textSecondary }]}>
Browse deposit journeys, service support actions, and request tools from one place.
</Text>
</View>
</View>
<View style={styles.heroStats}>
<View style={[styles.heroStat, { backgroundColor: theme.colors.surfaceMuted }]}>
<Text style={[styles.heroStatLabel, { color: theme.colors.textSecondary }]}>
Open requests
</Text>
<Text style={[styles.heroStatValue, { color: theme.colors.textPrimary }]}>
{openRequestsCount}
</Text>
</View>
<View style={[styles.heroStat, { backgroundColor: theme.colors.surfaceMuted }]}>
<Text style={[styles.heroStatLabel, { color: theme.colors.textSecondary }]}>
Latest status
</Text>
{latestRequest ? (
<ServiceStatusPill status={latestRequest.status} />
) : (
<Text style={[styles.heroStatEmpty, { color: theme.colors.textMuted }]}>
No requests
</Text>
)}
</View>
</View>
</Card>
<View style={styles.bottomSpacer} />
</ScreenShell>
);
}
const styles = StyleSheet.create({
heroCard: {
gap: 16,
shadowOpacity: 0.04,
shadowRadius: 14,
elevation: 2,
},
heroHeader: {
flexDirection: 'row',
gap: 12,
},
heroIcon: {
width: 42,
height: 42,
borderRadius: 14,
alignItems: 'center',
justifyContent: 'center',
},
heroCopy: {
flex: 1,
gap: 4,
},
heroTitle: {
fontSize: 18,
fontWeight: '800',
},
heroSubtitle: {
fontSize: 13,
lineHeight: 18,
},
heroStats: {
flexDirection: 'row',
gap: 12,
},
heroStat: {
flex: 1,
borderRadius: 18,
padding: 14,
gap: 6,
},
heroStatLabel: {
fontSize: 11,
fontWeight: '700',
textTransform: 'uppercase',
letterSpacing: 0.8,
},
heroStatValue: {
fontSize: 22,
fontWeight: '900',
letterSpacing: -0.5,
},
heroStatEmpty: {
fontSize: 13,
fontWeight: '600',
},
sectionHeader: {
gap: 4,
},
sectionTitle: {
fontSize: 18,
fontWeight: '800',
},
sectionSubtitle: {
fontSize: 13,
lineHeight: 18,
},
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
bottomSpacer: {
height: 60,
},
});