Fix form keyboard issues and sticky footers in agent screens
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
import React from 'react';
|
||||
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
|
||||
import { Button, Card, EmptyState, Loader, ScreenShell } from '../../../components';
|
||||
import { useToast } from '../../../hooks/useToast';
|
||||
import type { InsuranceStackParamList } from '../../../navigation/types';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { formatCompactCurrency, formatCurrency, formatDate } from '../../../utils/formatters';
|
||||
import { InsuranceStatusPill } from '../components/InsuranceStatusPill';
|
||||
import { useGetInsurancePolicyDetailQuery } from '../services/insuranceApi';
|
||||
|
||||
type Props = NativeStackScreenProps<InsuranceStackParamList, 'InsurancePolicyDetails'>;
|
||||
|
||||
export default function InsurancePolicyDetailsScreen({ navigation, route }: Props) {
|
||||
const theme = useTheme();
|
||||
const toast = useToast();
|
||||
const { data, isLoading, isError, refetch } = useGetInsurancePolicyDetailQuery(
|
||||
route.params.policyId,
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenShell title="Policy Details" subtitle="Coverage, insurer, and servicing actions">
|
||||
{isLoading ? (
|
||||
<Card>
|
||||
<Loader />
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{isError || !data ? (
|
||||
<EmptyState
|
||||
title="Unable to load policy"
|
||||
description="Please retry in a moment."
|
||||
actionLabel="Retry"
|
||||
onAction={refetch}
|
||||
/>
|
||||
) : (
|
||||
<ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}>
|
||||
<Card style={styles.heroCard}>
|
||||
<View style={styles.heroHeader}>
|
||||
<View style={styles.heroCopy}>
|
||||
<Text style={[styles.heroTitle, { color: theme.colors.textPrimary }]}>
|
||||
{data.planName}
|
||||
</Text>
|
||||
<Text style={[styles.heroMeta, { color: theme.colors.textSecondary }]}>
|
||||
{data.policyNumber} · {data.insurer.name}
|
||||
</Text>
|
||||
</View>
|
||||
<InsuranceStatusPill status={data.status} />
|
||||
</View>
|
||||
|
||||
<View style={styles.metricGrid}>
|
||||
<View style={[styles.metricCard, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
|
||||
Coverage
|
||||
</Text>
|
||||
<Text style={[styles.metricValue, { color: theme.colors.textPrimary }]}>
|
||||
{formatCompactCurrency(data.coverageAmount)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.metricCard, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
|
||||
Premium
|
||||
</Text>
|
||||
<Text style={[styles.metricValue, { color: theme.colors.textPrimary }]}>
|
||||
{formatCurrency(data.premiumAmount)}
|
||||
</Text>
|
||||
<Text style={[styles.metricCaption, { color: theme.colors.textMuted }]}>
|
||||
{data.premiumFrequency}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoList}>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Start date:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{formatDate(data.startDate)}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Expiry date:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{formatDate(data.expiryDate)}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Next premium due:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{formatDate(data.nextPremiumDueDate)}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Nominee:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{data.nomineeName} ({data.nomineeRelationship})
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
<Card style={styles.actionCard}>
|
||||
<View style={styles.actionRow}>
|
||||
<View style={styles.actionCol}>
|
||||
<Button
|
||||
title="Pay Premium"
|
||||
onPress={() =>
|
||||
navigation.navigate('InsuranceRenewals', { policyId: data.id })
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.actionCol}>
|
||||
<Button
|
||||
title="File Claim"
|
||||
variant="secondary"
|
||||
onPress={() =>
|
||||
navigation.navigate('InsuranceClaims', { policyId: data.id })
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Button
|
||||
title="Download Policy PDF"
|
||||
variant="secondary"
|
||||
onPress={() =>
|
||||
toast(
|
||||
'Policy download ready',
|
||||
'success',
|
||||
`${data.documents[0]?.name ?? 'Policy schedule'} is ready from the secure vault.`,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card style={styles.sectionCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Benefits and inclusions
|
||||
</Text>
|
||||
{data.benefits.map((benefit) => (
|
||||
<View key={benefit} style={styles.listRow}>
|
||||
<Ionicons name="checkmark-circle" size={16} color={theme.colors.accent} />
|
||||
<Text style={[styles.listText, { color: theme.colors.textPrimary }]}>
|
||||
{benefit}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
{data.inclusions.map((inclusion) => (
|
||||
<View key={inclusion} style={styles.listRow}>
|
||||
<Ionicons name="shield-checkmark" size={16} color={theme.colors.primary} />
|
||||
<Text style={[styles.listText, { color: theme.colors.textPrimary }]}>
|
||||
{inclusion}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</Card>
|
||||
|
||||
<Card style={styles.sectionCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Exclusions
|
||||
</Text>
|
||||
{data.exclusions.map((exclusion) => (
|
||||
<View key={exclusion} style={styles.listRow}>
|
||||
<Ionicons name="close-circle" size={16} color={theme.colors.error} />
|
||||
<Text style={[styles.listText, { color: theme.colors.textPrimary }]}>
|
||||
{exclusion}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</Card>
|
||||
|
||||
<Card style={styles.sectionCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Insurer details
|
||||
</Text>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Claims ratio:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{data.insurer.claimSettlementRatio}%
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Support:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{data.insurer.supportPhone}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Email:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{data.insurer.supportEmail}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
||||
Address:{' '}
|
||||
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
||||
{data.insurer.address}
|
||||
</Text>
|
||||
</Text>
|
||||
</Card>
|
||||
|
||||
<Card style={styles.sectionCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Policy documents
|
||||
</Text>
|
||||
{data.documents.map((document) => (
|
||||
<Pressable
|
||||
key={document.id}
|
||||
onPress={() =>
|
||||
toast('Document available', 'info', `${document.name} is available in PDF.`)
|
||||
}
|
||||
style={[styles.documentRow, { backgroundColor: theme.colors.surfaceMuted }]}
|
||||
>
|
||||
<View style={styles.documentCopy}>
|
||||
<Text style={[styles.documentName, { color: theme.colors.textPrimary }]}>
|
||||
{document.name}
|
||||
</Text>
|
||||
<Text style={[styles.documentMeta, { color: theme.colors.textMuted }]}>
|
||||
{document.mimeType} · {document.sizeKb} KB
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons name="download-outline" size={18} color={theme.colors.primary} />
|
||||
</Pressable>
|
||||
))}
|
||||
</Card>
|
||||
</ScrollView>
|
||||
)}
|
||||
<View style={{ height: theme.spacing.xxl }} />
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scroll: {
|
||||
gap: 16,
|
||||
paddingBottom: 32,
|
||||
},
|
||||
heroCard: {
|
||||
gap: 14,
|
||||
},
|
||||
heroHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
gap: 12,
|
||||
},
|
||||
heroCopy: {
|
||||
flex: 1,
|
||||
gap: 2,
|
||||
},
|
||||
heroTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
},
|
||||
heroMeta: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
metricGrid: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
metricCard: {
|
||||
flex: 1,
|
||||
borderRadius: 16,
|
||||
padding: 14,
|
||||
gap: 4,
|
||||
},
|
||||
metricLabel: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
metricValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: '900',
|
||||
},
|
||||
metricCaption: {
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
},
|
||||
infoList: {
|
||||
gap: 6,
|
||||
},
|
||||
infoRow: {
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
},
|
||||
infoStrong: {
|
||||
fontWeight: '700',
|
||||
},
|
||||
actionCard: {
|
||||
gap: 12,
|
||||
},
|
||||
actionRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
actionCol: {
|
||||
flex: 1,
|
||||
},
|
||||
sectionCard: {
|
||||
gap: 10,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
},
|
||||
listRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
gap: 8,
|
||||
},
|
||||
listText: {
|
||||
flex: 1,
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
fontWeight: '600',
|
||||
},
|
||||
documentRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderRadius: 14,
|
||||
padding: 12,
|
||||
},
|
||||
documentCopy: {
|
||||
flex: 1,
|
||||
gap: 3,
|
||||
},
|
||||
documentName: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
},
|
||||
documentMeta: {
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user