Update CBS Mobile App codebase

This commit is contained in:
Abin Toppo
2026-09-21 13:21:25 +05:30
parent 58af541536
commit 2dcb10775c
339 changed files with 26750 additions and 16767 deletions
+240
View File
@@ -0,0 +1,240 @@
import React, { memo, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import Ionicons from '@react-native-vector-icons/ionicons';
import type { CbsAccount } from '../../types/cbs';
import { useTheme, textStyles } from '../../theme';
interface AccountCardProps {
account: CbsAccount;
/**
* The customer's name, from the session. Passed in rather than read here:
* this is a shared presentational primitive and none of them reach into app
* state. It is also not account data — `/accounts` returns no holder name.
*/
accountHolder?: string;
/**
* Whether the reveal control for the account number is offered. When false
* the number stays masked with no way to reveal it.
*/
canRevealNumber?: boolean;
}
/**
* The hero card for one account.
*
* This was a balance card until the app moved to `GET /accounts`, which
* returns no balance — and no other CBS endpoint returns one either. Rather
* than print a figure the bank never sent, the card now leads with what it can
* actually vouch for: which account this is, and whether it is usable.
*
* The account number is masked by default. `/accounts` is the only endpoint
* that returns an unmasked one, so revealing it is a deliberate act.
*/
const AccountCardComponent = ({
account,
accountHolder,
canRevealNumber = true,
}: AccountCardProps) => {
const theme = useTheme();
const [revealed, setRevealed] = useState(false);
const showFull = revealed && canRevealNumber;
const displayNumber = showFull
? account.accountNumber
: `XXXX ${account.accountNumber.slice(-4)}`;
return (
<LinearGradient
colors={theme.colors.brandGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={[styles.card, styles.gradientCard, { borderRadius: theme.radius.xl }]}
>
<View style={[styles.brandAccent, { backgroundColor: theme.colors.onBrandSurface }]} />
<View style={styles.topRow}>
<View style={styles.bankInfo}>
<View
style={[styles.bankIconFrame, { backgroundColor: theme.colors.onBrandSurfaceStrong }]}
>
<Ionicons name="business" size={20} color={theme.colors.onBrandPrimary} />
</View>
<View>
<Text style={[styles.bankName, { color: theme.colors.onBrandPrimary }]}>
CBS Bank
</Text>
<Text style={[styles.cardType, { color: theme.colors.onBrandSecondary }]}>
{`${account.accountType.toUpperCase()} ACCOUNT`}
</Text>
</View>
</View>
{/* A non-active account has to say so here: this is the card the
customer picks an account from, and dormant is the reason a later
transfer would be refused. */}
{account.status !== 'ACTIVE' ? (
<View style={[styles.statusBadge, { backgroundColor: theme.colors.onBrandPrimary }]}>
<Text style={[styles.statusBadgeText, { color: theme.colors.primary }]}>
{account.status}
</Text>
</View>
) : null}
</View>
<View style={styles.mainContent}>
<Text style={[styles.numberLabel, { color: theme.colors.onBrandSecondary }]}>
ACCOUNT NUMBER
</Text>
<View style={styles.numberRow}>
<Text
style={[styles.numberValue, { color: theme.colors.onBrandPrimary }]}
selectable={showFull}
>
{displayNumber}
</Text>
{canRevealNumber ? (
<Pressable
onPress={() => setRevealed((current) => !current)}
style={[styles.revealButton, { backgroundColor: theme.colors.onBrandSurface }]}
hitSlop={8}
accessibilityRole="button"
accessibilityLabel={showFull ? 'Hide account number' : 'Show account number'}
>
<Ionicons
name={showFull ? 'eye-off-outline' : 'eye-outline'}
size={20}
color={theme.colors.onBrandPrimary}
/>
</Pressable>
) : null}
</View>
</View>
<View style={styles.footerRow}>
<View style={styles.footerItem}>
<Text style={[styles.footerLabel, { color: theme.colors.onBrandMuted }]}>
{accountHolder ? 'ACCOUNT HOLDER' : 'IFSC'}
</Text>
<Text style={[styles.footerValue, { color: theme.colors.onBrandPrimary }]}>
{accountHolder ?? account.ifsc}
</Text>
</View>
<View style={[styles.footerItem, styles.footerItemEnd]}>
<Text style={[styles.footerLabel, { color: theme.colors.onBrandMuted }]}>
{account.mmid ? 'MMID' : 'IFSC'}
</Text>
<Text style={[styles.footerValue, { color: theme.colors.onBrandPrimary }]}>
{account.mmid ?? account.ifsc}
</Text>
</View>
</View>
</LinearGradient>
);
};
const styles = StyleSheet.create({
card: {
padding: 24,
gap: 32,
position: 'relative',
minHeight: 260,
},
gradientCard: {
overflow: 'hidden',
},
brandAccent: {
position: 'absolute',
top: -50,
right: -50,
width: 200,
height: 200,
borderRadius: 100,
},
topRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
bankInfo: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
bankIconFrame: {
width: 40,
height: 40,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
bankName: {
...textStyles.h3,
},
cardType: {
...textStyles.eyebrow,
fontSize: 10,
},
statusBadge: {
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 10,
},
statusBadgeText: {
...textStyles.eyebrow,
fontSize: 10,
letterSpacing: 0.5,
},
mainContent: {
gap: 6,
},
numberLabel: {
...textStyles.eyebrow,
letterSpacing: 1,
},
numberRow: {
flexDirection: 'row',
alignItems: 'center',
},
// An account number, so it is set in mono — a customer reads these back a
// group at a time.
numberValue: {
...textStyles.code,
flex: 1,
fontSize: 22,
letterSpacing: 1.5,
},
revealButton: {
marginLeft: 12,
width: 32,
height: 32,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
},
footerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-end',
marginTop: 4,
},
footerItem: {
gap: 4,
flexShrink: 1,
},
footerItemEnd: {
alignItems: 'flex-end',
},
footerLabel: {
...textStyles.eyebrow,
fontSize: 9,
letterSpacing: 1,
},
footerValue: {
...textStyles.bodyStrong,
fontSize: 14,
},
});
export const AccountCard = memo(AccountCardComponent);