Skip to content

Commit 5692341

Browse files
I've added the Settle Up screen and its functionality.
1 parent 19ca52a commit 5692341

File tree

6 files changed

+226
-0
lines changed

6 files changed

+226
-0
lines changed

frontend/api/groups.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ export const updateMemberRole = (groupId, memberId, role) =>
4545

4646
export const removeMember = (groupId, memberId) =>
4747
apiClient.delete(`/groups/${groupId}/members/${memberId}`);
48+
49+
export const recordSettlement = (groupId, settlementData) =>
50+
apiClient.post(`/groups/${groupId}/settlements`, settlementData);

frontend/navigation/GroupsStackNavigator.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import GroupDetailsScreen from '../screens/GroupDetailsScreen';
44
import GroupSettingsScreen from '../screens/GroupSettingsScreen';
55
import HomeScreen from '../screens/HomeScreen';
66
import JoinGroupScreen from '../screens/JoinGroupScreen';
7+
import SettleUpScreen from '../screens/SettleUpScreen';
78

89
const Stack = createNativeStackNavigator();
910

@@ -15,6 +16,7 @@ const GroupsStackNavigator = () => {
1516
<Stack.Screen name="AddExpense" component={AddExpenseScreen} options={{ headerShown: false }} />
1617
<Stack.Screen name="JoinGroup" component={JoinGroupScreen} options={{ headerShown: false }} />
1718
<Stack.Screen name="GroupSettings" component={GroupSettingsScreen} options={{ headerShown: false }} />
19+
<Stack.Screen name="SettleUp" component={SettleUpScreen} options={{ headerShown: false }} />
1820
</Stack.Navigator>
1921
);
2022
};

frontend/screens/GroupDetailsScreen.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,18 @@ const GroupDetailsScreen = ({ route, navigation }) => {
183183
</Text>
184184
</View>
185185
))}
186+
<TouchableOpacity
187+
style={styles.settleUpButton}
188+
onPress={() =>
189+
navigation.navigate("SettleUp", {
190+
groupId,
191+
settlement: settlements[0],
192+
members,
193+
})
194+
}
195+
>
196+
<Text style={styles.settleUpButtonText}>Settle Up</Text>
197+
</TouchableOpacity>
186198
</View>
187199
)}
188200
</TouchableOpacity>
@@ -355,6 +367,18 @@ const styles = StyleSheet.create({
355367
textAlign: "center",
356368
marginTop: spacing.lg,
357369
},
370+
settleUpButton: {
371+
backgroundColor: colors.primary,
372+
padding: spacing.sm,
373+
borderRadius: spacing.sm,
374+
alignItems: "center",
375+
marginTop: spacing.md,
376+
},
377+
settleUpButtonText: {
378+
...typography.body,
379+
color: colors.white,
380+
fontWeight: "bold",
381+
},
358382
});
359383

360384
export default GroupDetailsScreen;

frontend/screens/SettleUpScreen.js

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { Ionicons } from "@expo/vector-icons";
2+
import { useContext, useState } from "react";
3+
import {
4+
Alert,
5+
StyleSheet,
6+
Text,
7+
TouchableOpacity,
8+
View,
9+
} from "react-native";
10+
import { Button, TextInput } from "react-native-paper";
11+
import RNPickerSelect from "react-native-picker-select";
12+
import { recordSettlement } from "../api/groups";
13+
import { AuthContext } from "../context/AuthContext";
14+
import { colors, spacing, typography } from "../styles/theme";
15+
16+
const SettleUpScreen = ({ route, navigation }) => {
17+
const { groupId, settlement, members } = route.params;
18+
const { token } = useContext(AuthContext);
19+
const [fromUser, setFromUser] = useState(settlement.fromUserId);
20+
const [toUser, setToUser] = useState(settlement.toUserId);
21+
const [amount, setAmount] = useState(settlement.amount.toString());
22+
const [isLoading, setIsLoading] = useState(false);
23+
24+
const memberOptions = members.map((member) => ({
25+
label: member.user.name,
26+
value: member.userId,
27+
}));
28+
29+
const handleRecordPayment = async () => {
30+
if (!fromUser || !toUser || !amount) {
31+
Alert.alert("Error", "Please fill in all fields.");
32+
return;
33+
}
34+
35+
const settlementData = {
36+
payer_id: fromUser,
37+
payee_id: toUser,
38+
amount: parseFloat(amount),
39+
};
40+
41+
try {
42+
setIsLoading(true);
43+
await recordSettlement(groupId, settlementData);
44+
Alert.alert("Success", "Payment recorded successfully.");
45+
navigation.goBack();
46+
} catch (error) {
47+
console.error("Failed to record settlement:", error);
48+
Alert.alert("Error", "Failed to record settlement.");
49+
} finally {
50+
setIsLoading(false);
51+
}
52+
};
53+
54+
return (
55+
<View style={styles.container}>
56+
<View style={styles.header}>
57+
<TouchableOpacity onPress={() => navigation.goBack()}>
58+
<Ionicons name="arrow-back" size={24} color={colors.text} />
59+
</TouchableOpacity>
60+
<Text style={styles.title}>Settle Up</Text>
61+
</View>
62+
<Text style={styles.label}>From</Text>
63+
<RNPickerSelect
64+
onValueChange={(value) => setFromUser(value)}
65+
items={memberOptions}
66+
style={pickerSelectStyles}
67+
value={fromUser}
68+
placeholder={{ label: "Select a member", value: null }}
69+
/>
70+
<Text style={styles.label}>To</Text>
71+
<RNPickerSelect
72+
onValueChange={(value) => setToUser(value)}
73+
items={memberOptions}
74+
style={pickerSelectStyles}
75+
value={toUser}
76+
placeholder={{ label: "Select a member", value: null }}
77+
/>
78+
<TextInput
79+
label="Amount"
80+
value={amount}
81+
onChangeText={setAmount}
82+
keyboardType="numeric"
83+
style={styles.input}
84+
/>
85+
<Button
86+
mode="contained"
87+
onPress={handleRecordPayment}
88+
loading={isLoading}
89+
style={styles.button}
90+
>
91+
Record Payment
92+
</Button>
93+
</View>
94+
);
95+
};
96+
97+
const styles = StyleSheet.create({
98+
container: {
99+
flex: 1,
100+
backgroundColor: colors.secondary,
101+
padding: spacing.md,
102+
},
103+
header: {
104+
flexDirection: "row",
105+
alignItems: "center",
106+
marginBottom: spacing.lg,
107+
},
108+
title: {
109+
...typography.h2,
110+
color: colors.text,
111+
marginLeft: spacing.md,
112+
},
113+
label: {
114+
...typography.body,
115+
color: colors.textSecondary,
116+
marginBottom: spacing.xs,
117+
},
118+
input: {
119+
marginBottom: spacing.md,
120+
},
121+
button: {
122+
marginTop: spacing.md,
123+
},
124+
});
125+
126+
const pickerSelectStyles = StyleSheet.create({
127+
inputIOS: {
128+
fontSize: 16,
129+
paddingVertical: 12,
130+
paddingHorizontal: 10,
131+
borderWidth: 1,
132+
borderColor: "gray",
133+
borderRadius: 4,
134+
color: "black",
135+
paddingRight: 30,
136+
backgroundColor: colors.white,
137+
marginBottom: spacing.md,
138+
},
139+
inputAndroid: {
140+
fontSize: 16,
141+
paddingHorizontal: 10,
142+
paddingVertical: 8,
143+
borderWidth: 0.5,
144+
borderColor: "purple",
145+
borderRadius: 8,
146+
color: "black",
147+
paddingRight: 30,
148+
backgroundColor: colors.white,
149+
marginBottom: spacing.md,
150+
},
151+
});
152+
153+
export default SettleUpScreen;
4.15 KB
Loading
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from playwright.sync_api import sync_playwright, expect
2+
3+
def run(playwright):
4+
browser = playwright.chromium.launch(headless=True)
5+
context = browser.new_context()
6+
page = context.new_page()
7+
8+
try:
9+
# 1. Log in
10+
page.goto("http://localhost:8081", timeout=120000)
11+
12+
# Wait for the app to load
13+
login_button = page.get_by_role("button", name="Login")
14+
expect(login_button).to_be_visible(timeout=120000)
15+
16+
# Use more robust locators for React Native Web
17+
email_input = page.get_by_placeholder("Email")
18+
password_input = page.get_by_placeholder("Password")
19+
20+
expect(email_input).to_be_visible()
21+
email_input.fill("alice@example.com")
22+
23+
expect(password_input).to_be_visible()
24+
password_input.fill("password123")
25+
26+
login_button.click()
27+
28+
# 2. Navigate to the "House Share" group
29+
house_share_group = page.get_by_text("House Share")
30+
expect(house_share_group).to_be_visible(timeout=30000)
31+
32+
# 3. Take a screenshot
33+
page.screenshot(path="jules-scratch/verification/groups_screen.png")
34+
print("Screenshot taken successfully.")
35+
36+
except Exception as e:
37+
print(f"An error occurred: {e}")
38+
page.screenshot(path="jules-scratch/verification/error.png")
39+
40+
finally:
41+
browser.close()
42+
43+
with sync_playwright() as playwright:
44+
run(playwright)

0 commit comments

Comments
 (0)