mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
fix(fitbit): show the retired card only to an account that had Fitbit
After the Web API turndown every account still saw a Fitbit card that can no longer connect. The status entry now publishes retirement.offered: false once the API is gone for an account with no Fitbit connection, no Fitbit app of its own and no Fitbit data, and the connections panel leaves the card out. Accounts that had Fitbit keep it with the retirement notice.
This commit is contained in:
1 parent
bf3830203f
commit
b53434daa0
8 files changed
+172
-40
No files matched your search
@@ -181,3 +181,38 @@ describe("ConnectionsPanel — every envelope entry surfaces", () => {
|
||||
expect(render()).not.toContain('data-testid="integration-fallback-row"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("ConnectionsPanel — the Fitbit card after the turndown", () => {
|
||||
const withFitbit = (retirement: Record<string, unknown>) => ({
|
||||
threshold: 3,
|
||||
integrations: fullEnvelope().integrations.map((e) =>
|
||||
e.integration === "fitbit"
|
||||
? { ...e, connected: false, configured: false, retirement }
|
||||
: e,
|
||||
),
|
||||
});
|
||||
|
||||
it("shows no Fitbit card when the server does not offer it", () => {
|
||||
envelope = withFitbit({
|
||||
endsOn: "2026-10-30",
|
||||
ended: true,
|
||||
successor: "google-health",
|
||||
offered: false,
|
||||
});
|
||||
const html = render();
|
||||
expect(html).not.toContain('id="fitbit"');
|
||||
expect(html).not.toContain('data-testid="fitbit-retirement-notice"');
|
||||
});
|
||||
|
||||
it("keeps the card and its notice for an account that had Fitbit", () => {
|
||||
envelope = withFitbit({
|
||||
endsOn: "2026-10-30",
|
||||
ended: true,
|
||||
successor: "google-health",
|
||||
offered: true,
|
||||
});
|
||||
const html = render();
|
||||
expect(html).toContain('id="fitbit"');
|
||||
expect(html).toContain('data-testid="fitbit-retirement-notice"');
|
||||
});
|
||||
});
|
||||
@@ -337,40 +337,44 @@ export function ConnectionsPanel({
|
||||
callbackUrl={callbackUrls.whoop}
|
||||
/>
|
||||
</div>
|
||||
<div id="fitbit" className="scroll-mt-28">
|
||||
<FitbitRetirementNotice retirement={fitbitStatus?.retirement} />
|
||||
<OAuthIntegrationCard
|
||||
config={OAUTH_INTEGRATIONS.fitbit}
|
||||
viewModel={fitbitStatus}
|
||||
callbackUrl={callbackUrls.fitbit}
|
||||
syncDisabled={fitbitRetired}
|
||||
connectDisabled={fitbitRetired}
|
||||
titleAccessory={
|
||||
<>
|
||||
<TagChip>{t("settings.fitbitTag")}</TagChip>
|
||||
{fitbitStatus?.retirement ? (
|
||||
<Badge
|
||||
variant="outline"
|
||||
data-testid="fitbit-retirement-badge"
|
||||
className="border-warning/50 text-warning"
|
||||
>
|
||||
{fitbitRetired
|
||||
? t("settings.fitbitRetirement.badgeEnded")
|
||||
: t("settings.fitbitRetirement.badgeEnding")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
variant="outline"
|
||||
data-testid="fitbit-experimental-badge"
|
||||
className="border-warning/50 text-warning"
|
||||
>
|
||||
{t("settings.fitbitExperimentalBadge")}
|
||||
</Badge>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{/* After the turndown the card is offered only to an account that had
|
||||
Fitbit; the server decides (`retirement.offered`). */}
|
||||
{fitbitStatus?.retirement?.offered !== false && (
|
||||
<div id="fitbit" className="scroll-mt-28">
|
||||
<FitbitRetirementNotice retirement={fitbitStatus?.retirement} />
|
||||
<OAuthIntegrationCard
|
||||
config={OAUTH_INTEGRATIONS.fitbit}
|
||||
viewModel={fitbitStatus}
|
||||
callbackUrl={callbackUrls.fitbit}
|
||||
syncDisabled={fitbitRetired}
|
||||
connectDisabled={fitbitRetired}
|
||||
titleAccessory={
|
||||
<>
|
||||
<TagChip>{t("settings.fitbitTag")}</TagChip>
|
||||
{fitbitStatus?.retirement ? (
|
||||
<Badge
|
||||
variant="outline"
|
||||
data-testid="fitbit-retirement-badge"
|
||||
className="border-warning/50 text-warning"
|
||||
>
|
||||
{fitbitRetired
|
||||
? t("settings.fitbitRetirement.badgeEnded")
|
||||
: t("settings.fitbitRetirement.badgeEnding")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
variant="outline"
|
||||
data-testid="fitbit-experimental-badge"
|
||||
className="border-warning/50 text-warning"
|
||||
>
|
||||
{t("settings.fitbitExperimentalBadge")}
|
||||
</Badge>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div id="google-health" className="scroll-mt-28">
|
||||
<GoogleHealthCard
|
||||
viewModel={pickStatus(integrationStatus, "google-health")}
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { GoogleHealthBackfillEndedEntry } from "@/lib/google-health/backfil
|
||||
import type { ConnectionHealth } from "@/lib/integrations/connection-health";
|
||||
import type { MetricFreshnessEntry } from "@/lib/integrations/sync-verdict";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import type { FitbitRetirement } from "@/lib/fitbit/retirement";
|
||||
import type { PublishedFitbitRetirement } from "@/lib/fitbit/retirement";
|
||||
|
||||
export type IntegrationKey =
|
||||
| "withings"
|
||||
@@ -73,7 +73,7 @@ export interface IntegrationStatusViewModel {
|
||||
/** Per-metric last-seen timestamps with the server-computed stale flag. */
|
||||
metricFreshness?: MetricFreshnessEntry[];
|
||||
/** Fitbit: the end of the legacy Web API, resolved on the server. */
|
||||
retirement?: FitbitRetirement;
|
||||
retirement?: PublishedFitbitRetirement;
|
||||
}
|
||||
|
||||
export interface IntegrationStatusEnvelope {
|
||||
|
||||
Reference in new issue
Block a user