diff --git a/src/components/settings/__tests__/one-piece-anatomy.test.tsx b/src/components/settings/__tests__/one-piece-anatomy.test.tsx index 6b9c50846..d801cf1e7 100644 --- a/src/components/settings/__tests__/one-piece-anatomy.test.tsx +++ b/src/components/settings/__tests__/one-piece-anatomy.test.tsx @@ -67,6 +67,20 @@ describe("SettingsCard — the container owns the rhythm", () => { const html = render(body); expect(html).toContain("gap-2"); }); + + it("drops the gap as well as the padding on a flush ledger card", () => { + // Dropping only the padding leaves the card's own gap standing between + // the rows as dead space the first row does not have. Both breakpoints + // are named because the `` primitive carries a `md:` gap that a + // bare `gap-0` loses to. + const html = render(rows); + expect(html).toContain("gap-0"); + expect(html).toContain("md:gap-0"); + expect(html).toContain("p-0"); + expect(html).toContain("md:p-0"); + expect(html).not.toMatch(/class="[^"]*\bgap-4\b/); + expect(html).not.toMatch(/class="[^"]*\bp-4\b/); + }); }); describe("SettingsCardHeader — one description sentence, one text run", () => { diff --git a/src/components/settings/cycle-tracking-card.tsx b/src/components/settings/cycle-tracking-card.tsx deleted file mode 100644 index fb5d4bed4..000000000 --- a/src/components/settings/cycle-tracking-card.tsx +++ /dev/null @@ -1,102 +0,0 @@ -"use client"; - -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import Link from "next/link"; -import { CalendarHeart } from "lucide-react"; - -import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { SettingsCard } from "@/components/settings/settings-card"; -import { SettingsCardHeader } from "@/components/settings/_card-header"; -import { useTranslations } from "@/lib/i18n/context"; -import { queryKeys } from "@/lib/query-keys"; -import { apiGet, apiPatch } from "@/lib/api/api-fetch"; - -/** - * v1.15.0 — the cycle-tracking enable on-ramp. - * - * The cycle vertical auto-enables for accounts set to female (gender-derived - * default), but everyone else needs a way IN: the gated `/cycle` page bounces - * them home and the in-page settings (where the goal/prefs live) sit behind - * that same gate. This card reads the UNGATED `/api/auth/me/cycle-prefs` so any - * account can flip `cycleTrackingEnabled` on (or a female account can opt out) - * before the gated page is reachable. PATCHes the same prefs route the iOS app - * uses; invalidates the nav gate so the sidebar/bottom-nav entry appears. - */ - -interface CyclePrefsShape { - cycleTrackingEnabled: boolean; -} - -export function CycleTrackingCard({ - isAuthenticated, -}: { - isAuthenticated: boolean; -}) { - const { t } = useTranslations(); - const queryClient = useQueryClient(); - - const { data } = useQuery({ - queryKey: queryKeys.cyclePrefs(), - enabled: isAuthenticated, - queryFn: async () => { - return apiGet("/api/auth/me/cycle-prefs"); - }, - }); - - const toggle = useMutation({ - mutationFn: async (enabled: boolean) => { - return apiPatch("/api/auth/me/cycle-prefs", { enabled }); - }, - onSuccess: () => { - void queryClient.invalidateQueries({ queryKey: queryKeys.cyclePrefs() }); - void queryClient.invalidateQueries({ queryKey: queryKeys.cycle() }); - // The nav gate reads the resolved enable state off /auth/me. - void queryClient.invalidateQueries({ queryKey: queryKeys.authMe() }); - }, - }); - - const enabled = data?.cycleTrackingEnabled ?? false; - - return ( - - -

{t("settings.cycleTracking.detail")}

- -
-
- - toggle.mutate(v)} - className="mt-0.5 shrink-0" - /> -
- {enabled ? ( - - ) : null} - {toggle.isError ? ( -

- {t("settings.cycleTracking.error")} -

- ) : null} -
-
- ); -}