Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions app/(consumer)/history/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as Haptics from 'expo-haptics';
import { Colors, Radius, Spacing, Typography } from '@/constants/theme';
import { ScreenHeader } from '@/components/shared/ScreenHeader';
import { Card } from '@/components/ui/Card';
import { Money } from '@/components/ui/Money';
import { Button } from '@/components/ui/Button';
import { Icon, type IconName } from '@/components/ui/Icon';
import { BankLogo } from '@/components/ui/BankLogo';
Expand Down Expand Up @@ -147,9 +148,13 @@ export default function TransactionDetailScreen() {
/>
</View>

<Text style={[styles.amount, { color: isCredit ? Colors.success : Colors.onSurface }]}>
{isCredit ? '+' : '−'}₦{transaction.amount.toLocaleString()}
</Text>
<Money
value={transaction.amount}
size={Typography.displayMd.fontSize}
color={isCredit ? Colors.success : Colors.onSurface}
sign={isCredit ? '+' : '−'}
style={styles.amount}
/>
<Text style={styles.merchant}>{transaction.merchantName}</Text>

<View style={[styles.statusPill, { backgroundColor: status.tone + '1f' }]}>
Expand Down Expand Up @@ -392,10 +397,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
marginTop: Spacing.lg,
},
amount: {
fontFamily: 'SpaceGrotesk_700Bold',
fontSize: Typography.displayMd.fontSize,
letterSpacing: Typography.displayMd.letterSpacing,
amount: { letterSpacing: Typography.displayMd.letterSpacing,
marginTop: Spacing.lg,
},
merchant: {
Expand Down
7 changes: 2 additions & 5 deletions app/(consumer)/rewards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Colors, Radius, Spacing, Typography } from '@/constants/theme';
import { SectionTitle } from '@/components/shared/SectionTitle';
import { Button } from '@/components/ui/Button';
import { Icon } from '@/components/ui/Icon';
import { Money } from '@/components/ui/Money';
import { EmptyState } from '@/components/shared/EmptyState';
import { useRewardsStore } from '@/store/rewards';
import { CASHBACK_RATES, CATEGORY_ICON, REWARD_POINT_VALUE } from '@/mock/data';
Expand Down Expand Up @@ -81,7 +82,7 @@ export default function RewardsScreen() {
translate; showing what it is worth is the whole point of a
rewards programme. */}
<Text style={styles.eyebrow}>Your points are worth</Text>
<Text style={styles.value}>₦{pointsValueNGN.toLocaleString()}</Text>
<Money value={pointsValueNGN} size={Typography.displayLg.fontSize} style={styles.value} />
<Text style={styles.subValue}>
{points.toLocaleString()} pts · ₦{REWARD_POINT_VALUE.toFixed(2)} each
</Text>
Expand Down Expand Up @@ -280,11 +281,7 @@ const styles = StyleSheet.create({
paddingHorizontal: Spacing.xl,
},
value: {
fontFamily: 'SpaceGrotesk_700Bold',
fontSize: Typography.displayLg.fontSize,
lineHeight: Typography.displayLg.lineHeight,
letterSpacing: Typography.displayLg.letterSpacing,
color: Colors.onSurface,
paddingHorizontal: Spacing.xl,
marginTop: Spacing.xs,
},
Expand Down
14 changes: 8 additions & 6 deletions app/(consumer)/scan/success.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Colors, Radius, Spacing, Typography } from '@/constants/theme';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Icon } from '@/components/ui/Icon';
import { Money } from '@/components/ui/Money';
import { usePaymentStore } from '@/store/payment';

function formatTime(date: Date) {
Expand Down Expand Up @@ -75,7 +76,12 @@ export default function SuccessScreen() {

<Text style={styles.title}>Payment Sent!</Text>
<Text style={styles.merchant}>{transaction.merchantName}</Text>
<Text style={styles.amount}>₦{transaction.amount.toLocaleString()}</Text>
<Money
value={transaction.amount}
size={Typography.displayMd.fontSize}
color={Colors.primary}
style={styles.amount}
/>

<Animated.View style={[styles.pointsBadge, badgeStyle]}>
<Icon name="star" size={13} color={Colors.primary} />
Expand Down Expand Up @@ -175,11 +181,7 @@ const styles = StyleSheet.create({
color: Colors.onSurfaceVariant,
marginTop: Spacing.xs,
},
amount: {
fontFamily: 'SpaceGrotesk_700Bold',
fontSize: Typography.displayMd.fontSize,
color: Colors.primary,
marginTop: Spacing.md,
amount: { marginTop: Spacing.md,
},
pointsBadge: {
flexDirection: 'row',
Expand Down
13 changes: 7 additions & 6 deletions app/(consumer)/sources/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SectionTitle } from '@/components/shared/SectionTitle';
import { Badge, type BadgeKind } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
import { Icon } from '@/components/ui/Icon';
import { Money } from '@/components/ui/Money';
import { CryptoLogo, hasCryptoLogo } from '@/components/ui/CryptoLogo';
import { BankLogo } from '@/components/ui/BankLogo';
import { TransactionRow } from '@/components/shared/TransactionRow';
Expand Down Expand Up @@ -101,7 +102,11 @@ export default function SourceDetailScreen() {
</View>

<Text style={styles.balanceLabel}>Balance</Text>
<Text style={styles.balance}>₦{Math.round(source.balance).toLocaleString()}</Text>
<Money
value={Math.round(source.balance)}
size={Typography.displayMd.fontSize}
style={styles.balance}
/>
{withheld > 0 ? (
<Text style={styles.balanceSpendable}>
₦{Math.round(spendable).toLocaleString()} available · ₦
Expand Down Expand Up @@ -248,11 +253,7 @@ const styles = StyleSheet.create({
textTransform: 'uppercase',
marginTop: Spacing.lg,
},
balance: {
fontFamily: 'SpaceGrotesk_700Bold',
fontSize: Typography.displayMd.fontSize,
color: Colors.onSurface,
marginTop: Spacing.xs,
balance: { marginTop: Spacing.xs,
},
balanceSpendable: {
fontFamily: 'Inter_400Regular',
Expand Down
9 changes: 3 additions & 6 deletions app/(merchant)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { QuickActions } from '@/components/shared/QuickActions';
import { EmptyState } from '@/components/shared/EmptyState';
import { Badge } from '@/components/ui/Badge';
import { Icon } from '@/components/ui/Icon';
import { Money } from '@/components/ui/Money';
import { useMerchantStore } from '@/store/merchant';
import { fetchMerchantPayments, fetchSettlements } from '@/services/merchant';
import { REVENUE_LAST_7_DAYS } from '@/mock/merchant';
Expand Down Expand Up @@ -51,7 +52,7 @@ export default function MerchantHomeScreen() {
</View>

<Text style={styles.revenueLabel}>TODAY'S REVENUE</Text>
<Text style={styles.revenue}>₦{todayRevenue.toLocaleString()}</Text>
<Money value={todayRevenue} size={Typography.displayLg.fontSize} style={styles.revenue} />

<View style={styles.statsRow}>
<View style={styles.statCard}>
Expand Down Expand Up @@ -134,11 +135,7 @@ const styles = StyleSheet.create({
textTransform: 'uppercase',
marginTop: Spacing.xl,
},
revenue: {
fontFamily: 'SpaceGrotesk_700Bold',
fontSize: Typography.displayLg.fontSize,
lineHeight: Typography.displayLg.lineHeight,
color: Colors.onSurface,
revenue: { lineHeight: Typography.displayLg.lineHeight,
marginTop: Spacing.xs,
},
statsRow: {
Expand Down
95 changes: 95 additions & 0 deletions components/ui/Money.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { Text, StyleSheet, type TextStyle, type StyleProp } from 'react-native';
import { Colors } from '@/constants/theme';
import type { CurrencyCode } from '@/types/payment';

/**
* A currency amount set for display.
*
* Space Grotesk draws ₦ (U+20A6) with crossbars almost as heavy as its
* counters, and at Bold display weight the glyph both fills in and kerns
* straight into the following digit — "₦10" renders with the symbol and the 1
* touching. The glyph is present in the font; it is simply not usable at this
* weight and size. Inter's ₦ at the same size is clean.
*
* So the symbol is set in Inter, slightly smaller than the digits and followed
* by a thin space, while the digits stay in Space Grotesk. Mixing a face for
* the currency mark is ordinary typographic practice, and it is the smallest
* change that fixes every large amount in the app at once.
*
* Only worth using at display sizes. In body copy Inter already sets both the
* symbol and the number, and there is nothing to correct.
*/

const SYMBOL_SCALE = 0.72;
/** U+2009. Narrower than a word space — a gap, not a separation. */
const THIN_SPACE = ' ';

const SYMBOL: Partial<Record<CurrencyCode, string>> = {
NGN: '₦',
USD: '$',
GBP: '£',
EUR: '€',
};

export interface MoneyProps {
/** Amount in major units. */
value: number;
currency?: CurrencyCode;
/** Font size of the digits; the symbol is scaled from this. */
size: number;
color?: string;
/** Space Grotesk weight for the digits. */
weight?: '500Medium' | '700Bold';
/** Prefix shown before the symbol, e.g. a debit/credit sign. */
sign?: string;
maximumFractionDigits?: number;
style?: StyleProp<TextStyle>;
numberOfLines?: number;
}

export function Money({
value,
currency = 'NGN',
size,
color = Colors.onSurface,
weight = '700Bold',
sign,
maximumFractionDigits = 0,
style,
numberOfLines,
}: MoneyProps) {
const symbol = SYMBOL[currency] ?? '';
const digits = value.toLocaleString(undefined, { maximumFractionDigits });

return (
<Text
style={[
styles.base,
{ fontSize: size, color, fontFamily: `SpaceGrotesk_${weight}` },
style,
]}
numberOfLines={numberOfLines}
// The visual is two faces; screen readers should hear one amount.
accessibilityLabel={`${sign ?? ''}${symbol}${digits}`}
>
{sign}
<Text style={[styles.symbol, { fontSize: Math.round(size * SYMBOL_SCALE) }]}>
{symbol}
</Text>
{THIN_SPACE}
{digits}
</Text>
);
}

const styles = StyleSheet.create({
base: {
// Tightened tracking is what makes the collision visible in the first
// place, so it is not applied here; per-call-site style can still set it.
includeFontPadding: false,
},
symbol: {
fontFamily: 'Inter_600SemiBold',
},
});
Loading
Loading