Skip to content

Commit 1a83ca7

Browse files
feat: Add core screens for group detail, login, and registration, along with balance and money display components.
1 parent 09618cd commit 1a83ca7

5 files changed

Lines changed: 41 additions & 18 deletions

File tree

apps/mobile/app/components/BalanceCard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface BalanceCardProps {
1212
subtitle?: string;
1313
icon?: keyof typeof MaterialCommunityIcons.glyphMap;
1414
variant?: 'default' | 'success' | 'danger';
15+
currency?: string;
1516
}
1617

1718
export const BalanceCard = memo(function BalanceCard({
@@ -20,6 +21,7 @@ export const BalanceCard = memo(function BalanceCard({
2021
subtitle,
2122
icon = 'wallet',
2223
variant = 'default',
24+
currency = 'USD',
2325
}: BalanceCardProps) {
2426
const { colors, typography } = useAppTheme();
2527

apps/mobile/app/components/ui/MoneyText.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import React from 'react';
1+
import React from 'react';
22
import { StyleSheet, Text as RNText } from 'react-native';
33
import { useAppTheme } from '../../theme/useAppTheme';
44

55
interface MoneyTextProps {
66
cents: string;
77
size?: 'sm' | 'md' | 'lg';
88
variant?: 'default' | 'success' | 'danger';
9+
currency?: string;
910
}
1011

11-
export function MoneyText({ cents, size = 'md', variant = 'default' }: MoneyTextProps) {
12+
export function MoneyText({ cents, size = 'md', variant = 'default', currency = 'USD' }: MoneyTextProps) {
1213
const { colors } = useAppTheme();
1314
const amount = Number(cents) / 100;
15+
const symbol = currency === 'INR' ? '₹' : '$';
1416

1517
const colorMap = {
1618
default: colors.text_primary,
@@ -33,9 +35,9 @@ export function MoneyText({ cents, size = 'md', variant = 'default' }: MoneyText
3335
fontSize: sizeMap[size],
3436
},
3537
]}
36-
accessibilityLabel={`$${amount.toFixed(2)}`}
38+
accessibilityLabel={`${symbol}${amount.toFixed(2)}`}
3739
>
38-
${amount.toFixed(2)}
40+
{symbol}{amount.toFixed(2)}
3941
</RNText>
4042
);
4143
}

apps/mobile/app/screens/GroupDetailScreen.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Swipeable } from 'react-native-gesture-handler';
44
import { Text } from 'react-native-paper';
55
import { MaterialCommunityIcons } from '@expo/vector-icons';
66
import Animated, { FadeInDown } from 'react-native-reanimated';
7-
import type { ExpenseDto, GroupMemberSummaryDto } from '@fairshare/shared-types';
7+
import type { ExpenseDto, GroupMemberSummaryDto, GroupDto } from '@fairshare/shared-types';
88
import { groupService } from '../services/group.service';
99
import { expenseService } from '../services/expense.service';
1010
import { realtimeService } from '../services/realtime.service';
@@ -35,6 +35,7 @@ export function GroupDetailScreen({
3535
const [loading, setLoading] = React.useState(true);
3636
const [balances, setBalances] = React.useState<Array<{ id: string; amountCents: string; userId: string; counterpartyUserId: string }>>([]);
3737
const [members, setMembers] = React.useState<GroupMemberSummaryDto[]>([]);
38+
const [group, setGroup] = React.useState<GroupDto | null>(null);
3839
const [summary, setSummary] = React.useState<{
3940
totalExpensesCents: string;
4041
topSpenderUserId: string | null;
@@ -52,15 +53,17 @@ export function GroupDetailScreen({
5253
const load = React.useCallback(async () => {
5354
startScreenLoad('GroupDetail');
5455
try {
55-
const [expenseData, balanceData, memberData] = await Promise.all([
56+
const [expenseData, balanceData, memberData, groupData] = await Promise.all([
5657
expenseService.list(route.params.groupId),
5758
groupService.balances(route.params.groupId),
5859
groupService.members(route.params.groupId),
60+
groupService.get(route.params.groupId),
5961
]);
6062
const summaryData = await groupService.summary(route.params.groupId);
6163
setExpenses(route.params.groupId, expenseData.items);
6264
setBalances(balanceData);
6365
setMembers(memberData);
66+
setGroup(groupData);
6467
setSummary({
6568
totalExpensesCents: summaryData.totalExpensesCents,
6669
topSpenderUserId: summaryData.topSpenderUserId,
@@ -135,6 +138,8 @@ export function GroupDetailScreen({
135138
const payer = memberById.get(expense.payerId);
136139
const participantCount = expense.splits?.length ?? 0;
137140

141+
const symbol = group?.currency === 'INR' ? '₹' : '$';
142+
138143
return (
139144
<Swipeable
140145
key={expense.id}
@@ -149,7 +154,7 @@ export function GroupDetailScreen({
149154
>
150155
<ExpenseCard
151156
description={expense.description}
152-
amount={`$${(Number(expense.totalAmountCents) / 100).toFixed(2)}`}
157+
amount={`${symbol}${(Number(expense.totalAmountCents) / 100).toFixed(2)}`}
153158
payerName={payer?.name ?? 'Unknown'}
154159
payerInitials={getInitials(payer?.name ?? 'U')}
155160
participantCount={participantCount}
@@ -180,17 +185,21 @@ export function GroupDetailScreen({
180185
showsVerticalScrollIndicator={false}
181186
>
182187
{/* Balance Summary */}
183-
{summary && (
188+
{summary && group && (
184189
<Animated.View entering={FadeInDown.duration(400)} style={styles.balanceSection}>
185190
<BalanceCard
186-
title="Total Spent"
187-
amount={`$${(Number(summary.totalExpensesCents) / 100).toFixed(2)}`}
191+
title="Total Group Spending"
192+
amount={`${group.currency === 'INR' ? '₹' : '$'}${(Number(summary.totalExpensesCents) / 100).toFixed(2)}`}
188193
icon="cash-multiple"
189194
/>
190195
<BalanceCard
191-
title="Your Balance"
192-
amount={`$${Math.abs(userBalance).toFixed(2)}`}
193-
subtitle={userBalance > 0 ? 'You are owed' : userBalance < 0 ? 'You owe' : 'Settled up'}
196+
title="Your Personal Balance"
197+
amount={`${group.currency === 'INR' ? '₹' : '$'}${Math.abs(userBalance).toFixed(2)}`}
198+
subtitle={
199+
userBalance !== 0
200+
? `${userBalance > 0 ? 'You are owed' : 'You owe'} (${((Math.abs(userBalance) * 100) / (Number(summary.totalExpensesCents) / 100 || 1)).toFixed(1)}% of total)`
201+
: 'Settled up'
202+
}
194203
variant={userBalance > 0 ? 'success' : userBalance < 0 ? 'danger' : 'default'}
195204
icon={userBalance >= 0 ? 'trending-up' : 'trending-down'}
196205
/>

apps/mobile/app/screens/LoginScreen.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ export function LoginScreen({ navigation }: { navigation: { navigate: (route: st
9090
error={Boolean(errors.password)}
9191
mode="outlined"
9292
outlineStyle={{ borderRadius: 12 }}
93-
style={styles.input}
93+
style={[styles.input, { backgroundColor: colors.surface }]}
94+
textColor={colors.text_primary}
95+
outlineColor={colors.border}
96+
activeOutlineColor={colors.primary}
9497
/>
9598
)}
9699
/>
@@ -148,7 +151,7 @@ const styles = StyleSheet.create({
148151
width: '100%',
149152
},
150153
input: {
151-
backgroundColor: '#FFFFFF',
154+
marginBottom: spacing.xs,
152155
},
153156
loginButton: {
154157
marginTop: spacing.md,

apps/mobile/app/screens/RegisterScreen.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ export function RegisterScreen({ navigation }: { navigation: { goBack: () => voi
6161
error={Boolean(errors.name)}
6262
mode="outlined"
6363
outlineStyle={{ borderRadius: 12 }}
64-
style={styles.input}
64+
style={[styles.input, { backgroundColor: colors.surface }]}
65+
textColor={colors.text_primary}
66+
outlineColor={colors.border}
67+
activeOutlineColor={colors.primary}
6568
/>
6669
)}
6770
/>
@@ -86,7 +89,11 @@ export function RegisterScreen({ navigation }: { navigation: { goBack: () => voi
8689
error={Boolean(errors.email)}
8790
mode="outlined"
8891
outlineStyle={{ borderRadius: 12 }}
89-
style={styles.input}
92+
style={[styles.input, { backgroundColor: colors.surface }]}
93+
textColor={colors.text_primary}
94+
outlineColor={colors.border}
95+
activeOutlineColor={colors.primary}
96+
secureTextEntry
9097
/>
9198
)}
9299
/>
@@ -154,7 +161,7 @@ const styles = StyleSheet.create({
154161
width: '100%',
155162
},
156163
input: {
157-
backgroundColor: '#FFFFFF',
164+
marginBottom: spacing.xs,
158165
},
159166
registerButton: {
160167
marginTop: spacing.md,

0 commit comments

Comments
 (0)