picoclaw/web/frontend/src/components/credentials/device-code-sheet.tsx
wenjie ba5eb6abf0 fix(frontend): polish model and credential UX; remove Providers nav
- remove the Providers item from sidebar navigation and locale keys
- simplify chat composer by dropping attach/voice action buttons
- support ReactNode titles in credential cards and add provider brand icons
- refine sheet header/footer styling and device-code footer button hierarchy
- disable “Set default” when a model is unconfigured or already default
2026-03-09 19:24:44 +08:00

97 lines
3 KiB
TypeScript

import { IconRefresh } from "@tabler/icons-react"
import { useTranslation } from "react-i18next"
import type { OAuthFlowState } from "@/api/oauth"
import { Button } from "@/components/ui/button"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet"
interface DeviceCodeSheetProps {
open: boolean
flow: OAuthFlowState | null
flowHint: string
onOpenChange: (open: boolean) => void
}
export function DeviceCodeSheet({
open,
flow,
flowHint,
onOpenChange,
}: DeviceCodeSheetProps) {
const { t } = useTranslation()
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent
side="right"
className="data-[side=right]:!w-full data-[side=right]:sm:!w-[480px] data-[side=right]:sm:!max-w-[480px]"
>
<SheetHeader className="border-b-muted border-b px-6 py-5">
<SheetTitle>
{t("credentials.device.title", "OpenAI Device Login")}
</SheetTitle>
<SheetDescription>
{t(
"credentials.device.description",
"Open the verification page and enter the code below. This page will refresh automatically.",
)}
</SheetDescription>
</SheetHeader>
<div className="space-y-4 px-6 py-5">
<div>
<p className="text-muted-foreground text-xs uppercase">
{t("credentials.device.code", "User Code")}
</p>
<p className="mt-1 rounded-md border px-3 py-2 font-mono text-lg font-semibold tracking-wide">
{flow?.user_code || "-"}
</p>
</div>
<div>
<p className="text-muted-foreground text-xs uppercase">
{t("credentials.device.url", "Verification URL")}
</p>
<a
href={flow?.verify_url || "#"}
target="_blank"
rel="noreferrer"
className="text-primary mt-1 block text-sm break-all underline"
>
{flow?.verify_url || "-"}
</a>
</div>
<div className="text-muted-foreground flex items-center gap-2 text-sm">
<IconRefresh className="size-4" />
{t("credentials.device.polling", "Polling login status...")}
</div>
{flow && (
<div className="bg-muted rounded-md border px-3 py-2 text-sm">
{flowHint}
</div>
)}
</div>
<SheetFooter className="border-t-muted border-t px-6 py-4">
<Button variant="ghost" onClick={() => onOpenChange(false)}>
{t("common.cancel", "Close")}
</Button>
<Button asChild disabled={!flow?.verify_url}>
<a href={flow?.verify_url || "#"} target="_blank" rel="noreferrer">
{t("credentials.device.open", "Open Verification Page")}
</a>
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
)
}