Fix form keyboard issues and sticky footers in agent screens
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../app/hooks';
|
||||
import { Button, Card, Input, ScreenShell } from '../../../components';
|
||||
import type { InsuranceStackParamList } from '../../../navigation/types';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { formatCompactCurrency, formatCurrency } from '../../../utils/formatters';
|
||||
import { InsuranceCategoryChip } from '../components/InsuranceCategoryChip';
|
||||
import { InsuranceRangeSlider } from '../components/InsuranceRangeSlider';
|
||||
import { useEstimateInsurancePremiumQuery } from '../services/insuranceApi';
|
||||
import { insuranceActions } from '../store/insuranceSlice';
|
||||
import { selectInsuranceCalculator } from '../store/insuranceSelectors';
|
||||
import { INSURANCE_CATEGORIES } from '../types';
|
||||
|
||||
type Props = NativeStackScreenProps<InsuranceStackParamList, 'InsuranceCalculator'>;
|
||||
|
||||
export default function InsuranceCalculatorScreen({ navigation, route }: Props) {
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = useTheme();
|
||||
const calculator = useAppSelector(selectInsuranceCalculator);
|
||||
const effectiveCategory = calculator.category;
|
||||
|
||||
useEffect(() => {
|
||||
if (route.params?.category) {
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({
|
||||
field: 'category',
|
||||
value: route.params.category,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [dispatch, route.params?.category]);
|
||||
const estimateQuery = useEstimateInsurancePremiumQuery({
|
||||
...calculator,
|
||||
category: effectiveCategory,
|
||||
});
|
||||
|
||||
const estimate = estimateQuery.data;
|
||||
const coverageValue = useMemo(
|
||||
() => Number(calculator.coverageAmount || 0),
|
||||
[calculator.coverageAmount],
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenShell title="Calculator" subtitle="Estimate premiums by age, cover, and tenure">
|
||||
<ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false} keyboardShouldPersistTaps="handled">
|
||||
<Card style={styles.sectionCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Policy category
|
||||
</Text>
|
||||
<View style={styles.categoryWrap}>
|
||||
{INSURANCE_CATEGORIES.map((category) => (
|
||||
<InsuranceCategoryChip
|
||||
key={category}
|
||||
category={category}
|
||||
selected={effectiveCategory === category}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({
|
||||
field: 'category',
|
||||
value: category,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
<Card style={styles.sectionCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Adjust your assumptions
|
||||
</Text>
|
||||
<InsuranceRangeSlider
|
||||
label="Age"
|
||||
min={18}
|
||||
max={70}
|
||||
step={1}
|
||||
value={Number(calculator.age || 18)}
|
||||
onValueChange={(value) =>
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({
|
||||
field: 'age',
|
||||
value: String(value),
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<InsuranceRangeSlider
|
||||
label="Coverage"
|
||||
min={100000}
|
||||
max={20000000}
|
||||
step={100000}
|
||||
value={coverageValue || 100000}
|
||||
formatter={(value) => formatCompactCurrency(value)}
|
||||
onValueChange={(value) =>
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({
|
||||
field: 'coverageAmount',
|
||||
value: String(value),
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<InsuranceRangeSlider
|
||||
label="Tenure"
|
||||
min={1}
|
||||
max={30}
|
||||
step={1}
|
||||
value={Number(calculator.tenureYears || 1)}
|
||||
suffix=" yr"
|
||||
onValueChange={(value) =>
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({
|
||||
field: 'tenureYears',
|
||||
value: String(value),
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card style={styles.sectionCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Manual inputs
|
||||
</Text>
|
||||
<Input
|
||||
label="Age"
|
||||
keyboardType="number-pad"
|
||||
value={calculator.age}
|
||||
onChangeText={(value) =>
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({ field: 'age', value }),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
label="Coverage Amount"
|
||||
keyboardType="number-pad"
|
||||
value={calculator.coverageAmount}
|
||||
onChangeText={(value) =>
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({
|
||||
field: 'coverageAmount',
|
||||
value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
label="Tenure Years"
|
||||
keyboardType="number-pad"
|
||||
value={calculator.tenureYears}
|
||||
onChangeText={(value) =>
|
||||
dispatch(
|
||||
insuranceActions.updateInsuranceCalculator({
|
||||
field: 'tenureYears',
|
||||
value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card style={styles.resultCard}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
||||
Estimated premium
|
||||
</Text>
|
||||
<View style={styles.resultGrid}>
|
||||
<View style={[styles.resultMetric, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.resultLabel, { color: theme.colors.textSecondary }]}>
|
||||
Monthly
|
||||
</Text>
|
||||
<Text style={[styles.resultValue, { color: theme.colors.textPrimary }]}>
|
||||
{estimate ? formatCurrency(estimate.monthlyPremium) : '--'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.resultMetric, { backgroundColor: theme.colors.surfaceMuted }]}>
|
||||
<Text style={[styles.resultLabel, { color: theme.colors.textSecondary }]}>
|
||||
Annual
|
||||
</Text>
|
||||
<Text style={[styles.resultValue, { color: theme.colors.textPrimary }]}>
|
||||
{estimate ? formatCurrency(estimate.annualPremium) : '--'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={[styles.recommendation, { color: theme.colors.textSecondary }]}>
|
||||
Recommended protection: {estimate ? formatCompactCurrency(estimate.recommendedCover) : '--'}
|
||||
</Text>
|
||||
<Button
|
||||
title="Buy This Cover"
|
||||
onPress={() =>
|
||||
navigation.navigate('BuyInsurance', { category: effectiveCategory })
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</ScrollView>
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scroll: {
|
||||
gap: 16,
|
||||
paddingBottom: 28,
|
||||
},
|
||||
sectionCard: {
|
||||
gap: 14,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
},
|
||||
categoryWrap: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 10,
|
||||
},
|
||||
resultCard: {
|
||||
gap: 14,
|
||||
},
|
||||
resultGrid: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
resultMetric: {
|
||||
flex: 1,
|
||||
borderRadius: 16,
|
||||
padding: 14,
|
||||
gap: 4,
|
||||
},
|
||||
resultLabel: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
resultValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: '900',
|
||||
},
|
||||
recommendation: {
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user