Skip to content

Commit df20f03

Browse files
committed
Refactor screens for improved readability and consistency
- Updated import statements to use consistent formatting across GroupDetailsScreen, GroupSettingsScreen, HomeScreen, and JoinGroupScreen. - Changed string literals to use double quotes for consistency. - Simplified API calls by removing unnecessary token parameters in group-related API functions. - Enhanced error handling and user feedback with clearer alert messages. - Improved layout and styling for better user experience in various components. - Refactored settlement status calculations and rendering logic for clarity.
1 parent 5da289e commit df20f03

File tree

10 files changed

+1035
-748
lines changed

10 files changed

+1035
-748
lines changed

frontend/api/auth.js

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
1-
import axios from 'axios';
2-
3-
const API_URL = 'https://splitwiser-production.up.railway.app';
4-
5-
const apiClient = axios.create({
6-
baseURL: API_URL,
7-
headers: {
8-
'Content-Type': 'application/json',
9-
},
10-
});
1+
import { apiClient } from "./client";
112

123
export const login = (email, password) => {
13-
return apiClient.post('/auth/login/email', { email, password });
4+
return apiClient.post("/auth/login/email", { email, password });
145
};
156

167
export const signup = (name, email, password) => {
17-
return apiClient.post('/auth/signup/email', { name, email, password });
8+
return apiClient.post("/auth/signup/email", { name, email, password });
189
};
1910

2011
export const updateUser = (token, userData) => {
21-
return apiClient.patch('/user/', userData, {
22-
headers: {
23-
Authorization: `Bearer ${token}`,
24-
},
25-
});
12+
return apiClient.patch("/user/", userData);
13+
};
14+
15+
export const refresh = (refresh_token) => {
16+
return apiClient.post("/auth/refresh", { refresh_token });
2617
};

frontend/api/client.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import axios from "axios";
2+
3+
const API_URL = "https://splitwiser-production.up.railway.app";
4+
5+
let accessToken = null;
6+
let refreshToken = null;
7+
let isRefreshing = false;
8+
let refreshPromise = null;
9+
let tokenUpdateListener = null; // function({ accessToken, refreshToken })
10+
11+
export const setTokenUpdateListener = (fn) => {
12+
tokenUpdateListener = fn;
13+
};
14+
15+
export const setAuthTokens = async ({ newAccessToken, newRefreshToken }) => {
16+
if (newAccessToken) accessToken = newAccessToken;
17+
if (newRefreshToken) refreshToken = newRefreshToken;
18+
};
19+
20+
export const clearAuthTokens = async () => {
21+
accessToken = null;
22+
refreshToken = null;
23+
};
24+
25+
export const getAccessToken = () => accessToken;
26+
export const getRefreshToken = () => refreshToken;
27+
28+
export const apiClient = axios.create({
29+
baseURL: API_URL,
30+
headers: { "Content-Type": "application/json" },
31+
});
32+
33+
// Attach Authorization header
34+
apiClient.interceptors.request.use((config) => {
35+
if (accessToken && !config.headers?.Authorization) {
36+
config.headers = config.headers || {};
37+
config.headers.Authorization = `Bearer ${accessToken}`;
38+
}
39+
return config;
40+
});
41+
42+
async function performRefresh() {
43+
// Avoid multiple refresh calls
44+
if (isRefreshing && refreshPromise) return refreshPromise;
45+
isRefreshing = true;
46+
refreshPromise = (async () => {
47+
try {
48+
if (!refreshToken) throw new Error("No refresh token");
49+
const resp = await axios.post(
50+
`${API_URL}/auth/refresh`,
51+
{ refresh_token: refreshToken },
52+
{ headers: { "Content-Type": "application/json" } }
53+
);
54+
const { access_token, refresh_token } = resp.data || {};
55+
accessToken = access_token || accessToken;
56+
refreshToken = refresh_token || refreshToken;
57+
// Notify listener (AuthContext) so it can persist & update state
58+
if (tokenUpdateListener) {
59+
tokenUpdateListener({ accessToken, refreshToken });
60+
}
61+
return accessToken;
62+
} finally {
63+
isRefreshing = false;
64+
const p = refreshPromise; // preserve for awaiting callers
65+
refreshPromise = null;
66+
return p; // not used
67+
}
68+
})();
69+
return refreshPromise;
70+
}
71+
72+
// Retry logic on 401
73+
apiClient.interceptors.response.use(
74+
(response) => response,
75+
async (error) => {
76+
const originalRequest = error.config || {};
77+
const status = error.response?.status;
78+
79+
// Avoid refresh loop
80+
const isAuthRefreshCall = originalRequest.url?.includes("/auth/refresh");
81+
82+
if (status === 401 && !originalRequest._retry && !isAuthRefreshCall) {
83+
originalRequest._retry = true;
84+
try {
85+
await performRefresh();
86+
// Set new Authorization and retry
87+
originalRequest.headers = originalRequest.headers || {};
88+
if (accessToken)
89+
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
90+
return apiClient(originalRequest);
91+
} catch (e) {
92+
// Propagate original error; caller should handle logout
93+
return Promise.reject(error);
94+
}
95+
}
96+
return Promise.reject(error);
97+
}
98+
);

frontend/api/groups.js

Lines changed: 30 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,47 @@
1-
import axios from 'axios';
2-
3-
const API_URL = 'https://splitwiser-production.up.railway.app';
4-
5-
// This creates a new axios instance.
6-
// It's better to have a single instance that can be configured with interceptors.
7-
// I will create a single apiClient in a separate file later if needed.
8-
const apiClient = axios.create({
9-
baseURL: API_URL,
10-
headers: {
11-
'Content-Type': 'application/json',
12-
},
13-
});
14-
15-
export const getGroups = (token) => {
16-
return apiClient.get('/groups', {
17-
headers: {
18-
Authorization: `Bearer ${token}`,
19-
},
20-
});
21-
};
1+
import { apiClient } from "./client";
222

23-
export const getOptimizedSettlements = (token, groupId) => {
24-
return apiClient.post(`/groups/${groupId}/settlements/optimize`, {}, {
25-
headers: {
26-
Authorization: `Bearer ${token}`,
27-
},
28-
});
29-
};
3+
export const getGroups = () => apiClient.get("/groups");
304

31-
export const createExpense = (token, groupId, expenseData) => {
32-
return apiClient.post(`/groups/${groupId}/expenses`, expenseData, {
33-
headers: {
34-
Authorization: `Bearer ${token}`,
35-
},
36-
});
37-
};
5+
export const getOptimizedSettlements = (groupId) =>
6+
apiClient.post(`/groups/${groupId}/settlements/optimize`, {});
387

39-
export const getGroupDetails = (token, groupId) => {
40-
return Promise.all([
41-
getGroupMembers(token, groupId),
42-
getGroupExpenses(token, groupId),
43-
]);
44-
};
8+
export const createExpense = (groupId, expenseData) =>
9+
apiClient.post(`/groups/${groupId}/expenses`, expenseData);
4510

46-
export const getGroupMembers = (token, groupId) => {
47-
return apiClient.get(`/groups/${groupId}/members`, {
48-
headers: {
49-
Authorization: `Bearer ${token}`,
50-
},
51-
});
11+
export const getGroupDetails = (groupId) => {
12+
return Promise.all([getGroupMembers(groupId), getGroupExpenses(groupId)]);
5213
};
5314

54-
export const getGroupExpenses = (token, groupId) => {
55-
return apiClient.get(`/groups/${groupId}/expenses`, {
56-
headers: {
57-
Authorization: `Bearer ${token}`,
58-
},
59-
});
60-
};
15+
export const getGroupMembers = (groupId) =>
16+
apiClient.get(`/groups/${groupId}/members`);
6117

62-
export const createGroup = (token, name) => {
63-
return apiClient.post('/groups', { name }, {
64-
headers: {
65-
Authorization: `Bearer ${token}`,
66-
},
67-
});
68-
};
18+
export const getGroupExpenses = (groupId) =>
19+
apiClient.get(`/groups/${groupId}/expenses`);
6920

70-
export const joinGroup = (token, joinCode) => {
71-
return apiClient.post('/groups/join', { joinCode }, {
72-
headers: {
73-
Authorization: `Bearer ${token}`,
74-
},
75-
});
76-
};
21+
export const createGroup = (name) => apiClient.post("/groups", { name });
7722

78-
export const getUserBalanceSummary = (token) => {
79-
return apiClient.get('/users/me/balance-summary', {
80-
headers: {
81-
Authorization: `Bearer ${token}`,
82-
},
83-
});
84-
};
23+
export const joinGroup = (joinCode) =>
24+
apiClient.post("/groups/join", { joinCode });
8525

86-
export const getFriendsBalance = (token) => {
87-
return apiClient.get('/users/me/friends-balance', {
88-
headers: {
89-
Authorization: `Bearer ${token}`,
90-
},
91-
});
92-
};
26+
export const getUserBalanceSummary = () =>
27+
apiClient.get("/users/me/balance-summary");
28+
29+
export const getFriendsBalance = () =>
30+
apiClient.get("/users/me/friends-balance");
9331

9432
// New APIs for Group Settings
95-
export const getGroupById = (token, groupId) => {
96-
return apiClient.get(`/groups/${groupId}`, {
97-
headers: {
98-
Authorization: `Bearer ${token}`,
99-
},
100-
});
101-
};
33+
export const getGroupById = (groupId) => apiClient.get(`/groups/${groupId}`);
10234

103-
export const updateGroup = (token, groupId, updates) => {
104-
return apiClient.patch(`/groups/${groupId}`, updates, {
105-
headers: {
106-
Authorization: `Bearer ${token}`,
107-
},
108-
});
109-
};
35+
export const updateGroup = (groupId, updates) =>
36+
apiClient.patch(`/groups/${groupId}`, updates);
11037

111-
export const deleteGroup = (token, groupId) => {
112-
return apiClient.delete(`/groups/${groupId}`, {
113-
headers: {
114-
Authorization: `Bearer ${token}`,
115-
},
116-
});
117-
};
38+
export const deleteGroup = (groupId) => apiClient.delete(`/groups/${groupId}`);
11839

119-
export const leaveGroup = (token, groupId) => {
120-
return apiClient.post(`/groups/${groupId}/leave`, {}, {
121-
headers: {
122-
Authorization: `Bearer ${token}`,
123-
},
124-
});
125-
};
40+
export const leaveGroup = (groupId) =>
41+
apiClient.post(`/groups/${groupId}/leave`, {});
12642

127-
export const updateMemberRole = (token, groupId, memberId, role) => {
128-
return apiClient.patch(`/groups/${groupId}/members/${memberId}`, { role }, {
129-
headers: {
130-
Authorization: `Bearer ${token}`,
131-
},
132-
});
133-
};
43+
export const updateMemberRole = (groupId, memberId, role) =>
44+
apiClient.patch(`/groups/${groupId}/members/${memberId}`, { role });
13445

135-
export const removeMember = (token, groupId, memberId) => {
136-
return apiClient.delete(`/groups/${groupId}/members/${memberId}`, {
137-
headers: {
138-
Authorization: `Bearer ${token}`,
139-
},
140-
});
141-
};
46+
export const removeMember = (groupId, memberId) =>
47+
apiClient.delete(`/groups/${groupId}/members/${memberId}`);

0 commit comments

Comments
 (0)