diff --git a/messages/de.json b/messages/de.json index a1ef194b3..d8a9503f7 100644 --- a/messages/de.json +++ b/messages/de.json @@ -3506,6 +3506,10 @@ "apiTokensDescription": "Erstelle API-Tokens für die externe Medikamenten-Einnahme (z.B. Shortcuts, Automationen).", "tokenNamePlaceholder": "Token-Name (z.B. iPhone Shortcut)", "tokenCreated": "Token erstellt — kopiere ihn jetzt! Er wird nicht erneut angezeigt.", + "tokenCopy": "Token kopieren", + "tokenCopied": "Token in die Zwischenablage kopiert", + "tokenCopyFailed": "Token konnte nicht kopiert werden", + "tokenRevokeFailed": "Token konnte nicht widerrufen werden. Bitte erneut versuchen.", "tokenRevoke": "Token widerrufen?", "tokenRevokeAction": "Token widerrufen", "tokenRevokeDescription": "Der Token wird unwiderruflich deaktiviert. Anwendungen, die ihn nutzen, verlieren den Zugriff.", diff --git a/messages/en.json b/messages/en.json index c5b164939..975794b60 100644 --- a/messages/en.json +++ b/messages/en.json @@ -3506,6 +3506,10 @@ "apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).", "tokenNamePlaceholder": "Token name (e.g., iPhone Shortcut)", "tokenCreated": "Token created — copy it now! It won't be shown again.", + "tokenCopy": "Copy token", + "tokenCopied": "Token copied to clipboard", + "tokenCopyFailed": "Could not copy the token", + "tokenRevokeFailed": "Could not revoke the token. Please try again.", "tokenRevoke": "Revoke token?", "tokenRevokeAction": "Revoke token", "tokenRevokeDescription": "The token will be permanently deactivated. Applications using it will lose access.", diff --git a/messages/es.json b/messages/es.json index 9103c1a91..3d73c7767 100644 --- a/messages/es.json +++ b/messages/es.json @@ -3506,6 +3506,10 @@ "apiTokensDescription": "Crea tokens de API para registrar tomas de medicación externas (p. ej., Atajos, automatizaciones).", "tokenNamePlaceholder": "Nombre del token (p. ej., Atajo de iPhone)", "tokenCreated": "Token creado — ¡cópialo ahora! No se mostrará de nuevo.", + "tokenCopy": "Copiar token", + "tokenCopied": "Token copiado al portapapeles", + "tokenCopyFailed": "No se pudo copiar el token", + "tokenRevokeFailed": "No se pudo revocar el token. Inténtalo de nuevo.", "tokenRevoke": "¿Revocar token?", "tokenRevokeAction": "Revocar token", "tokenRevokeDescription": "El token se desactivará de forma permanente. Las aplicaciones que lo usen perderán el acceso.", diff --git a/messages/fr.json b/messages/fr.json index 282e00420..bf590bea3 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -3506,6 +3506,10 @@ "apiTokensDescription": "Créez des jetons API pour enregistrer des prises de médicaments externes (par exemple Raccourcis, automatisations).", "tokenNamePlaceholder": "Nom du jeton (par exemple Raccourci iPhone)", "tokenCreated": "Jeton créé — copiez-le maintenant ! Il ne sera plus affiché.", + "tokenCopy": "Copier le jeton", + "tokenCopied": "Jeton copié dans le presse-papiers", + "tokenCopyFailed": "Impossible de copier le jeton", + "tokenRevokeFailed": "Impossible de révoquer le jeton. Veuillez réessayer.", "tokenRevoke": "Révoquer le jeton ?", "tokenRevokeAction": "Révoquer le jeton", "tokenRevokeDescription": "Le jeton sera désactivé définitivement. Les applications qui l’utilisent perdront l’accès.", diff --git a/messages/it.json b/messages/it.json index 7cf07cb29..247e001bf 100644 --- a/messages/it.json +++ b/messages/it.json @@ -3506,6 +3506,10 @@ "apiTokensDescription": "Crea token API per registrare assunzioni di farmaci esterne (ad es. Comandi rapidi, automazioni).", "tokenNamePlaceholder": "Nome del token (ad es. Comando rapido iPhone)", "tokenCreated": "Token creato — copialo ora! Non verrà mostrato di nuovo.", + "tokenCopy": "Copia token", + "tokenCopied": "Token copiato negli appunti", + "tokenCopyFailed": "Impossibile copiare il token", + "tokenRevokeFailed": "Impossibile revocare il token. Riprova.", "tokenRevoke": "Revocare il token?", "tokenRevokeAction": "Revoca token", "tokenRevokeDescription": "Il token verrà disattivato definitivamente. Le applicazioni che lo usano perderanno l’accesso.", diff --git a/messages/pl.json b/messages/pl.json index f8875743b..4333891fa 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -3506,6 +3506,10 @@ "apiTokensDescription": "Twórz tokeny API do zewnętrznego rejestrowania przyjęć leków (np. Skróty, automatyzacje).", "tokenNamePlaceholder": "Nazwa tokena (np. Skrót iPhone)", "tokenCreated": "Token utworzony — skopiuj go teraz! Nie zostanie pokazany ponownie.", + "tokenCopy": "Kopiuj token", + "tokenCopied": "Token skopiowany do schowka", + "tokenCopyFailed": "Nie udało się skopiować tokenu", + "tokenRevokeFailed": "Nie udało się unieważnić tokenu. Spróbuj ponownie.", "tokenRevoke": "Cofnąć token?", "tokenRevokeAction": "Unieważnij token", "tokenRevokeDescription": "Token zostanie trwale dezaktywowany. Aplikacje, które go używają, stracą dostęp.", diff --git a/src/components/settings/api-section.tsx b/src/components/settings/api-section.tsx index 6f153b2f7..8479e5bb1 100644 --- a/src/components/settings/api-section.tsx +++ b/src/components/settings/api-section.tsx @@ -2,7 +2,8 @@ import { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { ChevronDown, Key, Loader2, Trash2 } from "lucide-react"; +import { Check, ChevronDown, Copy, Key, Loader2, Trash2 } from "lucide-react"; +import { toast } from "sonner"; import { AlertDialog, @@ -165,6 +166,7 @@ function ApiTokensCard() { const [creating, setCreating] = useState(false); const [tokenMsg, setTokenMsg] = useState(null); const [showRevokedTokens, setShowRevokedTokens] = useState(false); + const [tokenCopied, setTokenCopied] = useState(false); const { data: tokens } = useQuery({ queryKey: queryKeys.tokens(), @@ -203,10 +205,30 @@ function ApiTokensCard() { } } + async function handleCopyToken() { + if (!newToken) return; + try { + await navigator.clipboard.writeText(newToken); + setTokenCopied(true); + toast.success(t("settings.tokenCopied")); + // Revert the inline check affordance after a short beat so a + // second copy reads as a fresh action. + setTimeout(() => setTokenCopied(false), 2_000); + } catch { + toast.error(t("settings.tokenCopyFailed")); + } + } + async function handleRevoke(tokenId: string) { - const res = await fetch(`/api/tokens/${tokenId}`, { method: "DELETE" }); - if (res.ok) { + try { + const res = await fetch(`/api/tokens/${tokenId}`, { method: "DELETE" }); + if (!res.ok) { + toast.error(t("settings.tokenRevokeFailed")); + return; + } queryClient.invalidateQueries({ queryKey: queryKeys.tokens() }); + } catch { + toast.error(t("settings.tokenRevokeFailed")); } } @@ -266,13 +288,33 @@ function ApiTokensCard() { {newToken && ( -
+

{t("settings.tokenCreated")}

- - {newToken} - +
+ + {newToken} + + +
)}