Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export default function NoAuthProviderControls({

return (
<NoAuthProviderCard
providerId={providerId}
enabled={enabled}
saving={savingEnabled}
onEnabledChange={handleEnabledChange}
Expand Down
6 changes: 5 additions & 1 deletion src/shared/components/NoAuthAccountCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Card from "./Card";
import Button from "./Button";
import DistributeProxiesButton from "./DistributeProxiesButton";
import NoAuthProviderToggle from "./NoAuthProviderToggle";
import { resolveNoAuthBannerDescription } from "./NoAuthProviderCard";

interface NoAuthAccountCardProps {
providerId: string;
Expand Down Expand Up @@ -257,7 +258,10 @@ export default function NoAuthAccountCard({
onManualApiKeyAdd,
}: NoAuthAccountCardProps) {
const t = useTranslations("noAuthProvider");
const resolvedDescription = description || t("accountDescription");
const resolvedDescription = resolveNoAuthBannerDescription(
providerId,
description || t("accountDescription")
);
const resolvedAddLabel = addLabel || t("addAccount");
const [connections, setConnections] = useState<Connection[]>([]);
const [loading, setLoading] = useState(true);
Expand Down
21 changes: 20 additions & 1 deletion src/shared/components/NoAuthProviderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,42 @@

import { useTranslations } from "next-intl";
import type { ReactNode } from "react";
import { NOAUTH_PROVIDERS } from "@/shared/constants/providers/noauth";
import Card from "./Card";
import NoAuthProviderToggle from "./NoAuthProviderToggle";

interface NoAuthProviderCardProps {
providerId?: string;
enabled?: boolean;
saving?: boolean;
onEnabledChange?: (enabled: boolean) => void;
providerProxyControl?: ReactNode;
}

export function resolveNoAuthBannerDescription(
providerId: string | undefined,
fallback: string
): string {
const provider =
providerId && providerId in NOAUTH_PROVIDERS
? NOAUTH_PROVIDERS[providerId as keyof typeof NOAUTH_PROVIDERS]
: undefined;
const requirement =
provider && "notice" in provider && typeof provider.notice?.text === "string"
? provider.notice.text.trim()
: undefined;
return requirement ? `${fallback} ${requirement}` : fallback;
}

export default function NoAuthProviderCard({
providerId,
enabled = true,
saving = false,
onEnabledChange,
providerProxyControl,
}: NoAuthProviderCardProps) {
const t = useTranslations("noAuthProvider");
const description = resolveNoAuthBannerDescription(providerId, t("description"));

return (
<Card>
Expand All @@ -29,7 +48,7 @@ export default function NoAuthProviderCard({
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium">{t("title")}</p>
<p className="text-xs text-text-muted">{t("description")}</p>
<p className="text-xs text-text-muted">{description}</p>
</div>
</div>
<div className="flex w-full flex-wrap items-center justify-end gap-2 sm:w-auto sm:flex-nowrap">
Expand Down
52 changes: 52 additions & 0 deletions tests/unit/noauth-banner-requirement.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import test from "node:test";
import assert from "node:assert/strict";
import React from "react";
import { renderToStaticMarkup as reactRenderToStaticMarkup } from "react-dom/server";
import { NextIntlClientProvider } from "next-intl";
import enMessages from "../../src/i18n/messages/en.json" with { type: "json" };
import { NOAUTH_PROVIDERS } from "../../src/shared/constants/providers/noauth.ts";
import NoAuthProviderCard from "../../src/shared/components/NoAuthProviderCard.tsx";
import NoAuthAccountCard from "../../src/shared/components/NoAuthAccountCard.tsx";

const PLAIN = enMessages.noAuthProvider.description;

function renderBanner(providerId: keyof typeof NOAUTH_PROVIDERS) {
return reactRenderToStaticMarkup(
React.createElement(
NextIntlClientProvider,
{ locale: "en", timeZone: "UTC", messages: { noAuthProvider: enMessages.noAuthProvider } },
React.createElement(NoAuthProviderCard, { providerId, enabled: true })
)
);
}

test("a no-auth banner includes the provider notice when one is stored", () => {
const html = renderBanner("cloudflare-playground");

assert.match(html, /ready to use immediately/);
assert.match(html, /Requires Playwright with a Chromium browser on first request/);
});

test("a no-auth banner stays the plain ready message when the provider has no notice or auth hint", () => {
const html = renderBanner("duckduckgo-web");

assert.match(html, new RegExp(PLAIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
assert.doesNotMatch(html, /DuckDuckGo AI Chat is anonymous/);
});

test("an account no-auth banner includes the stored notice beside the account message", () => {
const html = reactRenderToStaticMarkup(
React.createElement(
NextIntlClientProvider,
{ locale: "en", timeZone: "UTC", messages: { noAuthProvider: enMessages.noAuthProvider } },
React.createElement(NoAuthAccountCard, {
providerId: "opencode",
providerName: "OpenCode",
generateAccountId: () => "account-1",
})
)
);

assert.match(html, /Ready to use/);
assert.match(html, /requests that do not match the OpenCode client contract/);
});
Loading