Update CBS Mobile App codebase
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View, Pressable } from 'react-native';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useTheme, fontFamily } from '../../../theme';
|
||||
|
||||
interface HomeSectionHeaderProps {
|
||||
title: string;
|
||||
@@ -32,11 +32,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
fontFamily: fontFamily.bold,
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
seeAll: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View, ScrollView, Pressable } from 'react-native';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { FeatureKey } from '../../../constants/featureKeys';
|
||||
import { useEnabledItems } from '../../../features';
|
||||
import { useTheme, fontFamily } from '../../../theme';
|
||||
|
||||
interface Category {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
/**
|
||||
* The module this chip opens. Every one of these is a whole tab or stack, so
|
||||
* an untagged chip is a chip that can navigate to a route the navigator no
|
||||
* longer registers — which is a crash, not a no-op.
|
||||
*/
|
||||
feature: FeatureKey;
|
||||
}
|
||||
|
||||
interface HorizontalCategoriesProps {
|
||||
@@ -14,14 +22,21 @@ interface HorizontalCategoriesProps {
|
||||
}
|
||||
|
||||
const CATEGORIES: Category[] = [
|
||||
{ id: 'cards', name: 'Cards', icon: 'card-outline' },
|
||||
{ id: 'deposits', name: 'Deposits', icon: 'wallet-outline' },
|
||||
{ id: 'loans', name: 'Loans', icon: 'business-outline' },
|
||||
{ id: 'insurance', name: 'Insurance', icon: 'shield-outline' },
|
||||
{ id: 'cards', name: 'Cards', icon: 'card-outline', feature: FeatureKey.CARDS },
|
||||
{ id: 'deposits', name: 'Deposits', icon: 'wallet-outline', feature: FeatureKey.SERVICES_DEPOSITS_OVERVIEW },
|
||||
{ id: 'loans', name: 'Loans', icon: 'business-outline', feature: FeatureKey.LOANS },
|
||||
{ id: 'insurance', name: 'Insurance', icon: 'shield-outline', feature: FeatureKey.INSURANCE },
|
||||
];
|
||||
|
||||
export const HorizontalCategories = ({ onCategoryPress }: HorizontalCategoriesProps) => {
|
||||
const theme = useTheme();
|
||||
const categories = useEnabledItems(CATEGORIES);
|
||||
|
||||
// With every module behind these chips switched off, the row is an empty
|
||||
// band of padding rather than a row of nothing.
|
||||
if (categories.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@@ -29,7 +44,7 @@ export const HorizontalCategories = ({ onCategoryPress }: HorizontalCategoriesPr
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.container}
|
||||
>
|
||||
{CATEGORIES.map((cat) => {
|
||||
{categories.map((cat) => {
|
||||
return (
|
||||
<Pressable
|
||||
key={cat.id}
|
||||
@@ -108,6 +123,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
name: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import type { InsightMetric, SpendingBreakdown } from '../../../types/app';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useTheme, fontFamily } from '../../../theme';
|
||||
import { formatCompactCurrency } from '../../../utils/formatters';
|
||||
import { Card } from '../../../components';
|
||||
|
||||
@@ -60,7 +60,7 @@ const InsightsCardComponent = ({ insights, spending }: InsightsCardProps) => {
|
||||
const styles = StyleSheet.create({
|
||||
heading: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
},
|
||||
metricRow: {
|
||||
flexDirection: 'row',
|
||||
@@ -77,7 +77,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
metricValue: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
},
|
||||
subheading: {
|
||||
fontSize: 12,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo } from 'react';
|
||||
import { StyleSheet, Text, View, Pressable, ImageBackground } from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useTheme, fontFamily } from '../../../theme';
|
||||
|
||||
interface PromoBannerProps {
|
||||
title: string;
|
||||
@@ -81,13 +81,13 @@ const styles = StyleSheet.create({
|
||||
badgeText: {
|
||||
color: 'white',
|
||||
fontSize: 10,
|
||||
fontWeight: '800',
|
||||
fontFamily: fontFamily.bold,
|
||||
letterSpacing: 1,
|
||||
},
|
||||
rateText: {
|
||||
color: 'rgba(255, 255, 255, 0.8)',
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
},
|
||||
content: {
|
||||
gap: 6,
|
||||
@@ -96,13 +96,13 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
color: 'white',
|
||||
fontSize: 24,
|
||||
fontWeight: '900',
|
||||
fontFamily: fontFamily.bold,
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
subtitle: {
|
||||
color: 'rgba(255, 255, 255, 0.7)',
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
fontFamily: fontFamily.medium,
|
||||
lineHeight: 20,
|
||||
maxWidth: '80%',
|
||||
},
|
||||
@@ -116,7 +116,7 @@ const styles = StyleSheet.create({
|
||||
ctaText: {
|
||||
color: 'white',
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
},
|
||||
arrowCircle: {
|
||||
width: 24,
|
||||
|
||||
@@ -7,16 +7,38 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useTheme, fontFamily, type CategoryHue } from '../../../theme';
|
||||
import { Card } from '../../../components';
|
||||
import { FeatureKey } from '../../../constants/featureKeys';
|
||||
import { useEnabledItems } from '../../../features';
|
||||
|
||||
const CBS_ACTIONS = [
|
||||
{ id: 'transfer', title: 'Transfer', subtitle: 'Send Funds', icon: 'send', color: '#1E40AF', route: 'Payments' },
|
||||
{ id: 'payments', title: 'Pay Bills', subtitle: 'Utilities', icon: 'receipt', color: '#10B981', route: 'Payments' },
|
||||
{ id: 'airtime', title: 'Airtime', subtitle: 'Recharge', icon: 'phone-portrait', color: '#8B5CF6', route: 'Payments' },
|
||||
{ id: 'loans', title: 'Loans', subtitle: 'Manage', icon: 'cash', color: '#F59E0B', route: 'LoanFlow' },
|
||||
{ id: 'cards', title: 'Cards', subtitle: 'Security', icon: 'card', color: '#EC4899', route: 'Cards' },
|
||||
{ id: 'services', title: 'Stats', subtitle: 'Summary', icon: 'stats-chart', color: '#0EA5E9', route: 'Home' },
|
||||
/**
|
||||
* The tiles.
|
||||
*
|
||||
* Each names a categorical HUE rather than a hex. The hue is decorative — it
|
||||
* exists so that neighbouring tiles are easy to tell apart, and it carries no
|
||||
* state — so it resolves through `theme.colors.category`, which supplies a
|
||||
* legible tone plus a matching tint chip for whichever theme is live.
|
||||
*
|
||||
* The previous `${hex}15` suffix built that chip by compositing the raw hue at
|
||||
* 8% alpha over the card. That reads on white and disappears completely on a
|
||||
* dark card, which is the specific bug the paired ramp exists to fix.
|
||||
*/
|
||||
const CBS_ACTIONS: ReadonlyArray<{
|
||||
id: string;
|
||||
feature: FeatureKey;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
icon: string;
|
||||
hue: CategoryHue;
|
||||
route: string;
|
||||
}> = [
|
||||
{ id: 'transfer', feature: FeatureKey.HOME_QUICK_ACTIONS_TRANSFER, title: 'Transfer', subtitle: 'Send Funds', icon: 'send', hue: 'blue', route: 'Payments' },
|
||||
{ id: 'payments', feature: FeatureKey.HOME_QUICK_ACTIONS_BILL_PAY, title: 'Pay Bills', subtitle: 'Utilities', icon: 'receipt', hue: 'green', route: 'Payments' },
|
||||
{ id: 'airtime', feature: FeatureKey.HOME_QUICK_ACTIONS_AIRTIME, title: 'Airtime', subtitle: 'Recharge', icon: 'phone-portrait', hue: 'violet', route: 'Payments' },
|
||||
{ id: 'loans', feature: FeatureKey.HOME_QUICK_ACTIONS_LOANS, title: 'Loans', subtitle: 'Manage', icon: 'cash', hue: 'amber', route: 'LoanFlow' },
|
||||
{ id: 'cards', feature: FeatureKey.HOME_QUICK_ACTIONS_CARDS, title: 'Cards', subtitle: 'Security', icon: 'card', hue: 'pink', route: 'Cards' },
|
||||
{ id: 'services', feature: FeatureKey.HOME_QUICK_ACTIONS_STATS, title: 'Stats', subtitle: 'Summary', icon: 'stats-chart', hue: 'sky', route: 'Home' },
|
||||
];
|
||||
|
||||
interface QuickActionsProps {
|
||||
@@ -25,10 +47,11 @@ interface QuickActionsProps {
|
||||
|
||||
const QuickActionsComponent = ({ onPressRoute }: QuickActionsProps) => {
|
||||
const theme = useTheme();
|
||||
const actions = useEnabledItems(CBS_ACTIONS);
|
||||
|
||||
return (
|
||||
<View style={styles.grid}>
|
||||
{CBS_ACTIONS.map((action) => (
|
||||
{actions.map((action) => (
|
||||
<Card
|
||||
key={action.id}
|
||||
style={styles.card}
|
||||
@@ -37,8 +60,17 @@ const QuickActionsComponent = ({ onPressRoute }: QuickActionsProps) => {
|
||||
onPress={() => onPressRoute(action.route)}
|
||||
style={styles.pressable}
|
||||
>
|
||||
<View style={[styles.iconContainer, { backgroundColor: `${action.color}15` }]}>
|
||||
<Ionicons name={action.icon as any} size={22} color={action.color} />
|
||||
<View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{ backgroundColor: theme.colors.category[action.hue].surface },
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name={action.icon as any}
|
||||
size={22}
|
||||
color={theme.colors.category[action.hue].tone}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={[styles.title, { color: theme.colors.textPrimary }]}>{action.title}</Text>
|
||||
@@ -79,14 +111,14 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
fontSize: 13,
|
||||
fontWeight: '800',
|
||||
fontFamily: fontFamily.bold,
|
||||
textAlign: 'center',
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 9,
|
||||
marginTop: 2,
|
||||
fontWeight: '600',
|
||||
fontFamily: fontFamily.semibold,
|
||||
textAlign: 'center',
|
||||
opacity: 0.6,
|
||||
textTransform: 'uppercase',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useTheme, fontFamily } from '../../../theme';
|
||||
|
||||
interface ServiceGridItemProps {
|
||||
id: string;
|
||||
@@ -78,7 +78,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
letterSpacing: -0.2,
|
||||
lineHeight: 17,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo } from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { useTheme, fontFamily } from '../../../theme';
|
||||
import { Card } from '../../../components';
|
||||
|
||||
interface SummaryCardProps {
|
||||
@@ -75,12 +75,12 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
fontFamily: fontFamily.bold,
|
||||
letterSpacing: -0.3,
|
||||
},
|
||||
date: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
fontFamily: fontFamily.medium,
|
||||
marginTop: 1,
|
||||
},
|
||||
grid: {
|
||||
@@ -94,13 +94,13 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
label: {
|
||||
fontSize: 10,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
},
|
||||
value: {
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
fontFamily: fontFamily.bold,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
|
||||
Reference in New Issue
Block a user