diff --git a/app/api/n8n/status/route.ts b/app/api/n8n/status/route.ts index b8a6c91..34ff34a 100644 --- a/app/api/n8n/status/route.ts +++ b/app/api/n8n/status/route.ts @@ -6,10 +6,21 @@ export const revalidate = 30; export async function GET(request: NextRequest) { // Rate limiting for n8n status endpoint - const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'; + const ip = + request.headers.get("x-forwarded-for") || + request.headers.get("x-real-ip") || + "unknown"; + const ua = request.headers.get("user-agent") || "unknown"; const { checkRateLimit } = await import('@/lib/auth'); - if (!checkRateLimit(ip, 30, 60000)) { // 30 requests per minute for status + // In dev, many requests can share ip=unknown; use UA to avoid a shared bucket. + const rateKey = + process.env.NODE_ENV === "development" && ip === "unknown" + ? `ua:${ua.slice(0, 120)}` + : ip; + const maxPerMinute = process.env.NODE_ENV === "development" ? 300 : 30; + + if (!checkRateLimit(rateKey, maxPerMinute, 60000)) { // requests per minute return NextResponse.json( { error: 'Rate limit exceeded. Please try again later.' }, { status: 429 } diff --git a/app/components/ActivityFeed.tsx b/app/components/ActivityFeed.tsx index 4c722b6..a280bc1 100644 --- a/app/components/ActivityFeed.tsx +++ b/app/components/ActivityFeed.tsx @@ -84,8 +84,17 @@ export default function ActivityFeed() { const fetchData = async () => { try { + const fallback: StatusData = { + status: { text: "offline", color: "gray" }, + music: null, + gaming: null, + coding: null, + }; + // Check if fetch is available (should be, but safety check) if (typeof fetch === 'undefined') { + setData(fallback); + setHasActivity(false); return; } @@ -104,6 +113,9 @@ export default function ActivityFeed() { if (process.env.NODE_ENV === 'development' && res) { console.warn('ActivityFeed: API returned non-OK status:', res.status); } + // Don't stay in tiny "loading" state forever; show stable fallback UI. + setData(fallback); + setHasActivity(false); return; } @@ -114,6 +126,8 @@ export default function ActivityFeed() { if (process.env.NODE_ENV === 'development') { console.warn('ActivityFeed: Failed to parse JSON response:', parseError); } + setData(fallback); + setHasActivity(false); return; } @@ -131,6 +145,8 @@ export default function ActivityFeed() { } if (!json || typeof json !== 'object') { + setData(fallback); + setHasActivity(false); return; } @@ -168,7 +184,14 @@ export default function ActivityFeed() { if (process.env.NODE_ENV === 'development') { console.error("Failed to fetch activity:", error); } - // Don't set error state - just fail silently + // Don't set error state - show stable fallback + setData({ + status: { text: "offline", color: "gray" }, + music: null, + gaming: null, + coding: null, + }); + setHasActivity(false); } };