Skip to content

Commit 627224b

Browse files
authored
Extract provider status refresh button (#459)
- Add a reusable refresh control with busy-state text and spin animation - Cover idle and refreshing states with component tests - Replace the inline settings action with the new component
1 parent 0695d5c commit 627224b

3 files changed

Lines changed: 57 additions & 5 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { renderToStaticMarkup } from "react-dom/server";
2+
import { describe, expect, it, vi } from "vitest";
3+
4+
import { ProviderStatusRefreshButton } from "./ProviderStatusRefreshButton";
5+
6+
describe("ProviderStatusRefreshButton", () => {
7+
it("shows an idle refresh control without a busy indicator", () => {
8+
const markup = renderToStaticMarkup(
9+
<ProviderStatusRefreshButton refreshing={false} onRefresh={vi.fn()} />,
10+
);
11+
12+
expect(markup).toContain("Refresh status");
13+
expect(markup).toContain('aria-busy="false"');
14+
expect(markup).not.toContain("animate-spin");
15+
expect(markup).not.toContain("disabled=");
16+
});
17+
18+
it("shows an animated busy state while refresh is in flight", () => {
19+
const markup = renderToStaticMarkup(
20+
<ProviderStatusRefreshButton refreshing onRefresh={vi.fn()} />,
21+
);
22+
23+
expect(markup).toContain("Refreshing status");
24+
expect(markup).toContain('aria-busy="true"');
25+
expect(markup).toContain("animate-spin");
26+
expect(markup).not.toContain("disabled=");
27+
});
28+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { RefreshCwIcon } from "lucide-react";
2+
3+
import { cn } from "../../lib/utils";
4+
import { Button } from "../ui/button";
5+
6+
interface ProviderStatusRefreshButtonProps {
7+
refreshing: boolean;
8+
onRefresh: () => void;
9+
}
10+
11+
export function ProviderStatusRefreshButton({
12+
refreshing,
13+
onRefresh,
14+
}: ProviderStatusRefreshButtonProps) {
15+
return (
16+
<Button size="sm" variant="outline" type="button" aria-busy={refreshing} onClick={onRefresh}>
17+
<RefreshCwIcon
18+
className={cn("size-3.5 transition-transform duration-500", refreshing && "animate-spin")}
19+
/>
20+
{refreshing ? "Refreshing status" : "Refresh status"}
21+
</Button>
22+
);
23+
}

apps/web/src/routes/_chat.settings.index.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
ChevronDownIcon,
66
Loader2Icon,
77
PlusIcon,
8-
RefreshCwIcon,
98
SkipForwardIcon,
109
XCircleIcon,
1110
XIcon,
@@ -39,6 +38,7 @@ import { Button } from "../components/ui/button";
3938
import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
4039
import { EnvironmentVariablesEditor } from "../components/EnvironmentVariablesEditor";
4140
import { HotkeysSettingsSection } from "../components/settings/HotkeysSettingsSection";
41+
import { ProviderStatusRefreshButton } from "../components/settings/ProviderStatusRefreshButton";
4242
import { SettingsShell, type SettingsSectionId } from "../components/settings/SettingsShell";
4343
import { useSettingsRouteContext } from "../components/settings/SettingsRouteContext";
4444
import {
@@ -471,6 +471,7 @@ function SettingsRouteView() {
471471
const keybindingsConfigPath = serverConfigQuery.data?.keybindingsConfigPath ?? null;
472472
const availableEditors = serverConfigQuery.data?.availableEditors;
473473
const providerStatuses = serverConfigQuery.data?.providers ?? [];
474+
const isRefreshingProviderStatuses = serverConfigQuery.isFetching;
474475
const selectableProviders = getSelectableThreadProviders({
475476
statuses: providerStatuses,
476477
openclawGatewayUrl: settings.openclawGatewayUrl,
@@ -1330,10 +1331,10 @@ function SettingsRouteView() {
13301331
title="Authentication"
13311332
description="Only providers that are ready and authenticated enough to run will appear in the new-thread provider picker. Existing threads remain pinned to their current provider."
13321333
actions={
1333-
<Button size="sm" variant="outline" onClick={() => void refreshProviderStatuses()}>
1334-
<RefreshCwIcon className="size-3.5" />
1335-
Refresh status
1336-
</Button>
1334+
<ProviderStatusRefreshButton
1335+
refreshing={isRefreshingProviderStatuses}
1336+
onRefresh={() => void refreshProviderStatuses()}
1337+
/>
13371338
}
13381339
>
13391340
<SettingsRow

0 commit comments

Comments
 (0)