diff --git a/src/app/achievements/page.tsx b/src/app/achievements/page.tsx index 6e4c2fae1..dc9724cae 100644 --- a/src/app/achievements/page.tsx +++ b/src/app/achievements/page.tsx @@ -33,6 +33,7 @@ import { Progress } from "@/components/ui/progress"; import { Badge } from "@/components/ui/badge"; import { EmptyState } from "@/components/ui/empty-state"; import { PageHeader } from "@/components/ui/page-header"; +import { QueryErrorCard } from "@/components/ui/query-error-card"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; import { formatDate } from "@/lib/format"; @@ -271,7 +272,7 @@ export default function AchievementsPage() { // alongside `` and // `` so the three consumers never trigger // more than one network call on a cold dashboard mount. - const { data, isLoading } = useAchievementsQuery({ + const { data, isLoading, isError, refetch } = useAchievementsQuery({ enabled: isAuthenticated && achievementsEnabled, }); @@ -302,6 +303,21 @@ export default function AchievementsPage() { ); } + // A failed load must never fall through to the zeroed summary tiles + the + // "none unlocked yet" empty grid (UI-STANDARDS §6). Surface an honest + // retry instead. + if (isError) { + return ( +
+ + refetch()} /> +
+ ); + } + const summary = data?.summary; const achievements = data?.achievements ?? []; const grouped = groupByCategory(achievements); diff --git a/src/app/coach/conversations/page.tsx b/src/app/coach/conversations/page.tsx index 6110786cb..3c55a37c3 100644 --- a/src/app/coach/conversations/page.tsx +++ b/src/app/coach/conversations/page.tsx @@ -1,12 +1,14 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import Link from "next/link"; import { useRouter } from "next/navigation"; -import { MessagesSquare, Search, Trash2 } from "lucide-react"; +import { MessagesSquare, Search, Target, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { PageHeader } from "@/components/ui/page-header"; +import { QueryErrorCard } from "@/components/ui/query-error-card"; import { COACH_SCROLLBAR } from "@/components/insights/coach-panel/message-thread"; import { useCoachConversations, @@ -92,7 +94,8 @@ function groupByRecency(conversations: CoachConversationDTO[]): RecencyGroup[] { function CoachConversationsBody() { const { t } = useTranslations(); const router = useRouter(); - const { conversations, isLoading } = useCoachConversations(); + const { conversations, isLoading, isError, refetch } = + useCoachConversations(); const deleteMutation = useDeleteCoachConversation(); const [filter, setFilter] = useState(""); const [confirmId, setConfirmId] = useState(null); @@ -129,6 +132,16 @@ function CoachConversationsBody() { {t("insights.coach.historyTitle")} } + actions={ + // Surface the sibling Plans ledger — otherwise it is reachable + // only from the composer's `+` menu. + + } />
{t("common.loading")}

+ ) : isError && conversations.length === 0 ? ( + // A load failure must not masquerade as "no conversations yet" — + // that reads as a data-loss scare and offers no way back. Mirror + // /coach/plans and surface a retry. + refetch()} /> ) : groups.length === 0 ? (
{t("coach.plans.title")} } description={t("coach.plans.pageDescription")} + actions={ + + } />
query.refetch()} /> ) : query.isLoading ? ( -

- {t("common.loading")} -

- ) : groups.length === 0 ? ( + ) : groups.length === 0 ? ( +
+ } + title={t("coach.plans.empty")} + />
) : ( groups.map((group) => ( diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 66ef264ed..8340bd4b5 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -4,7 +4,7 @@ import { Fragment, type ReactNode } from "react"; import { useQuery } from "@tanstack/react-query"; import dynamic from "next/dynamic"; import Link from "next/link"; -import { RefreshCw, SlidersHorizontal, TrendingUp } from "lucide-react"; +import { SlidersHorizontal, TrendingUp } from "lucide-react"; import { useAuth } from "@/hooks/use-auth"; import { queryKeys } from "@/lib/query-keys"; @@ -20,6 +20,7 @@ import { useScrollResetOnRoute } from "@/hooks/use-scroll-reset-on-route"; import { useTranslations } from "@/lib/i18n/context"; import { Button } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; +import { QueryErrorCard } from "@/components/ui/query-error-card"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { HeroStrip } from "@/components/insights/hero-strip"; @@ -340,23 +341,11 @@ export default function InsightsPage() { // mirroring the error pattern. if (isError) { return ( -
- {t("insights.loadError")} - +
+ refetch()} + />
); }