mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
feat(settings): put a sync-now button on the four cards that lacked one
A route with no caller is an API detail, not a feature. The Oura, Polar, Strava and Nightscout cards get the same action WHOOP already had, in the same row as Test connection, rendered only once connected: post the sync, show the count or the failure, invalidate the reads that depend on measurements. No full-history dialog, since these providers have no such arm. Keys added across the six locales.
This commit is contained in:
1 parent
a9f1629f82
commit
0bcbb88db1
10 files changed
+252
No files matched your search
@@ -5543,6 +5543,9 @@
|
||||
"nightscoutUpdate": "Änderungen speichern",
|
||||
"nightscoutConnected": "Nightscout verbunden.",
|
||||
"nightscoutConnectFailed": "Verbindung zu Nightscout fehlgeschlagen.",
|
||||
"nightscoutSync": "Jetzt synchronisieren",
|
||||
"nightscoutSyncResult": "{count} Messwerte synchronisiert",
|
||||
"nightscoutSyncFailed": "Synchronisierung fehlgeschlagen",
|
||||
"nightscoutDisconnect": "Trennen",
|
||||
"nightscoutDisconnectTitle": "Nightscout trennen?",
|
||||
"nightscoutDisconnectDescription": "Künftige Synchronisierungen stoppen. Bereits importierte Glukosewerte bleiben in deinem Verlauf.",
|
||||
@@ -5558,6 +5561,9 @@
|
||||
"polarSaveCredentials": "Speichern",
|
||||
"polarUnavailable": "Polar ist auf diesem Server nicht konfiguriert. Füge oben deine eigenen Polar-OAuth-Zugangsdaten hinzu oder bitte deinen Administrator, die gemeinsame App zu konfigurieren.",
|
||||
"polarConnect": "Polar verbinden",
|
||||
"polarSync": "Jetzt synchronisieren",
|
||||
"polarSyncResult": "{count} Messwerte synchronisiert",
|
||||
"polarSyncFailed": "Synchronisierung fehlgeschlagen",
|
||||
"polarDisconnect": "Trennen",
|
||||
"polarDisconnectTitle": "Polar trennen?",
|
||||
"polarDisconnectDescription": "Künftige Synchronisierungen stoppen. Bereits importierte Werte bleiben in deinem Verlauf.",
|
||||
@@ -5585,6 +5591,9 @@
|
||||
"ouraSaveCredentials": "Speichern",
|
||||
"ouraUnavailable": "Oura ist auf diesem Server nicht konfiguriert. Füge oben deine eigenen Oura-OAuth-Zugangsdaten hinzu oder bitte deinen Administrator, die gemeinsame App zu konfigurieren.",
|
||||
"ouraConnect": "Oura verbinden",
|
||||
"ouraSync": "Jetzt synchronisieren",
|
||||
"ouraSyncResult": "{count} Messwerte synchronisiert",
|
||||
"ouraSyncFailed": "Synchronisierung fehlgeschlagen",
|
||||
"ouraDisconnect": "Trennen",
|
||||
"ouraDisconnectTitle": "Oura trennen?",
|
||||
"ouraDisconnectDescription": "Künftige Synchronisierungen stoppen. Bereits importierte Werte bleiben in deinem Verlauf.",
|
||||
@@ -5813,6 +5822,9 @@
|
||||
"stravaSaveCredentials": "Speichern",
|
||||
"stravaUnavailable": "Strava ist auf diesem Server nicht konfiguriert. Füge oben deine eigenen Strava-API-Zugangsdaten hinzu oder bitte deinen Administrator, die gemeinsame App zu konfigurieren.",
|
||||
"stravaConnect": "Strava verbinden",
|
||||
"stravaSync": "Jetzt synchronisieren",
|
||||
"stravaSyncResult": "{count} Messwerte synchronisiert",
|
||||
"stravaSyncFailed": "Synchronisierung fehlgeschlagen",
|
||||
"stravaDisconnect": "Trennen",
|
||||
"stravaDisconnectTitle": "Strava trennen?",
|
||||
"stravaDisconnectDescription": "Künftige Synchronisierungen stoppen. Bereits importierte Aktivitäten bleiben in deinem Verlauf.",
|
||||
|
||||
@@ -5533,6 +5533,9 @@
|
||||
"nightscoutUpdate": "Save changes",
|
||||
"nightscoutConnected": "Nightscout connected.",
|
||||
"nightscoutConnectFailed": "Could not connect to Nightscout.",
|
||||
"nightscoutSync": "Sync now",
|
||||
"nightscoutSyncResult": "{count} measurements synchronized",
|
||||
"nightscoutSyncFailed": "Sync failed",
|
||||
"nightscoutDisconnect": "Disconnect",
|
||||
"nightscoutDisconnectTitle": "Disconnect Nightscout?",
|
||||
"nightscoutDisconnectDescription": "Future syncs stop. Glucose readings already imported stay in your history.",
|
||||
@@ -5548,6 +5551,9 @@
|
||||
"polarSaveCredentials": "Save",
|
||||
"polarUnavailable": "Polar is not configured on this server. Add your own Polar OAuth credentials above, or ask your administrator to configure the shared app.",
|
||||
"polarConnect": "Connect Polar",
|
||||
"polarSync": "Sync now",
|
||||
"polarSyncResult": "{count} measurements synchronized",
|
||||
"polarSyncFailed": "Sync failed",
|
||||
"polarDisconnect": "Disconnect",
|
||||
"polarDisconnectTitle": "Disconnect Polar?",
|
||||
"polarDisconnectDescription": "Future syncs stop. Readings already imported stay in your history.",
|
||||
@@ -5575,6 +5581,9 @@
|
||||
"ouraSaveCredentials": "Save",
|
||||
"ouraUnavailable": "Oura is not configured on this server. Add your own Oura OAuth credentials above, or ask your administrator to configure the shared app.",
|
||||
"ouraConnect": "Connect Oura",
|
||||
"ouraSync": "Sync now",
|
||||
"ouraSyncResult": "{count} measurements synchronized",
|
||||
"ouraSyncFailed": "Sync failed",
|
||||
"ouraDisconnect": "Disconnect",
|
||||
"ouraDisconnectTitle": "Disconnect Oura?",
|
||||
"ouraDisconnectDescription": "Future syncs stop. Readings already imported stay in your history.",
|
||||
@@ -5803,6 +5812,9 @@
|
||||
"stravaSaveCredentials": "Save",
|
||||
"stravaUnavailable": "Strava is not configured on this server. Add your own Strava API credentials above, or ask your administrator to configure the shared app.",
|
||||
"stravaConnect": "Connect Strava",
|
||||
"stravaSync": "Sync now",
|
||||
"stravaSyncResult": "{count} measurements synchronized",
|
||||
"stravaSyncFailed": "Sync failed",
|
||||
"stravaDisconnect": "Disconnect",
|
||||
"stravaDisconnectTitle": "Disconnect Strava?",
|
||||
"stravaDisconnectDescription": "Future syncs stop. Activities already imported stay in your history.",
|
||||
|
||||
@@ -5545,6 +5545,9 @@
|
||||
"nightscoutUpdate": "Guardar cambios",
|
||||
"nightscoutConnected": "Nightscout conectado.",
|
||||
"nightscoutConnectFailed": "No se pudo conectar con Nightscout.",
|
||||
"nightscoutSync": "Sincronizar ahora",
|
||||
"nightscoutSyncResult": "{count} mediciones sincronizadas",
|
||||
"nightscoutSyncFailed": "Error de sincronización",
|
||||
"nightscoutDisconnect": "Desconectar",
|
||||
"nightscoutDisconnectTitle": "¿Desconectar Nightscout?",
|
||||
"nightscoutDisconnectDescription": "Las sincronizaciones futuras se detienen. Las lecturas de glucosa ya importadas permanecen en tu historial.",
|
||||
@@ -5560,6 +5563,9 @@
|
||||
"polarSaveCredentials": "Guardar",
|
||||
"polarUnavailable": "Polar no está configurado en este servidor. Añade arriba tus propias credenciales OAuth de Polar, o pide a tu administrador que configure la app compartida.",
|
||||
"polarConnect": "Conectar Polar",
|
||||
"polarSync": "Sincronizar ahora",
|
||||
"polarSyncResult": "{count} mediciones sincronizadas",
|
||||
"polarSyncFailed": "Error de sincronización",
|
||||
"polarDisconnect": "Desconectar",
|
||||
"polarDisconnectTitle": "¿Desconectar Polar?",
|
||||
"polarDisconnectDescription": "Las sincronizaciones futuras se detienen. Las lecturas ya importadas permanecen en tu historial.",
|
||||
@@ -5587,6 +5593,9 @@
|
||||
"ouraSaveCredentials": "Guardar",
|
||||
"ouraUnavailable": "Oura no está configurado en este servidor. Añade arriba tus propias credenciales OAuth de Oura, o pide a tu administrador que configure la app compartida.",
|
||||
"ouraConnect": "Conectar Oura",
|
||||
"ouraSync": "Sincronizar ahora",
|
||||
"ouraSyncResult": "{count} mediciones sincronizadas",
|
||||
"ouraSyncFailed": "Error de sincronización",
|
||||
"ouraDisconnect": "Desconectar",
|
||||
"ouraDisconnectTitle": "¿Desconectar Oura?",
|
||||
"ouraDisconnectDescription": "Las sincronizaciones futuras se detienen. Las lecturas ya importadas permanecen en tu historial.",
|
||||
@@ -5815,6 +5824,9 @@
|
||||
"stravaSaveCredentials": "Guardar",
|
||||
"stravaUnavailable": "Strava no está configurado en este servidor. Añade arriba tus propias credenciales de API de Strava o pide a tu administrador que configure la app compartida.",
|
||||
"stravaConnect": "Conectar Strava",
|
||||
"stravaSync": "Sincronizar ahora",
|
||||
"stravaSyncResult": "{count} mediciones sincronizadas",
|
||||
"stravaSyncFailed": "Error de sincronización",
|
||||
"stravaDisconnect": "Desconectar",
|
||||
"stravaDisconnectTitle": "¿Desconectar Strava?",
|
||||
"stravaDisconnectDescription": "Las sincronizaciones futuras se detienen. Las actividades ya importadas permanecen en tu historial.",
|
||||
|
||||
@@ -5547,6 +5547,9 @@
|
||||
"nightscoutUpdate": "Enregistrer les modifications",
|
||||
"nightscoutConnected": "Nightscout connecté.",
|
||||
"nightscoutConnectFailed": "Impossible de se connecter à Nightscout.",
|
||||
"nightscoutSync": "Synchroniser",
|
||||
"nightscoutSyncResult": "{count} mesures synchronisées",
|
||||
"nightscoutSyncFailed": "Échec de la synchronisation",
|
||||
"nightscoutDisconnect": "Déconnecter",
|
||||
"nightscoutDisconnectTitle": "Déconnecter Nightscout ?",
|
||||
"nightscoutDisconnectDescription": "Les synchronisations futures s'arrêtent. Les mesures de glycémie déjà importées restent dans votre historique.",
|
||||
@@ -5562,6 +5565,9 @@
|
||||
"polarSaveCredentials": "Enregistrer",
|
||||
"polarUnavailable": "Polar n'est pas configuré sur ce serveur. Ajoutez vos propres identifiants OAuth Polar ci-dessus, ou demandez à votre administrateur de configurer l'application partagée.",
|
||||
"polarConnect": "Connecter Polar",
|
||||
"polarSync": "Synchroniser",
|
||||
"polarSyncResult": "{count} mesures synchronisées",
|
||||
"polarSyncFailed": "Échec de la synchronisation",
|
||||
"polarDisconnect": "Déconnecter",
|
||||
"polarDisconnectTitle": "Déconnecter Polar ?",
|
||||
"polarDisconnectDescription": "Les synchronisations futures s'arrêtent. Les mesures déjà importées restent dans votre historique.",
|
||||
@@ -5589,6 +5595,9 @@
|
||||
"ouraSaveCredentials": "Enregistrer",
|
||||
"ouraUnavailable": "Oura n'est pas configuré sur ce serveur. Ajoutez vos propres identifiants OAuth Oura ci-dessus, ou demandez à votre administrateur de configurer l'application partagée.",
|
||||
"ouraConnect": "Connecter Oura",
|
||||
"ouraSync": "Synchroniser",
|
||||
"ouraSyncResult": "{count} mesures synchronisées",
|
||||
"ouraSyncFailed": "Échec de la synchronisation",
|
||||
"ouraDisconnect": "Déconnecter",
|
||||
"ouraDisconnectTitle": "Déconnecter Oura ?",
|
||||
"ouraDisconnectDescription": "Les synchronisations futures s'arrêtent. Les mesures déjà importées restent dans votre historique.",
|
||||
@@ -5817,6 +5826,9 @@
|
||||
"stravaSaveCredentials": "Enregistrer",
|
||||
"stravaUnavailable": "Strava n'est pas configuré sur ce serveur. Ajoute tes propres identifiants API Strava ci-dessus ou demande à ton administrateur de configurer l'application partagée.",
|
||||
"stravaConnect": "Connecter Strava",
|
||||
"stravaSync": "Synchroniser",
|
||||
"stravaSyncResult": "{count} mesures synchronisées",
|
||||
"stravaSyncFailed": "Échec de la synchronisation",
|
||||
"stravaDisconnect": "Déconnecter",
|
||||
"stravaDisconnectTitle": "Déconnecter Strava ?",
|
||||
"stravaDisconnectDescription": "Les synchronisations futures s'arrêtent. Les activités déjà importées restent dans ton historique.",
|
||||
|
||||
@@ -5537,6 +5537,9 @@
|
||||
"nightscoutUpdate": "Salva modifiche",
|
||||
"nightscoutConnected": "Nightscout connesso.",
|
||||
"nightscoutConnectFailed": "Impossibile connettersi a Nightscout.",
|
||||
"nightscoutSync": "Sincronizza ora",
|
||||
"nightscoutSyncResult": "{count} misurazioni sincronizzate",
|
||||
"nightscoutSyncFailed": "Sincronizzazione non riuscita",
|
||||
"nightscoutDisconnect": "Disconnetti",
|
||||
"nightscoutDisconnectTitle": "Disconnettere Nightscout?",
|
||||
"nightscoutDisconnectDescription": "Le sincronizzazioni future si interrompono. Le letture della glicemia già importate restano nella tua cronologia.",
|
||||
@@ -5552,6 +5555,9 @@
|
||||
"polarSaveCredentials": "Salva",
|
||||
"polarUnavailable": "Polar non è configurato su questo server. Aggiungi sopra le tue credenziali OAuth di Polar oppure chiedi al tuo amministratore di configurare l'app condivisa.",
|
||||
"polarConnect": "Collega Polar",
|
||||
"polarSync": "Sincronizza ora",
|
||||
"polarSyncResult": "{count} misurazioni sincronizzate",
|
||||
"polarSyncFailed": "Sincronizzazione non riuscita",
|
||||
"polarDisconnect": "Disconnetti",
|
||||
"polarDisconnectTitle": "Disconnettere Polar?",
|
||||
"polarDisconnectDescription": "Le sincronizzazioni future si interrompono. Le letture già importate restano nella tua cronologia.",
|
||||
@@ -5579,6 +5585,9 @@
|
||||
"ouraSaveCredentials": "Salva",
|
||||
"ouraUnavailable": "Oura non è configurato su questo server. Aggiungi sopra le tue credenziali OAuth di Oura oppure chiedi al tuo amministratore di configurare l'app condivisa.",
|
||||
"ouraConnect": "Collega Oura",
|
||||
"ouraSync": "Sincronizza ora",
|
||||
"ouraSyncResult": "{count} misurazioni sincronizzate",
|
||||
"ouraSyncFailed": "Sincronizzazione non riuscita",
|
||||
"ouraDisconnect": "Disconnetti",
|
||||
"ouraDisconnectTitle": "Disconnettere Oura?",
|
||||
"ouraDisconnectDescription": "Le sincronizzazioni future si interrompono. Le letture già importate restano nella tua cronologia.",
|
||||
@@ -5807,6 +5816,9 @@
|
||||
"stravaSaveCredentials": "Salva",
|
||||
"stravaUnavailable": "Strava non è configurato su questo server. Aggiungi sopra le tue credenziali API di Strava o chiedi al tuo amministratore di configurare l'app condivisa.",
|
||||
"stravaConnect": "Collega Strava",
|
||||
"stravaSync": "Sincronizza ora",
|
||||
"stravaSyncResult": "{count} misurazioni sincronizzate",
|
||||
"stravaSyncFailed": "Sincronizzazione non riuscita",
|
||||
"stravaDisconnect": "Disconnetti",
|
||||
"stravaDisconnectTitle": "Disconnettere Strava?",
|
||||
"stravaDisconnectDescription": "Le sincronizzazioni future si interrompono. Le attività già importate restano nella cronologia.",
|
||||
|
||||
@@ -5531,6 +5531,9 @@
|
||||
"nightscoutUpdate": "Zapisz zmiany",
|
||||
"nightscoutConnected": "Nightscout połączony.",
|
||||
"nightscoutConnectFailed": "Nie można połączyć z Nightscout.",
|
||||
"nightscoutSync": "Synchronizuj teraz",
|
||||
"nightscoutSyncResult": "Zsynchronizowano {count} pomiarów",
|
||||
"nightscoutSyncFailed": "Synchronizacja nie powiodła się",
|
||||
"nightscoutDisconnect": "Rozłącz",
|
||||
"nightscoutDisconnectTitle": "Rozłączyć Nightscout?",
|
||||
"nightscoutDisconnectDescription": "Przyszłe synchronizacje zostaną zatrzymane. Już zaimportowane odczyty glukozy pozostaną w historii.",
|
||||
@@ -5546,6 +5549,9 @@
|
||||
"polarSaveCredentials": "Zapisz",
|
||||
"polarUnavailable": "Polar nie jest skonfigurowany na tym serwerze. Dodaj powyżej własne dane OAuth Polar lub poproś administratora o skonfigurowanie współdzielonej aplikacji.",
|
||||
"polarConnect": "Połącz Polar",
|
||||
"polarSync": "Synchronizuj teraz",
|
||||
"polarSyncResult": "Zsynchronizowano {count} pomiarów",
|
||||
"polarSyncFailed": "Synchronizacja nie powiodła się",
|
||||
"polarDisconnect": "Rozłącz",
|
||||
"polarDisconnectTitle": "Rozłączyć Polar?",
|
||||
"polarDisconnectDescription": "Przyszłe synchronizacje zostaną zatrzymane. Już zaimportowane odczyty pozostaną w historii.",
|
||||
@@ -5573,6 +5579,9 @@
|
||||
"ouraSaveCredentials": "Zapisz",
|
||||
"ouraUnavailable": "Oura nie jest skonfigurowana na tym serwerze. Dodaj powyżej własne dane OAuth Oura lub poproś administratora o skonfigurowanie współdzielonej aplikacji.",
|
||||
"ouraConnect": "Połącz Oura",
|
||||
"ouraSync": "Synchronizuj teraz",
|
||||
"ouraSyncResult": "Zsynchronizowano {count} pomiarów",
|
||||
"ouraSyncFailed": "Synchronizacja nie powiodła się",
|
||||
"ouraDisconnect": "Rozłącz",
|
||||
"ouraDisconnectTitle": "Rozłączyć Oura?",
|
||||
"ouraDisconnectDescription": "Przyszłe synchronizacje zostaną zatrzymane. Już zaimportowane odczyty pozostaną w historii.",
|
||||
@@ -5801,6 +5810,9 @@
|
||||
"stravaSaveCredentials": "Zapisz",
|
||||
"stravaUnavailable": "Strava nie jest skonfigurowana na tym serwerze. Dodaj powyżej własne dane API Strava lub poproś administratora o skonfigurowanie wspólnej aplikacji.",
|
||||
"stravaConnect": "Połącz Strava",
|
||||
"stravaSync": "Synchronizuj teraz",
|
||||
"stravaSyncResult": "Zsynchronizowano {count} pomiarów",
|
||||
"stravaSyncFailed": "Synchronizacja nie powiodła się",
|
||||
"stravaDisconnect": "Rozłącz",
|
||||
"stravaDisconnectTitle": "Rozłączyć Strava?",
|
||||
"stravaDisconnectDescription": "Przyszłe synchronizacje zostaną zatrzymane. Już zaimportowane aktywności pozostaną w historii.",
|
||||
|
||||
@@ -83,3 +83,26 @@ describe("NightscoutCard — parked + test + data-link + invalidation", () => {
|
||||
expect(invalidatedKeys).toContainEqual(queryKeys.nightscoutStatus());
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* v1.32.28 — sync now, matching the treatment the OAuth cards get.
|
||||
*/
|
||||
describe("NightscoutCard — sync-now action", () => {
|
||||
it("offers the sync action on a connected instance", () => {
|
||||
statusPayload = {
|
||||
connected: true,
|
||||
configured: true,
|
||||
state: "connected",
|
||||
lastSuccessAt: null,
|
||||
lastError: null,
|
||||
};
|
||||
const html = render();
|
||||
expect(html).toContain('data-testid="nightscout-sync"');
|
||||
expect(html).toContain("Sync now");
|
||||
});
|
||||
|
||||
it("does not offer it before an instance is configured", () => {
|
||||
statusPayload = { connected: false, configured: false };
|
||||
expect(render()).not.toContain('data-testid="nightscout-sync"');
|
||||
});
|
||||
});
|
||||
@@ -178,3 +178,34 @@ describe("OAuthProviderCard — redirect-URI mini-guide (v1.29.x, UX audit H2)",
|
||||
expect(html).not.toContain('data-testid="polar-redirect-guide"');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* v1.32.28 — sync now. The route without the button would be an endpoint with
|
||||
* no caller; the button is what makes the manual sync a feature rather than an
|
||||
* API detail. Rendered only once the provider is connected, in the same action
|
||||
* row as Test connection.
|
||||
*/
|
||||
describe("OAuthProviderCard — sync-now action", () => {
|
||||
it("offers the sync action on a connected provider", () => {
|
||||
statusPayload = {
|
||||
connected: true,
|
||||
configured: true,
|
||||
available: true,
|
||||
state: "connected",
|
||||
lastSuccessAt: null,
|
||||
lastError: null,
|
||||
};
|
||||
const html = render();
|
||||
expect(html).toContain('data-testid="polar-sync"');
|
||||
expect(html).toContain("Sync now");
|
||||
});
|
||||
|
||||
it("does not offer it before the provider is connected", () => {
|
||||
statusPayload = {
|
||||
connected: false,
|
||||
configured: true,
|
||||
available: true,
|
||||
};
|
||||
expect(render()).not.toContain('data-testid="polar-sync"');
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Droplet,
|
||||
Link2,
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
Save,
|
||||
Unlink,
|
||||
} from "lucide-react";
|
||||
@@ -96,6 +97,11 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [msg, setMsg] = useState<string | null>(null);
|
||||
const [msgType, setMsgType] = useState<"success" | "error" | null>(null);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [syncMsg, setSyncMsg] = useState<string | null>(null);
|
||||
const [syncMsgType, setSyncMsgType] = useState<"success" | "error" | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const { data: status } = useQuery({
|
||||
queryKey: queryKeys.nightscoutStatus(),
|
||||
@@ -170,6 +176,39 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
|
||||
setSaving(false);
|
||||
}
|
||||
|
||||
// Sync now. Same shape as the WHOOP card's incremental arm, minus the
|
||||
// full-history dialog — Nightscout has no full-sync arm to call.
|
||||
async function handleSync() {
|
||||
setSyncing(true);
|
||||
setSyncMsg(null);
|
||||
setSyncMsgType(null);
|
||||
try {
|
||||
const res = await apiFetchRaw("/api/nightscout/sync", { method: "POST" });
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
setSyncMsg(
|
||||
t("settings.nightscoutSyncResult", { count: json.data.imported }),
|
||||
);
|
||||
setSyncMsgType("success");
|
||||
void invalidateKeys(queryClient, measurementDependentKeys);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.nightscoutStatus(),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.integrationsStatus(),
|
||||
});
|
||||
} else {
|
||||
setSyncMsg(json.error || t("settings.nightscoutSyncFailed"));
|
||||
setSyncMsgType("error");
|
||||
}
|
||||
} catch {
|
||||
setSyncMsg(t("settings.nightscoutSyncFailed"));
|
||||
setSyncMsgType("error");
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
}
|
||||
|
||||
const pillState = pillStateFor(status);
|
||||
const errorMessage =
|
||||
(pillState === "error" || pillState === "parked") && status?.lastError
|
||||
@@ -337,6 +376,22 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
|
||||
{status?.connected && (
|
||||
<>
|
||||
<div className="flex flex-wrap items-start gap-2 [&>*]:min-w-[10rem] sm:[&>*]:min-w-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="min-h-11"
|
||||
onClick={handleSync}
|
||||
disabled={syncing}
|
||||
data-testid="nightscout-sync"
|
||||
>
|
||||
{syncing ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
|
||||
) : (
|
||||
<RefreshCw className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{t("settings.nightscoutSync")}
|
||||
</Button>
|
||||
<TestConnectionButton
|
||||
endpoint="/api/nightscout/test"
|
||||
disabled={!status?.connected}
|
||||
@@ -373,6 +428,15 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
{syncMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
data-testid="nightscout-sync-result"
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{syncMsg}
|
||||
</p>
|
||||
)}
|
||||
{/* connect→data loop: a discreet link to where the glucose
|
||||
readings now surface — doubles as the "your data is richer"
|
||||
cue. */}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ArrowRight,
|
||||
Link2,
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
Save,
|
||||
Unlink,
|
||||
type LucideIcon,
|
||||
@@ -125,6 +126,11 @@ export function OAuthProviderCard({
|
||||
const { t } = useTranslations();
|
||||
const queryClient = useQueryClient();
|
||||
const [msg, setMsg] = useState<string | null>(null);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [syncMsg, setSyncMsg] = useState<string | null>(null);
|
||||
const [syncMsgType, setSyncMsgType] = useState<"success" | "error" | null>(
|
||||
null,
|
||||
);
|
||||
const [clientId, setClientId] = useState("");
|
||||
const [clientSecret, setClientSecret] = useState("");
|
||||
const [credsSaving, setCredsSaving] = useState(false);
|
||||
@@ -145,6 +151,37 @@ export function OAuthProviderCard({
|
||||
});
|
||||
const status = viewModel ?? fetchedStatus;
|
||||
|
||||
// Sync now. Same shape as the WHOOP card's incremental arm, minus the
|
||||
// full-history dialog — none of these providers has a full-sync arm to call.
|
||||
async function handleSync() {
|
||||
setSyncing(true);
|
||||
setSyncMsg(null);
|
||||
setSyncMsgType(null);
|
||||
try {
|
||||
const res = await apiFetchRaw(`/api/${provider}/sync`, {
|
||||
method: "POST",
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
setSyncMsg(t(`${i18nPrefix}SyncResult`, { count: json.data.imported }));
|
||||
setSyncMsgType("success");
|
||||
void invalidateKeys(queryClient, measurementDependentKeys);
|
||||
queryClient.invalidateQueries({ queryKey: statusQueryKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.integrationsStatus(),
|
||||
});
|
||||
} else {
|
||||
setSyncMsg(json.error || t(`${i18nPrefix}SyncFailed`));
|
||||
setSyncMsgType("error");
|
||||
}
|
||||
} catch {
|
||||
setSyncMsg(t(`${i18nPrefix}SyncFailed`));
|
||||
setSyncMsgType("error");
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveCredentials(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setCredsSaving(true);
|
||||
@@ -374,6 +411,22 @@ export function OAuthProviderCard({
|
||||
{status?.connected ? (
|
||||
<>
|
||||
<div className="flex flex-wrap items-start gap-2 [&>*]:min-w-[10rem] sm:[&>*]:min-w-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="min-h-11"
|
||||
onClick={handleSync}
|
||||
disabled={syncing}
|
||||
data-testid={`${provider}-sync`}
|
||||
>
|
||||
{syncing ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
|
||||
) : (
|
||||
<RefreshCw className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{t(`${i18nPrefix}Sync`)}
|
||||
</Button>
|
||||
<TestConnectionButton
|
||||
endpoint={`/api/${provider}/test`}
|
||||
disabled={!status?.connected}
|
||||
@@ -411,6 +464,15 @@ export function OAuthProviderCard({
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
{syncMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
data-testid={`${provider}-sync-result`}
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{syncMsg}
|
||||
</p>
|
||||
)}
|
||||
{/* connect→data loop: a discreet link to where this provider's
|
||||
readings now surface — doubles as the "your data is richer"
|
||||
cue. */}
|
||||
|
||||
Reference in new issue
Block a user