Files
kandev/apps/web/components/settings/system/feature-toggle-card.tsx
T
João Salavisa 25564efeb8 feat(i18n): localize the remaining Settings → System routes (#2202)
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 17:18:08 +02:00

118 lines
4.4 KiB
TypeScript

"use client";
import { useTranslation } from "react-i18next";
import { Badge } from "@kandev/ui/badge";
import { Button } from "@kandev/ui/button";
import { CardContent, CardHeader, CardTitle } from "@kandev/ui/card";
import { Switch } from "@kandev/ui/switch";
import { IconFlask, IconLock, IconRefresh } from "@tabler/icons-react";
import type { TFunction } from "i18next";
import type { RuntimeFlagState } from "@/lib/types/runtime-flags";
import { SettingsCard } from "@/components/settings/settings-card";
type FeatureToggleCardProps = {
flag: RuntimeFlagState;
isDirty?: boolean;
saving: boolean;
onChange: (next: boolean) => void;
onReset: () => void;
};
export function FeatureToggleCard({
flag,
isDirty = false,
saving,
onChange,
onReset,
}: FeatureToggleCardProps) {
const { t } = useTranslation();
const disabled = saving || flag.env_locked || !flag.mutable;
return (
<SettingsCard isDirty={isDirty} data-testid={`feature-toggle-${flag.key}`}>
<CardHeader className="gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 space-y-2">
{/* `label`, `description` and `risk_description` are authored in
runtimeflags/registry.go and rendered by the backend, so they stay
English here — localizing them needs a key/value split in Go. */}
<CardTitle className="flex flex-wrap items-center gap-2 text-base">
{flag.label}
<FlagBadges flag={flag} />
</CardTitle>
<p className="text-sm text-muted-foreground">{flag.description}</p>
</div>
<Switch
checked={flag.effective_value}
data-settings-dirty={isDirty}
disabled={disabled}
onCheckedChange={onChange}
aria-label={t("system:featureToggleSwitchLabel", { label: flag.label })}
className="cursor-pointer disabled:cursor-not-allowed"
/>
</CardHeader>
<CardContent className="space-y-3">
{flag.risk_description && (
<p className="text-sm leading-6 text-muted-foreground">{flag.risk_description}</p>
)}
<FlagMetadata flag={flag} />
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
<Button
variant="outline"
size="sm"
disabled={saving || flag.env_locked || !flag.mutable || flag.override_value == null}
onClick={onReset}
className="cursor-pointer disabled:cursor-not-allowed"
>
<IconRefresh className="mr-1 h-3.5 w-3.5" />
{t("system:featureToggleUseDefault")}
</Button>
{flag.env_locked && (
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<IconLock className="h-3.5 w-3.5" />
{t("system:featureToggleEnvControlled")}
</span>
)}
</div>
</CardContent>
</SettingsCard>
);
}
function FlagBadges({ flag }: { flag: RuntimeFlagState }) {
const { t } = useTranslation();
return (
<>
{flag.stability === "experimental" && (
<Badge variant="secondary" className="gap-1">
<IconFlask className="h-3 w-3" />
{t("system:featureToggleExperimental")}
</Badge>
)}
{flag.kind === "debug" && <Badge variant="outline">{t("system:featureToggleDebug")}</Badge>}
</>
);
}
function FlagMetadata({ flag }: { flag: RuntimeFlagState }) {
const { t } = useTranslation();
return (
<div className="flex flex-col gap-2 text-xs text-muted-foreground sm:flex-row sm:flex-wrap sm:items-center">
<span>{t("system:featureToggleSource", { source: sourceLabel(flag, t) })}</span>
{/* `env_var` is the process environment variable name — an identifier. */}
<span>{t("system:featureToggleEnv", { envVar: flag.env_var })}</span>
{flag.restart_required && <span>{t("system:featureToggleRequiresRestart")}</span>}
{flag.requires_restart_to_apply && (
<span className="font-medium text-amber-700">
{t("system:featureTogglePendingRestart")}
</span>
)}
</div>
);
}
/** `source` is a wire enum; only its label is copy. */
function sourceLabel(flag: RuntimeFlagState, t: TFunction): string {
if (flag.source === "env") return t("system:featureToggleSourceEnv");
if (flag.source === "override") return t("system:featureToggleSourceOverride");
return t("system:featureToggleSourceDefault");
}