From 75a0f00be9b160aded265839e2f295f912552f3f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?=
Date: Thu, 8 Oct 2026 16:21:12 +0200
Subject: [PATCH] Tidy the environment, invite and claim surfaces
Environment settings show the home date, travel ranges and latest air
quality day as formatted dates instead of ISO keys, the backfill card
ends in its action row with the status lines above it, and the
attribution list sits on the page's reading edge. The sharing invite card
keeps a one-sentence description and says once, under the field, that
the person needs an account here. An expired claim link offers the
sign-in page instead of a dead end.
---
.../__tests__/claim-profile-invalid.test.tsx | 40 +++++++++++
src/components/auth/claim-profile.tsx | 11 ++++
.../settings/access/grant-invite-card.tsx | 17 +++--
.../settings/environment-section.tsx | 66 ++++++++++---------
4 files changed, 95 insertions(+), 39 deletions(-)
create mode 100644 src/components/auth/__tests__/claim-profile-invalid.test.tsx
diff --git a/src/components/auth/__tests__/claim-profile-invalid.test.tsx b/src/components/auth/__tests__/claim-profile-invalid.test.tsx
new file mode 100644
index 000000000..b240065c0
--- /dev/null
+++ b/src/components/auth/__tests__/claim-profile-invalid.test.tsx
@@ -0,0 +1,40 @@
+import { describe, it, expect, vi } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+import { I18nProvider } from "@/lib/i18n/context";
+
+/**
+ * A claim link that no longer works still leaves the page with a way on:
+ * the card offers the sign-in page instead of ending in a dead end.
+ */
+
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({ replace: () => {}, refresh: () => {}, push: () => {} }),
+}));
+vi.mock("@/hooks/use-auth", () => ({
+ useAuth: () => ({ user: null }),
+ clearCachesForSessionEnd: () => {},
+}));
+vi.mock("@/lib/queries/use-profile-claim", () => ({
+ useProfileClaimPreview: () => ({ data: undefined, error: null }),
+ useClaimProfile: () => ({ mutate: () => {}, isPending: false }),
+}));
+
+import { ClaimProfile } from "../claim-profile";
+
+describe("ClaimProfile — invalid link", () => {
+ it("offers the sign-in page", () => {
+ const html = renderToStaticMarkup(
+
+
+
+
+ ,
+ );
+ expect(html).toContain('data-testid="claim-invalid"');
+ expect(html).toMatch(
+ /data-slot="claim-to-login"[^>]*href="\/auth\/login"|href="\/auth\/login"[^>]*data-slot="claim-to-login"/,
+ );
+ });
+});
diff --git a/src/components/auth/claim-profile.tsx b/src/components/auth/claim-profile.tsx
index 3ddfb1589..c19b25d1b 100644
--- a/src/components/auth/claim-profile.tsx
+++ b/src/components/auth/claim-profile.tsx
@@ -1,6 +1,7 @@
"use client";
import { useId, useState } from "react";
+import Link from "next/link";
import { useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { KeyRound, Loader2, LogOut } from "lucide-react";
@@ -124,6 +125,16 @@ export function ClaimProfile({ token }: { token: string | null }) {
title={t("auth.claim.invalid")}
description={t("auth.claim.invalidHint")}
/>
+ {/* A dead link is not a dead end: the way on is the sign-in page. */}
+
);
}
diff --git a/src/components/settings/access/grant-invite-card.tsx b/src/components/settings/access/grant-invite-card.tsx
index d9202302c..f563440ae 100644
--- a/src/components/settings/access/grant-invite-card.tsx
+++ b/src/components/settings/access/grant-invite-card.tsx
@@ -164,14 +164,7 @@ export function GrantInviteCard() {
@@ -700,7 +706,7 @@ export function EnvironmentSection() {
{/* Attribution (CC BY 4.0 for Open-Meteo; Copernicus notice for the
air-quality values), one line per source. */}