Skip to content

Commit 1f5f9a3

Browse files
authored
Merge pull request #384 from Folex1275/backendW4
refactor: simplify rate limiter and enhance frontend error handling
2 parents 0af9f63 + d8bf116 commit 1f5f9a3

3 files changed

Lines changed: 34 additions & 17 deletions

File tree

backend/src/middleware/rateLimiter.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,7 @@ function createRateLimiter(options = {}) {
5353
return limiter;
5454
}
5555

56-
function rateLimiter(req, res, next) {
57-
const clientIP = getClientIP(req);
58-
59-
if (isWhitelisted(clientIP)) {
60-
return next();
61-
}
62-
63-
return createRateLimiter()(req, res, next);
64-
}
56+
const rateLimiter = createRateLimiter();
6557

6658
export { createRateLimiter, getClientIP };
6759

frontend/src/App.jsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,10 @@ import { useAppState, useAppDispatch, A } from './store/index.js';
2929
const STATUS_COLORS = { connected: '#22c55e', disconnected: '#ef4444', reconnecting: '#f59e0b' };
3030
const TIMEOUT_MS = 30000;
3131

32-
function withTimeout(promise) {
33-
return Promise.race([
34-
promise,
35-
new Promise((_, reject) =>
36-
setTimeout(() => reject(new Error('Request timed out. Please try again.')), TIMEOUT_MS)
37-
),
38-
]);
32+
function withTimeout(promiseFn) {
33+
const controller = new AbortController();
34+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
35+
return promiseFn(controller.signal).finally(() => clearTimeout(timer));
3936
}
4037

4138
function App() {
@@ -145,6 +142,8 @@ function App() {
145142

146143
const createAccount = async () => {
147144
try {
145+
const { data } = await withTimeout(signal => axios.post('/api/stellar/account/create', null, { signal }));
146+
setAccount(data);
148147
const { data } = await withTimeout(axios.post('/api/stellar/account/create'));
149148
dispatch({ type: A.SET_ACCOUNT, payload: data });
150149
resetForm();
@@ -172,6 +171,8 @@ function App() {
172171
if (!account) return;
173172
setLoading('balance');
174173
try {
174+
const { data } = await withTimeout(signal => axios.get(`/api/stellar/account/${account.publicKey}`, { signal }));
175+
setBalance(data);
175176
const { data } = await withTimeout(axios.get(`/api/stellar/account/${account.publicKey}`));
176177
dispatch({ type: A.SET_BALANCE, payload: data });
177178
} catch (error) {
@@ -203,6 +204,7 @@ function App() {
203204
}
204205

205206
try {
207+
const { data } = await withTimeout(signal => axios.post('/api/stellar/payment/send', payload, { signal }));
206208
const { data } = await withTimeout(axios.post('/api/stellar/payment/send', payload));
207209
msg.success(`Payment sent! Hash: ${data.hash}`);
208210
resetForm();

frontend/src/components/TransactionHistory.jsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,15 @@ export function TransactionHistory({ publicKey }) {
9797
const [loaded, setLoaded] = useState(false);
9898
const [selected, setSelected] = useState(null);
9999
const [filters, setFilters] = useState({ type: '', dateFrom: '', dateTo: '' });
100+
const [cursors, setCursors] = useState([]); // ring-buffer for back-pagination (max 50)
101+
const [error, setError] = useState(null);
102+
103+
const MAX_CURSOR_HISTORY = 50;
100104
const [cursors, setCursors] = useState([]);
101105

102106
const fetchPage = useCallback(async (cursor = null, isBack = false) => {
103107
setLoading(true);
108+
setError(null);
104109
try {
105110
const params = { limit: PAGE_SIZE, ...(cursor ? { cursor } : {}) };
106111
if (filters.type) params.type = filters.type;
@@ -110,6 +115,18 @@ export function TransactionHistory({ publicKey }) {
110115
setTxs(data.records);
111116
setNextCursor(data.nextCursor);
112117
setLoaded(true);
118+
119+
if (!isBack && cursor) {
120+
setCursors(prev => {
121+
const next = [...prev, cursor];
122+
return next.length > MAX_CURSOR_HISTORY ? next.slice(next.length - MAX_CURSOR_HISTORY) : next;
123+
});
124+
}
125+
} catch (e) {
126+
setError(e?.response?.data?.error ?? e?.message ?? 'Failed to load transactions.');
127+
} finally {
128+
setLoading(false);
129+
}
113130
if (!isBack && cursor) setCursors(prev => [...prev, cursor]);
114131
} catch { /* errors handled by parent */ }
115132
finally { setLoading(false); }
@@ -171,7 +188,13 @@ export function TransactionHistory({ publicKey }) {
171188
</form>
172189

173190
<AnimatePresence mode="wait">
174-
{loaded && (
191+
{error && (
192+
<motion.div key="error" className="tx-error" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
193+
<p>{error}</p>
194+
<button className="tx-page-btn" onClick={() => fetchPage(cursors[cursors.length - 1] ?? null)}>↺ Retry</button>
195+
</motion.div>
196+
)}
197+
{!error && loaded && (
175198
<motion.div key="list" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
176199
{txs.length === 0 ? (
177200
<p className="tx-empty" role="status">No transactions found.</p>

0 commit comments

Comments
 (0)