fix(onboarding,integrations,cycle): deep-link source chips + cycle settings wrench

- Onboarding 'more sources' chips anchor to their provider card
  (/settings/integrations#oura etc.); connection cards gain scroll anchors.
  Apple Health (no web card) stays on the page root.
- Cycle module gains a header settings wrench for parity with labs /
  medications / illness; the tab strip is now controlled so the wrench jumps
  to the settings tab and ?tab= deep-links keep working.
This commit is contained in:
Marc-André Bombeck committed 2026-07-08 23:59:10 +02:00
1 parent 188662311a
commit e5ca125dfb
3 files changed
+69 -18

No files matched your search

+37 -10
View File
@@ -1,9 +1,15 @@
"use client";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import dynamic from "next/dynamic";
import { useSearchParams } from "next/navigation";
import { Loader2, Plus, RefreshCw, Sparkles } from "lucide-react";
import {
Loader2,
Plus,
RefreshCw,
SlidersHorizontal,
Sparkles,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
@@ -100,6 +106,14 @@ export function CycleView() {
: "calendar";
}, [searchParams]);
// Controlled so the header "cycle settings" wrench (parity with the
// labs/medications/illness modules, which expose a customize glyph) can jump
// to the settings tab; a `?tab=` deep-link change still switches too.
const [tab, setTab] = useState<CycleTab>(initialTab);
useEffect(() => {
setTab(initialTab);
}, [initialTab]);
const today = useMemo(() => shiftToday(0), []);
const from = useMemo(() => shiftToday(-90), []);
const to = useMemo(() => shiftToday(180), []);
@@ -158,13 +172,26 @@ export function CycleView() {
title={<span data-tour-id="cycle-hero">{t("cycle.title")}</span>}
description={t("cycle.subtitle")}
actions={
<Button
onClick={() => openSheet(today)}
className="min-h-11 sm:min-h-9"
>
<Plus className="h-4 w-4" />
{t("cycle.logToday")}
</Button>
<>
<Button
variant="outline"
size="icon"
className="min-h-11 min-w-11 sm:min-h-9 sm:min-w-9"
aria-label={t("cycle.tabs.settings")}
title={t("cycle.tabs.settings")}
data-slot="cycle-settings-wrench"
onClick={() => setTab("settings")}
>
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
</Button>
<Button
onClick={() => openSheet(today)}
className="min-h-11 sm:min-h-9"
>
<Plus className="h-4 w-4" />
{t("cycle.logToday")}
</Button>
</>
}
/>
@@ -266,7 +293,7 @@ export function CycleView() {
) : null}
</div>
<Tabs defaultValue={initialTab}>
<Tabs value={tab} onValueChange={(v) => setTab(v as CycleTab)}>
{/* v1.15.10 — the four German labels (Kalender/Prognosen/
Erkenntnisse/Einstellungen) overflowed the `flex-1` equal-width
cells at 375 px and forced a horizontal scroll. Below `sm` the
@@ -170,7 +170,15 @@ export function SourceCardGrid() {
<li key={item.slug}>
<Button asChild variant="outline" size="sm" className="min-h-9">
<Link
href="/settings/integrations"
// Anchor each chip to its provider card so the tap lands on
// the card, not the top of the generic Integrations page.
// Apple Health has no web card (it syncs from the iOS app),
// so its chip stays on the page root.
href={
item.slug === "apple-health"
? "/settings/integrations"
: `/settings/integrations#${item.slug}`
}
data-testid={`source-more-${item.slug}`}
>
{t(item.labelKey)}
@@ -262,13 +262,29 @@ export function ConnectionsPanel() {
return (
<div className="space-y-6" data-slot="connections-panel">
<WithingsCard viewModel={withingsViewModel} />
<WhoopCard viewModel={whoopViewModel} />
<FitbitCard viewModel={fitbitViewModel} />
<GoogleHealthCard viewModel={googleHealthViewModel} />
<PolarCard enabled={isAuthenticated} viewModel={polarViewModel} />
<OuraCard enabled={isAuthenticated} viewModel={ouraViewModel} />
<NightscoutCard enabled={isAuthenticated} />
{/* Per-provider anchors so deep-links (onboarding "more sources" chips,
`/settings/integrations#oura`) scroll straight to the right card. */}
<div id="withings" className="scroll-mt-28">
<WithingsCard viewModel={withingsViewModel} />
</div>
<div id="whoop" className="scroll-mt-28">
<WhoopCard viewModel={whoopViewModel} />
</div>
<div id="fitbit" className="scroll-mt-28">
<FitbitCard viewModel={fitbitViewModel} />
</div>
<div id="google-health" className="scroll-mt-28">
<GoogleHealthCard viewModel={googleHealthViewModel} />
</div>
<div id="polar" className="scroll-mt-28">
<PolarCard enabled={isAuthenticated} viewModel={polarViewModel} />
</div>
<div id="oura" className="scroll-mt-28">
<OuraCard enabled={isAuthenticated} viewModel={ouraViewModel} />
</div>
<div id="nightscout" className="scroll-mt-28">
<NightscoutCard enabled={isAuthenticated} />
</div>
</div>
);
}