fix(frontend/copilot): remove dark mode classes, add 429 handling, use Next.js Link

- Remove all dark: Tailwind classes from copilot UsageLimits component
  and CoPilotUsageSection in credits page (no dark mode in copilot)
- Add 429 rate-limit detection in useCopilotStream onError with toast
  showing the reset time from the backend response
- Replace <a href> with Next.js <Link> in UsageLimits to avoid full
  page reload when navigating to credits page
This commit is contained in:
Zamil Majdy
2026-03-15 08:13:28 +07:00
parent a0d534f24b
commit 6cc2c7a50d
3 changed files with 27 additions and 19 deletions

View File

@@ -6,6 +6,7 @@ import {
} from "@/components/molecules/Popover/Popover";
import { Button } from "@/components/ui/button";
import { ChartBar } from "@phosphor-icons/react";
import Link from "next/link";
import { useUsageLimits } from "./useUsageLimits";
function formatResetTime(resetsAt: Date | string): string {
@@ -55,22 +56,18 @@ function UsageBar({
return (
<div className="flex flex-col gap-1">
<div className="flex items-baseline justify-between">
<span className="text-xs font-medium text-neutral-700 dark:text-neutral-300">
{label}
</span>
<span className="text-[11px] tabular-nums text-neutral-500 dark:text-neutral-400">
<span className="text-xs font-medium text-neutral-700">{label}</span>
<span className="text-[11px] tabular-nums text-neutral-500">
{percentLabel}
</span>
</div>
<div className="text-[10px] text-neutral-400 dark:text-neutral-500">
<div className="text-[10px] text-neutral-400">
Resets {formatResetTime(resetsAt)}
</div>
<div className="h-2 w-full overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-700">
<div className="h-2 w-full overflow-hidden rounded-full bg-neutral-200">
<div
className={`h-full rounded-full transition-[width] duration-300 ease-out ${
isHigh
? "bg-orange-500 dark:bg-orange-400"
: "bg-blue-500 dark:bg-blue-400"
isHigh ? "bg-orange-500" : "bg-blue-500"
}`}
style={{ width: `${Math.max(used > 0 ? 1 : 0, percent)}%` }}
/>
@@ -91,17 +88,13 @@ export function UsagePanelContent({
if (!hasDailyLimit && !hasWeeklyLimit) {
return (
<div className="text-xs text-neutral-500 dark:text-neutral-400">
No usage limits configured
</div>
<div className="text-xs text-neutral-500">No usage limits configured</div>
);
}
return (
<div className="flex flex-col gap-3">
<div className="text-xs font-semibold text-neutral-800 dark:text-neutral-200">
Usage limits
</div>
<div className="text-xs font-semibold text-neutral-800">Usage limits</div>
{hasDailyLimit && (
<UsageBar
label="Today"
@@ -119,12 +112,12 @@ export function UsagePanelContent({
/>
)}
{showBillingLink && (
<a
<Link
href="/profile/credits"
className="text-[11px] text-blue-600 hover:underline dark:text-blue-400"
className="text-[11px] text-blue-600 hover:underline"
>
Learn more about usage limits
</a>
</Link>
)}
</div>
);

View File

@@ -178,6 +178,21 @@ export function useCopilotStream({
onError: (error) => {
if (!sessionId) return;
// Detect rate limit (429) responses and show reset time to the user.
const isRateLimited =
error.message.includes("429") ||
error.message.toLowerCase().includes("usage limit");
if (isRateLimited) {
toast({
title: "Usage limit reached",
description:
error.message.replace(/^[^:]*:\s*/, "") ||
"You've reached your usage limit. Please try again later.",
variant: "destructive",
});
return;
}
// Detect authentication failures (from getAuthHeaders or 401 responses)
const isAuthError =
error.message.includes("Authentication failed") ||

View File

@@ -33,7 +33,7 @@ function CoPilotUsageSection() {
return (
<div className="my-6 space-y-4">
<h3 className="text-lg font-medium">CoPilot Usage Limits</h3>
<div className="rounded-lg border border-neutral-200 p-4 dark:border-neutral-700">
<div className="rounded-lg border border-neutral-200 p-4">
<UsagePanelContent usage={usage} showBillingLink={false} />
</div>
<Button className="w-full" onClick={() => router.push("/copilot")}>