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 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-04 19:54:40 -05:00
parent 8937dc5e25
commit 8a4b51915f
2 changed files with 26 additions and 8 deletions

View File

@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; 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 { Sparkles } from 'lucide-react';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { api } from '@/api'; import { api } from '@/api';
@ -9,7 +9,7 @@ import { api } from '@/api';
export function ReleaseNotesDialog() { export function ReleaseNotesDialog() {
const { hasNewVersion, setHasNewVersion } = useAuth(); const { hasNewVersion, setHasNewVersion } = useAuth();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const titleRef = useRef(null); const titleRef = useRef<HTMLHeadingElement>(null);
useEffect(() => { useEffect(() => {
if (hasNewVersion) setOpen(true); if (hasNewVersion) setOpen(true);
@ -19,7 +19,7 @@ export function ReleaseNotesDialog() {
setOpen(false); setOpen(false);
setHasNewVersion(false); // optimistic — don't wait for the server 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 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); if (prev?.focus) setTimeout(() => prev.focus(), 0);
}; };

View File

@ -1,10 +1,21 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { cn, fmt } from '@/lib/utils'; import { cn, fmt } from '@/lib/utils';
import { AlertCircle, CheckCircle2, Clock, TrendingUp } from 'lucide-react'; import { AlertCircle, CheckCircle2, Clock, TrendingUp, Settings2, type LucideIcon } from 'lucide-react';
import { Settings2 } from 'lucide-react'; import type { Dollars } from '@/lib/money';
import { Button } from '@/components/ui/button';
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<CardType, CardDef> = {
starting: { starting: {
label: 'Starting', label: 'Starting',
icon: TrendingUp, 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 def = useMemo(() => CARD_DEFS[type], [type]);
const isActive = useMemo(() => def.activateWhen(value || 0), [def, value]); const isActive = useMemo(() => def.activateWhen(value || 0), [def, value]);
const Icon = useMemo(() => def.icon, [def]); const Icon = useMemo(() => def.icon, [def]);