feat(settings): add 'warning' state to IntegrationStatusPill for persistent failures

The pill previously collapsed both error_transient and error_reauth onto the same red 'reconnect' chip. The v1.4.42 persistent failure kind (Withings 293 / contract mismatch) maps to error_transient but reauth won't fix it. Adds a fourth pill state — warning — with an amber dracula-yellow chip and locale-aware copy ('Verbunden, aber Serverfehler' / 'Connected, server error'). Wiring at the call-site is deferred so the persistent kind can flow through the API payload first.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 21:45:45 +02:00
1 parent 852c1a242a
commit 58f484ad08
7 files changed
+36 -16

No files matched your search

+1
View File
@@ -1892,6 +1892,7 @@
"integrationPill": {
"connected": "Verbunden",
"errorReconnect": "Fehler — neu verbinden",
"warningServerError": "Verbunden, aber Serverfehler",
"notConnected": "Nicht verbunden",
"justNow": "gerade eben",
"minutesAgo": "vor {count} min",
+1
View File
@@ -1892,6 +1892,7 @@
"integrationPill": {
"connected": "Connected",
"errorReconnect": "Error — reconnect",
"warningServerError": "Connected, server error",
"notConnected": "Not connected",
"justNow": "just now",
"minutesAgo": "{count} min ago",
+4 -3
View File
@@ -1890,9 +1890,10 @@
}
},
"integrationPill": {
"connected": "Connected",
"errorReconnect": "Error — reconnect",
"notConnected": "Not connected",
"connected": "Conectado",
"errorReconnect": "Error — reconectar",
"warningServerError": "Conectado, error del servidor",
"notConnected": "No conectado",
"justNow": "just now",
"minutesAgo": "{count} min ago",
"hoursAgo": "{count} h ago",
+4 -3
View File
@@ -1890,9 +1890,10 @@
}
},
"integrationPill": {
"connected": "Connected",
"errorReconnect": "Error — reconnect",
"notConnected": "Not connected",
"connected": "Connecté",
"errorReconnect": "Erreur — reconnecter",
"warningServerError": "Connecté, erreur serveur",
"notConnected": "Non connecté",
"justNow": "just now",
"minutesAgo": "{count} min ago",
"hoursAgo": "{count} h ago",
+4 -3
View File
@@ -1890,9 +1890,10 @@
}
},
"integrationPill": {
"connected": "Connected",
"errorReconnect": "Error — reconnect",
"notConnected": "Not connected",
"connected": "Connesso",
"errorReconnect": "Errore — riconnetti",
"warningServerError": "Connesso, errore del server",
"notConnected": "Non connesso",
"justNow": "just now",
"minutesAgo": "{count} min ago",
"hoursAgo": "{count} h ago",
+4 -3
View File
@@ -1890,9 +1890,10 @@
}
},
"integrationPill": {
"connected": "Connected",
"errorReconnect": "Error — reconnect",
"notConnected": "Not connected",
"connected": "Połączono",
"errorReconnect": "Błąd — połącz ponownie",
"warningServerError": "Połączono, błąd serwera",
"notConnected": "Nie połączono",
"justNow": "just now",
"minutesAgo": "{count} min ago",
"hoursAgo": "{count} h ago",
@@ -26,7 +26,11 @@ import { cn } from "@/lib/utils";
* abbreviated time form into a 12 char string at most.
*/
export type IntegrationPillState = "connected" | "error" | "disconnected";
export type IntegrationPillState =
| "connected"
| "warning"
| "error"
| "disconnected";
interface IntegrationStatusPillProps {
state: IntegrationPillState;
@@ -74,10 +78,15 @@ export function IntegrationStatusPill({
}: IntegrationStatusPillProps) {
const { t } = useTranslations();
// chipClass only applies to the `connected` branch — `error` falls back
// to `variant="destructive"` and `disconnected` to `variant="outline"`.
// chipClass only applies to the `connected` and `warning` branches —
// `error` falls back to `variant="destructive"` and `disconnected` to
// `variant="outline"`. The warning chip uses amber tokens to signal
// "connected, but the upstream just returned a contract-level error
// a reauth won't fix" — distinct from the red reconnect pill.
const connectedChipClass =
"border-dracula-green/30 bg-dracula-green/15 text-dracula-green";
const warningChipClass =
"border-dracula-yellow/30 bg-dracula-yellow/15 text-dracula-yellow";
let label: string;
let icon: React.ReactNode;
@@ -87,6 +96,10 @@ export function IntegrationStatusPill({
label = t("settings.integrationPill.connected");
icon = <CheckCircle2 aria-hidden="true" className="h-3 w-3" />;
break;
case "warning":
label = t("settings.integrationPill.warningServerError");
icon = <AlertTriangle aria-hidden="true" className="h-3 w-3" />;
break;
case "error":
label = t("settings.integrationPill.errorReconnect");
icon = <AlertTriangle aria-hidden="true" className="h-3 w-3" />;
@@ -111,7 +124,7 @@ export function IntegrationStatusPill({
data-testid="integration-status-pill"
data-state={state}
variant={
state === "connected"
state === "connected" || state === "warning"
? undefined
: state === "error"
? "destructive"
@@ -121,6 +134,7 @@ export function IntegrationStatusPill({
className={cn(
"max-w-full whitespace-nowrap",
state === "connected" && connectedChipClass,
state === "warning" && warningChipClass,
className,
)}
>