Skip to content

Commit 19ca52a

Browse files
committed
Revert "Refactor avatar handling across screens and introduce utility functions"
This reverts commit 6d75613.
1 parent 6d75613 commit 19ca52a

File tree

7 files changed

+647
-361
lines changed

7 files changed

+647
-361
lines changed

frontend/screens/AddExpenseScreen.js

Lines changed: 161 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -33,137 +33,183 @@ const CustomCheckbox = ({ label, status, onPress }) => (
3333
</TouchableOpacity>
3434
);
3535

36-
const SplitInputRow = ({ label, value, onChangeText, isPercentage }) => (
36+
const SplitInputRow = ({
37+
label,
38+
value,
39+
onChangeText,
40+
keyboardType = "numeric",
41+
disabled = false,
42+
isPercentage = false,
43+
}) => (
3744
<View style={styles.splitRow}>
3845
<Text style={styles.splitLabel}>{label}</Text>
39-
<TextInput
40-
value={value}
41-
onChangeText={onChangeText}
42-
keyboardType="numeric"
43-
style={styles.splitInput}
44-
/>
45-
{isPercentage && <Text style={styles.percentageSymbol}>%</Text>}
46+
<View style={{ flexDirection: "row", alignItems: "center" }}>
47+
<TextInput
48+
style={styles.splitInput}
49+
value={value}
50+
onChangeText={onChangeText}
51+
keyboardType={keyboardType}
52+
disabled={disabled}
53+
theme={{ colors: { primary: colors.accent } }}
54+
/>
55+
{isPercentage && <Text style={styles.percentageSymbol}>%</Text>}
56+
</View>
4657
</View>
4758
);
4859

4960
const AddExpenseScreen = ({ route, navigation }) => {
5061
const { groupId } = route.params;
51-
const { token, user } = useContext(AuthContext);
52-
const [members, setMembers] = useState([]);
53-
const [isLoading, setIsLoading] = useState(true);
62+
const { user } = useContext(AuthContext);
5463
const [description, setDescription] = useState("");
5564
const [amount, setAmount] = useState("");
56-
const [payerId, setPayerId] = useState(null);
65+
const [members, setMembers] = useState([]);
66+
const [isLoading, setIsLoading] = useState(true);
67+
const [isSubmitting, setIsSubmitting] = useState(false);
5768
const [splitMethod, setSplitMethod] = useState("equal");
58-
const [exactAmounts, setExactAmounts] = useState({});
69+
const [payerId, setPayerId] = useState(null);
70+
const [menuVisible, setMenuVisible] = useState(false);
71+
5972
const [percentages, setPercentages] = useState({});
6073
const [shares, setShares] = useState({});
74+
const [exactAmounts, setExactAmounts] = useState({});
6175
const [selectedMembers, setSelectedMembers] = useState({});
62-
const [menuVisible, setMenuVisible] = useState(false);
63-
const [isSubmitting, setIsSubmitting] = useState(false);
6476

6577
useEffect(() => {
66-
const loadMembers = async () => {
78+
const fetchMembers = async () => {
6779
try {
68-
const res = await getGroupMembers(groupId);
69-
setMembers(res.data || []);
70-
const initial = {};
71-
(res.data || []).forEach((m) => {
72-
initial[m.userId] = true; // include by default for equal split
80+
const response = await getGroupMembers(groupId);
81+
setMembers(response.data);
82+
const initialShares = {};
83+
const initialPercentages = {};
84+
const initialExactAmounts = {};
85+
const initialSelectedMembers = {};
86+
const numMembers = response.data.length;
87+
const basePercentage = Math.floor(100 / numMembers);
88+
const remainder = 100 - basePercentage * numMembers;
89+
90+
response.data.forEach((member, index) => {
91+
initialShares[member.userId] = "1";
92+
let memberPercentage = basePercentage;
93+
if (index < remainder) {
94+
memberPercentage += 1;
95+
}
96+
initialPercentages[member.userId] = memberPercentage.toString();
97+
initialExactAmounts[member.userId] = "0.00";
98+
initialSelectedMembers[member.userId] = true;
7399
});
74-
setSelectedMembers(initial);
75-
} catch (e) {
76-
Alert.alert("Error", "Failed to load members");
100+
setShares(initialShares);
101+
setPercentages(initialPercentages);
102+
setExactAmounts(initialExactAmounts);
103+
setSelectedMembers(initialSelectedMembers);
104+
105+
const currentUserMember = response.data.find(
106+
(member) => member.userId === user._id
107+
);
108+
if (currentUserMember) {
109+
setPayerId(user._id);
110+
} else if (response.data.length > 0) {
111+
setPayerId(response.data[0].userId);
112+
}
113+
} catch (error) {
114+
console.error("Failed to fetch members:", error);
115+
Alert.alert("Error", "Failed to fetch group members.");
77116
} finally {
78117
setIsLoading(false);
79118
}
80119
};
81-
loadMembers();
120+
if (groupId) {
121+
fetchMembers();
122+
}
82123
}, [groupId]);
83124

84-
const toNumber = (v) => {
85-
if (v === null || v === undefined) return 0;
86-
const cleaned = String(v).replace(/[^0-9.+-]/g, '').trim();
87-
if (cleaned === '' || cleaned === '.' || cleaned === '-' || cleaned === '+') return 0;
88-
const n = parseFloat(cleaned);
89-
return Number.isFinite(n) ? n : 0;
90-
};
91-
92125
const handleAddExpense = async () => {
93126
if (!description || !amount || !payerId) {
94127
Alert.alert("Error", "Please fill in all required fields.");
95128
return;
96129
}
97-
const numericAmount = toNumber(amount);
98-
if (numericAmount <= 0) {
99-
Alert.alert("Error", "Please enter a valid amount greater than 0.");
130+
const numericAmount = parseFloat(amount);
131+
if (isNaN(numericAmount) || numericAmount <= 0) {
132+
Alert.alert("Error", "Please enter a valid amount.");
100133
return;
101134
}
135+
102136
setIsSubmitting(true);
103137
try {
104138
let splits = [];
105139
if (splitMethod === "equal") {
106-
const includedMembers = Object.keys(selectedMembers).filter((id) => selectedMembers[id]);
140+
const includedMembers = Object.keys(selectedMembers).filter(
141+
(userId) => selectedMembers[userId]
142+
);
107143
if (includedMembers.length === 0)
108144
throw new Error("Select at least one member for the split.");
109-
const base = Math.floor((numericAmount * 100) / includedMembers.length);
110-
const remainder = (numericAmount * 100) - base * includedMembers.length;
111-
splits = includedMembers.map((userId, idx) => ({
145+
const splitAmount =
146+
Math.round((numericAmount / includedMembers.length) * 100) / 100;
147+
const remainder =
148+
Math.round(
149+
(numericAmount - splitAmount * includedMembers.length) * 100
150+
) / 100;
151+
splits = includedMembers.map((userId, index) => ({
112152
userId,
113-
amount: (base + (idx === 0 ? remainder : 0)) / 100,
153+
amount: index === 0 ? splitAmount + remainder : splitAmount,
114154
type: "equal",
115155
}));
116156
} else if (splitMethod === "exact") {
117-
const total = Object.values(exactAmounts).reduce((sum, val) => sum + toNumber(val), 0);
118-
if (!Number.isFinite(total) || Math.abs(total - numericAmount) > 0.01)
157+
const total = Object.values(exactAmounts).reduce(
158+
(sum, val) => sum + parseFloat(val || "0"),
159+
0
160+
);
161+
if (Math.abs(total - numericAmount) > 0.01)
119162
throw new Error("Exact amounts must add up to the total.");
120163
splits = Object.entries(exactAmounts)
121-
.map(([userId, value]) => ({ userId, amount: toNumber(value) }))
122-
.filter((s) => s.amount > 0)
123-
.map((s) => ({ ...s, type: "unequal" }));
124-
const diff = Math.round((numericAmount - splits.reduce((a, b) => a + b.amount, 0)) * 100) / 100;
125-
if (Math.abs(diff) > 0 && splits.length > 0) splits[0].amount += diff;
164+
.filter(([, value]) => parseFloat(value || "0") > 0)
165+
.map(([userId, value]) => ({
166+
userId,
167+
amount: parseFloat(value),
168+
type: "unequal",
169+
}));
126170
} else if (splitMethod === "percentage") {
127-
const totalPercentage = Object.values(percentages).reduce((sum, val) => sum + toNumber(val), 0);
128-
if (!Number.isFinite(totalPercentage) || Math.abs(totalPercentage - 100) > 0.01)
171+
const totalPercentage = Object.values(percentages).reduce(
172+
(sum, val) => sum + parseFloat(val || "0"),
173+
0
174+
);
175+
if (Math.abs(totalPercentage - 100) > 0.01) {
129176
throw new Error("Percentages must add up to 100.");
177+
}
130178
splits = Object.entries(percentages)
131-
.map(([userId, value]) => ({ userId, pct: toNumber(value) }))
132-
.filter((s) => s.pct > 0)
133-
.map((s) => ({
134-
userId: s.userId,
135-
amount: Math.round((numericAmount * (s.pct / 100)) * 100) / 100,
179+
.filter(([, value]) => parseFloat(value || "0") > 0)
180+
.map(([userId, value]) => ({
181+
userId,
182+
amount: (numericAmount * parseFloat(value)) / 100,
136183
type: "percentage",
137184
}));
138-
const diff = Math.round((numericAmount - splits.reduce((a, b) => a + b.amount, 0)) * 100) / 100;
139-
if (Math.abs(diff) > 0 && splits.length > 0) splits[0].amount += diff;
140185
} else if (splitMethod === "shares") {
141-
const totalShares = Object.values(shares).reduce((sum, val) => sum + toNumber(val), 0);
142-
if (!Number.isFinite(totalShares) || totalShares <= 0) throw new Error("Total shares must be positive.");
186+
const totalShares = Object.values(shares).reduce(
187+
(sum, val) => sum + parseFloat(val || "0"),
188+
0
189+
);
190+
if (totalShares === 0) {
191+
throw new Error("Total shares cannot be zero.");
192+
}
143193
splits = Object.entries(shares)
144-
.map(([userId, value]) => ({ userId, shares: toNumber(value) }))
145-
.filter((s) => s.shares > 0)
146-
.map((s) => ({
147-
userId: s.userId,
148-
amount: Math.round((numericAmount * (s.shares / totalShares)) * 100) / 100,
149-
type: "unequal",
194+
.filter(([, value]) => parseFloat(value || "0") > 0)
195+
.map(([userId, value]) => ({
196+
userId,
197+
amount: (numericAmount * parseFloat(value)) / totalShares,
198+
type: "shares",
150199
}));
151-
const diff = Math.round((numericAmount - splits.reduce((a, b) => a + b.amount, 0)) * 100) / 100;
152-
if (Math.abs(diff) > 0 && splits.length > 0) splits[0].amount += diff;
153200
}
154-
const splitTypeMap = { exact: "unequal", shares: "unequal" };
155201
const expenseData = {
156202
description,
157203
amount: numericAmount,
158204
paidBy: payerId,
159-
splitType: splitTypeMap[splitMethod] || splitMethod,
205+
splitType: splitMethod,
160206
splits,
161207
};
162208
await createExpense(groupId, expenseData);
163209
Alert.alert("Success", "Expense added successfully.");
164210
navigation.goBack();
165-
} catch (e) {
166-
Alert.alert("Error", e.message || "Failed to add expense.");
211+
} catch (error) {
212+
Alert.alert("Error", error.message || "Failed to create expense.");
167213
} finally {
168214
setIsSubmitting(false);
169215
}
@@ -190,7 +236,9 @@ const AddExpenseScreen = ({ route, navigation }) => {
190236
key={member.userId}
191237
label={member.user.name}
192238
value={exactAmounts[member.userId]}
193-
onChangeText={(text) => setExactAmounts({ ...exactAmounts, [member.userId]: text })}
239+
onChangeText={(text) =>
240+
setExactAmounts({ ...exactAmounts, [member.userId]: text })
241+
}
194242
/>
195243
));
196244
case "percentage":
@@ -199,7 +247,9 @@ const AddExpenseScreen = ({ route, navigation }) => {
199247
key={member.userId}
200248
label={member.user.name}
201249
value={percentages[member.userId]}
202-
onChangeText={(text) => setPercentages({ ...percentages, [member.userId]: text })}
250+
onChangeText={(text) =>
251+
setPercentages({ ...percentages, [member.userId]: text })
252+
}
203253
isPercentage
204254
/>
205255
));
@@ -209,7 +259,9 @@ const AddExpenseScreen = ({ route, navigation }) => {
209259
key={member.userId}
210260
label={member.user.name}
211261
value={shares[member.userId]}
212-
onChangeText={(text) => setShares({ ...shares, [member.userId]: text })}
262+
onChangeText={(text) =>
263+
setShares({ ...shares, [member.userId]: text })
264+
}
213265
/>
214266
));
215267
default:
@@ -225,16 +277,24 @@ const AddExpenseScreen = ({ route, navigation }) => {
225277
);
226278
}
227279

228-
const selectedPayerName = members.find((m) => m.userId === payerId)?.user.name || "Select Payer";
280+
const selectedPayerName =
281+
members.find((m) => m.userId === payerId)?.user.name || "Select Payer";
229282

230283
return (
231284
<KeyboardAvoidingView
232285
behavior={Platform.OS === "ios" ? "padding" : "height"}
233286
style={styles.container}
234287
>
235288
<Appbar.Header style={{ backgroundColor: colors.primary }}>
236-
<Appbar.BackAction onPress={() => navigation.goBack()} color={colors.white} />
237-
<Appbar.Content title="Add Expense" color={colors.white} titleStyle={{ ...typography.h2 }} />
289+
<Appbar.BackAction
290+
onPress={() => navigation.goBack()}
291+
color={colors.white}
292+
/>
293+
<Appbar.Content
294+
title="Add Expense"
295+
color={colors.white}
296+
titleStyle={{ ...typography.h2 }}
297+
/>
238298
</Appbar.Header>
239299
<ScrollView contentContainerStyle={styles.content}>
240300
<TextInput
@@ -252,30 +312,39 @@ const AddExpenseScreen = ({ route, navigation }) => {
252312
keyboardType="numeric"
253313
theme={{ colors: { primary: colors.accent } }}
254314
/>
315+
255316
<View>
256317
<Text style={styles.label}>Paid by</Text>
257318
<Menu
258319
visible={menuVisible}
259320
onDismiss={() => setMenuVisible(false)}
260321
anchor={
261-
<TouchableOpacity style={styles.menuAnchor} onPress={() => setMenuVisible(true)}>
322+
<TouchableOpacity
323+
style={styles.menuAnchor}
324+
onPress={() => setMenuVisible(true)}
325+
>
262326
<Text style={styles.menuAnchorText}>{selectedPayerName}</Text>
263-
<Ionicons name="chevron-down-outline" size={24} color={colors.textSecondary} />
327+
<Ionicons
328+
name="chevron-down-outline"
329+
size={24}
330+
color={colors.textSecondary}
331+
/>
264332
</TouchableOpacity>
265333
}
266334
>
267-
{members.map((member) => (
268-
<Menu.Item
269-
key={member.userId}
270-
onPress={() => {
271-
setPayerId(member.userId);
272-
setMenuVisible(false);
273-
}}
274-
title={member.user.name}
275-
/>
276-
))}
277-
</Menu>
335+
{members.map((member) => (
336+
<Menu.Item
337+
key={member.userId}
338+
onPress={() => {
339+
setPayerId(member.userId);
340+
setMenuVisible(false);
341+
}}
342+
title={member.user.name}
343+
/>
344+
))}
345+
</Menu>
278346
</View>
347+
279348
<Text style={styles.splitTitle}>Split Method</Text>
280349
<SegmentedButtons
281350
value={splitMethod}
@@ -289,7 +358,9 @@ const AddExpenseScreen = ({ route, navigation }) => {
289358
style={styles.input}
290359
theme={{ colors: { primary: colors.primary } }}
291360
/>
361+
292362
<View style={styles.splitInputsContainer}>{renderSplitInputs()}</View>
363+
293364
<Button
294365
mode="contained"
295366
onPress={handleAddExpense}

0 commit comments

Comments
 (0)