Skip to content

Commit 15288ca

Browse files
authored
Merge pull request #48 from nitishchaubeyy/feat/rate-limit-banner
feat: add global rate limit interceptor and warning banner
2 parents 6245374 + d11c3b1 commit 15288ca

3 files changed

Lines changed: 76 additions & 4 deletions

File tree

src/App.jsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@ import React from "react";
22
import { HashRouter } from "react-router-dom";
33
import { ThemeProvider } from "./context/ThemeContext";
44
import { AuthProvider } from "./context/AuthContext";
5+
import { RateLimitProvider } from "./context/RateLimitContext";
56
import AppRoutes from "./routes/AppRoutes";
67
import ErrorBoundary from "./components/ui/ErrorBoundary";
8+
import RateLimitBanner from "./components/ui/RateLimitBanner";
79

810
function App() {
911
return (
1012
<ErrorBoundary>
1113
<ThemeProvider>
1214
<AuthProvider>
13-
<HashRouter>
14-
<AppRoutes />
15-
</HashRouter>
15+
<RateLimitProvider>
16+
<HashRouter>
17+
<RateLimitBanner />
18+
<AppRoutes />
19+
</HashRouter>
20+
</RateLimitProvider>
1621
</AuthProvider>
1722
</ThemeProvider>
1823
</ErrorBoundary>
1924
);
2025
}
2126

22-
export default App;
27+
export default App;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import { AlertTriangle, X } from 'lucide-react';
3+
import { useRateLimit } from '../../context/RateLimitContext';
4+
5+
const RateLimitBanner = () => {
6+
const { isRateLimited, resetRateLimit } = useRateLimit();
7+
8+
if (!isRateLimited) return null;
9+
10+
return (
11+
<div className="fixed top-0 left-0 right-0 z-[9999] bg-rose-500 text-white p-3 shadow-lg flex items-center justify-between transition-all duration-300">
12+
<div className="flex items-center gap-3 max-w-7xl mx-auto w-full px-4">
13+
<AlertTriangle size={20} className="shrink-0" />
14+
<p className="text-sm md:text-base font-medium">
15+
GitHub API rate limit exceeded! Please wait a while or authenticate to continue tracking stats.
16+
</p>
17+
</div>
18+
<button
19+
onClick={resetRateLimit}
20+
className="shrink-0 p-1.5 hover:bg-rose-600 rounded-md transition-colors mr-2 sm:mr-6"
21+
aria-label="Close warning"
22+
>
23+
<X size={18} />
24+
</button>
25+
</div>
26+
);
27+
};
28+
29+
export default RateLimitBanner;

src/context/RateLimitContext.jsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { createContext, useContext, useState, useEffect } from 'react';
2+
import axios from 'axios';
3+
4+
const RateLimitContext = createContext();
5+
6+
export const useRateLimit = () => useContext(RateLimitContext);
7+
8+
export const RateLimitProvider = ({ children }) => {
9+
const [isRateLimited, setIsRateLimited] = useState(false);
10+
11+
useEffect(() => {
12+
const interceptor = axios.interceptors.response.use(
13+
(response) => response,
14+
(error) => {
15+
if (
16+
error.response &&
17+
error.response.status === 403 &&
18+
error.response.headers['x-ratelimit-remaining'] === '0'
19+
) {
20+
setIsRateLimited(true);
21+
}
22+
return Promise.reject(error);
23+
}
24+
);
25+
26+
return () => {
27+
axios.interceptors.response.eject(interceptor);
28+
};
29+
}, []);
30+
31+
const resetRateLimit = () => setIsRateLimited(false);
32+
33+
return (
34+
<RateLimitContext.Provider value={{ isRateLimited, resetRateLimit }}>
35+
{children}
36+
</RateLimitContext.Provider>
37+
);
38+
};

0 commit comments

Comments
 (0)