Skip to content

Commit e352420

Browse files
authored
Merge pull request #171 from Mosas2000/feature/rate-limit-tip-submissions
Add rate limiting with cooldown for tip submissions
2 parents e6f9dde + c370591 commit e352420

File tree

1 file changed

+32
-3
lines changed

1 file changed

+32
-3
lines changed

frontend/src/components/SendTip.jsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useMemo } from 'react';
1+
import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
22
import { openContractCall } from '@stacks/connect';
33
import {
44
stringUtf8CV,
@@ -19,6 +19,7 @@ const FEE_BASIS_POINTS = 50;
1919
const BASIS_POINTS_DIVISOR = 10000;
2020
const MIN_TIP_STX = 0.001;
2121
const MAX_TIP_STX = 10000;
22+
const COOLDOWN_SECONDS = 10;
2223

2324
export default function SendTip({ addToast }) {
2425
const { notifyTipSent } = useTipContext();
@@ -30,6 +31,28 @@ export default function SendTip({ addToast }) {
3031
const [pendingTx, setPendingTx] = useState(null);
3132
const [recipientError, setRecipientError] = useState('');
3233
const [amountError, setAmountError] = useState('');
34+
const [cooldown, setCooldown] = useState(0);
35+
const cooldownRef = useRef(null);
36+
37+
useEffect(() => {
38+
return () => {
39+
if (cooldownRef.current) clearInterval(cooldownRef.current);
40+
};
41+
}, []);
42+
43+
const startCooldown = useCallback(() => {
44+
setCooldown(COOLDOWN_SECONDS);
45+
cooldownRef.current = setInterval(() => {
46+
setCooldown(prev => {
47+
if (prev <= 1) {
48+
clearInterval(cooldownRef.current);
49+
cooldownRef.current = null;
50+
return 0;
51+
}
52+
return prev - 1;
53+
});
54+
}, 1000);
55+
}, []);
3356

3457
const senderAddress = useMemo(() => {
3558
try {
@@ -80,6 +103,11 @@ export default function SendTip({ addToast }) {
80103
};
81104

82105
const validateAndConfirm = () => {
106+
if (cooldown > 0) {
107+
addToast(`Please wait ${cooldown}s before sending another tip`, 'warning');
108+
return;
109+
}
110+
83111
if (!recipient || !amount) {
84112
addToast('Please fill in all required fields', 'warning');
85113
return;
@@ -160,6 +188,7 @@ export default function SendTip({ addToast }) {
160188
setMessage('');
161189
notifyTipSent();
162190
refetchBalance();
191+
startCooldown();
163192
addToast('Tip sent successfully! Transaction: ' + data.txId, 'success');
164193
},
165194
onCancel: () => {
@@ -293,7 +322,7 @@ export default function SendTip({ addToast }) {
293322

294323
<button
295324
onClick={validateAndConfirm}
296-
disabled={loading}
325+
disabled={loading || cooldown > 0}
297326
className="w-full bg-gray-900 hover:bg-black text-white font-bold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transform active:scale-95 transition-all disabled:bg-gray-400 disabled:shadow-none"
298327
>
299328
{loading ? (
@@ -304,7 +333,7 @@ export default function SendTip({ addToast }) {
304333
</svg>
305334
Processing...
306335
</span>
307-
) : 'Send Tip'}
336+
) : cooldown > 0 ? `Wait ${cooldown}s` : 'Send Tip'}
308337
</button>
309338
</div>
310339

0 commit comments

Comments
 (0)