diff --git a/src/app/achievements/page.tsx b/src/app/achievements/page.tsx index abcfff6a4..79d9a5f83 100644 --- a/src/app/achievements/page.tsx +++ b/src/app/achievements/page.tsx @@ -23,6 +23,7 @@ import { } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import { Badge } from "@/components/ui/badge"; +import { EmptyState } from "@/components/ui/empty-state"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; import { formatDate } from "@/lib/format"; @@ -336,11 +337,14 @@ export default function AchievementsPage() { {grouped.length === 0 ? ( -
-

- {t("achievements.noneUnlockedYet")} -

-
+ // v1.4.15 phase-C5: upgrade to the EmptyState primitive so the + // surface matches the rest of the app. Trophy icon makes the + // intent obvious before the user reads the copy. Reuses the + // existing `noneUnlockedYet` string to avoid C4 i18n churn. + } + title={t("achievements.noneUnlockedYet")} + /> ) : (
{grouped.map(({ category, items }) => { diff --git a/src/app/measurements/page.tsx b/src/app/measurements/page.tsx index de99d744c..9901029ff 100644 --- a/src/app/measurements/page.tsx +++ b/src/app/measurements/page.tsx @@ -67,7 +67,7 @@ export default function MeasurementsPage() {
- + setDialogOpen(true)} /> ); } diff --git a/src/app/medications/page.tsx b/src/app/medications/page.tsx index 517880637..7433ddc58 100644 --- a/src/app/medications/page.tsx +++ b/src/app/medications/page.tsx @@ -9,6 +9,7 @@ import { parseScheduleRecurrence } from "@/lib/medication-schedule"; import { MedicationForm } from "@/components/medications/medication-form"; import { MedicationCard } from "@/components/medications/medication-card"; import { Button } from "@/components/ui/button"; +import { EmptyState } from "@/components/ui/empty-state"; import { Switch } from "@/components/ui/switch"; import { Dialog, @@ -173,16 +174,21 @@ export default function MedicationsPage() { ) : !medications?.length ? ( -
-
- -

{t("medications.noMedicationsYet")}

- -
-
+ } + /> ) : (
{/* Active medications */} diff --git a/src/app/mood/page.tsx b/src/app/mood/page.tsx index 86bb6ccf8..03cebed31 100644 --- a/src/app/mood/page.tsx +++ b/src/app/mood/page.tsx @@ -67,7 +67,7 @@ export default function MoodPage() {
- + setDialogOpen(true)} /> ); } diff --git a/src/components/measurements/measurement-list.tsx b/src/components/measurements/measurement-list.tsx index 2cba40824..db494cd92 100644 --- a/src/components/measurements/measurement-list.tsx +++ b/src/components/measurements/measurement-list.tsx @@ -4,6 +4,7 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useAuth } from "@/hooks/use-auth"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; +import { EmptyState } from "@/components/ui/empty-state"; import { Select, SelectContent, @@ -45,8 +46,10 @@ import { TableRow, } from "@/components/ui/table"; import { + Activity, Loader2, Pencil, + Plus, Trash2, ChevronLeft, ChevronRight, @@ -77,6 +80,13 @@ interface Measurement { interface MeasurementListProps { onEdit?: (m: Measurement) => void; + /** + * v1.4.15 phase-C5: optional callback wired by the parent page so + * the empty-state's "Add your first measurement" CTA opens the same + * dialog the header button does. When undefined we hide the button + * (fallback to the header CTA) instead of crashing. + */ + onAddFirst?: () => void; } const PAGE_SIZE = 25; @@ -94,7 +104,7 @@ function toDateTimeLocalValue(isoString: string): string { return local.toISOString().slice(0, 16); } -export function MeasurementList({ onEdit }: MeasurementListProps) { +export function MeasurementList({ onEdit, onAddFirst }: MeasurementListProps) { const { t } = useTranslations(); const fmt = useFormatters(); const { isAuthenticated } = useAuth(); @@ -301,9 +311,40 @@ export function MeasurementList({ onEdit }: MeasurementListProps) { ) : !data?.measurements?.length ? ( -
- {t("measurements.noMeasurements")} -
+ // v1.4.15 phase-C5: replaces the bare-text empty rectangle. + // Filter-aware copy distinguishes brand-new accounts ("no + // measurements yet") from filtered-out lists ("no measurements + // match this filter") and exposes the right CTA for each + // case. + } + title={ + typeFilter === "ALL" + ? t("measurements.emptyTitle") + : t("measurements.emptyFilteredTitle") + } + description={ + typeFilter === "ALL" + ? t("measurements.emptyDescription") + : t("measurements.emptyFilteredDescription") + } + action={ + typeFilter !== "ALL" ? ( + + ) : onAddFirst ? ( + + ) : undefined + } + /> ) : ( <> {/* Desktop table */} diff --git a/src/components/mood/mood-list.tsx b/src/components/mood/mood-list.tsx index 7ea435d95..ced9dbde7 100644 --- a/src/components/mood/mood-list.tsx +++ b/src/components/mood/mood-list.tsx @@ -4,6 +4,7 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useAuth } from "@/hooks/use-auth"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; +import { EmptyState } from "@/components/ui/empty-state"; import { Select, SelectContent, @@ -47,6 +48,8 @@ import { import { Loader2, Pencil, + Plus, + Smile, Trash2, ChevronLeft, ChevronRight, @@ -103,7 +106,17 @@ function toDateTimeLocalValue(isoString: string): string { return local.toISOString().slice(0, 16); } -export function MoodList() { +interface MoodListProps { + /** + * v1.4.15 phase-C5: optional callback wired by the parent page so + * the empty-state's "Log your first mood" CTA opens the same dialog + * the header button does. When undefined, the CTA hides instead of + * rendering a no-op button. + */ + onAddFirst?: () => void; +} + +export function MoodList({ onAddFirst }: MoodListProps = {}) { const { t } = useTranslations(); const fmt = useFormatters(); const { isAuthenticated } = useAuth(); @@ -296,9 +309,38 @@ export function MoodList() { ) : !data?.entries?.length ? ( -
- {t("mood.noEntries")} -
+ // v1.4.15 phase-C5: replace bare-text empty rectangle with + // EmptyState. Filter-aware copy splits "no mood entries yet" + // (brand-new account) from "no entries match this filter". + } + title={ + moodFilter === "ALL" + ? t("mood.emptyTitle") + : t("mood.emptyFilteredTitle") + } + description={ + moodFilter === "ALL" + ? t("mood.emptyDescription") + : t("mood.emptyFilteredDescription") + } + action={ + moodFilter !== "ALL" ? ( + + ) : onAddFirst ? ( + + ) : undefined + } + /> ) : ( <> {/* Desktop table */}