249 lines
7.8 KiB
TypeScript
249 lines
7.8 KiB
TypeScript
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, fontFamily } 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,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
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,
|
|
fontFamily: fontFamily.bold,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
},
|
|
resultValue: {
|
|
fontSize: 18,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
recommendation: {
|
|
fontSize: 12,
|
|
lineHeight: 18,
|
|
},
|
|
});
|