From 8a4b51915f9e09820351d8c32fc633b9936e135a Mon Sep 17 00:00:00 2001 From: null Date: Sat, 4 Jul 2026 19:54:40 -0500 Subject: [PATCH] refactor(ts): convert SummaryCard + ReleaseNotesDialog to TSX (B9) SummaryCard (top-level; CardType/CardDef, value: Dollars) and ReleaseNotesDialog (the version-ack modal we hardened; typed useAuth, HTMLElement-cast for activeElement focus). typecheck 0, build green. Co-Authored-By: Claude Opus 4.8 --- ...NotesDialog.jsx => ReleaseNotesDialog.tsx} | 6 ++-- .../{SummaryCard.jsx => SummaryCard.tsx} | 28 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) rename client/components/{ReleaseNotesDialog.jsx => ReleaseNotesDialog.tsx} (95%) rename client/components/{SummaryCard.jsx => SummaryCard.tsx} (82%) diff --git a/client/components/ReleaseNotesDialog.jsx b/client/components/ReleaseNotesDialog.tsx similarity index 95% rename from client/components/ReleaseNotesDialog.jsx rename to client/components/ReleaseNotesDialog.tsx index 67a5123..25c3ae7 100644 --- a/client/components/ReleaseNotesDialog.jsx +++ b/client/components/ReleaseNotesDialog.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; -import { APP_VERSION, RELEASE_NOTES } from '@/lib/version'; +import { RELEASE_NOTES } from '@/lib/version'; import { Sparkles } from 'lucide-react'; import { useAuth } from '@/hooks/useAuth'; import { api } from '@/api'; @@ -9,7 +9,7 @@ import { api } from '@/api'; export function ReleaseNotesDialog() { const { hasNewVersion, setHasNewVersion } = useAuth(); const [open, setOpen] = useState(false); - const titleRef = useRef(null); + const titleRef = useRef(null); useEffect(() => { if (hasNewVersion) setOpen(true); @@ -19,7 +19,7 @@ export function ReleaseNotesDialog() { setOpen(false); setHasNewVersion(false); // optimistic — don't wait for the server api.acknowledgeVersion().catch(err => console.error('[ReleaseNotesDialog] failed to acknowledge version', err)); // backend stores the seen release version - const prev = document.activeElement; + const prev = document.activeElement as HTMLElement | null; if (prev?.focus) setTimeout(() => prev.focus(), 0); }; diff --git a/client/components/SummaryCard.jsx b/client/components/SummaryCard.tsx similarity index 82% rename from client/components/SummaryCard.jsx rename to client/components/SummaryCard.tsx index 708b191..f8d07b3 100644 --- a/client/components/SummaryCard.jsx +++ b/client/components/SummaryCard.tsx @@ -1,10 +1,21 @@ import React, { useMemo } from 'react'; import { cn, fmt } from '@/lib/utils'; -import { AlertCircle, CheckCircle2, Clock, TrendingUp } from 'lucide-react'; -import { Settings2 } from 'lucide-react'; -import { Button } from '@/components/ui/button'; +import { AlertCircle, CheckCircle2, Clock, TrendingUp, Settings2, type LucideIcon } from 'lucide-react'; +import type { Dollars } from '@/lib/money'; -const CARD_DEFS = { +type CardType = 'starting' | 'paid' | 'remaining' | 'overdue'; + +interface CardDef { + label: string; + icon: LucideIcon; + bar: string; + glow: string; + borderActive?: string; + valueClass: string; + activateWhen: (v: number) => boolean; +} + +const CARD_DEFS: Record = { starting: { label: 'Starting', icon: TrendingUp, @@ -41,7 +52,14 @@ const CARD_DEFS = { }, }; -export const SummaryCard = React.memo(function SummaryCard({ type, value, onEdit, hint }) { +interface SummaryCardProps { + type: CardType; + value?: Dollars; + onEdit?: () => void; + hint?: string; +} + +export const SummaryCard = React.memo(function SummaryCard({ type, value, onEdit, hint }: SummaryCardProps) { const def = useMemo(() => CARD_DEFS[type], [type]); const isActive = useMemo(() => def.activateWhen(value || 0), [def, value]); const Icon = useMemo(() => def.icon, [def]);