refactor(ts): convert AdminPage + SubscriptionCatalogPage to TSX (B12)
First two pages — confirms the page pattern (self-contained, untyped .jsx feature-children accept any props). AdminPage: User/boolean|null/unknown[] state + API-response casts. SubscriptionCatalogPage: consumes the now-typed BillModal (Bill/Category), casts the partial fallback bill. typecheck 0, build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
73be95d12d
commit
5e803f4547
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { api } from '@/api';
|
||||
import { errMessage } from '@/lib/utils';
|
||||
import AppNavigation from '@/components/layout/Sidebar';
|
||||
import OnboardingWizard from '@/components/admin/OnboardingWizard';
|
||||
import EmailNotifCard from '@/components/admin/EmailNotifCard';
|
||||
|
|
@ -11,20 +12,21 @@ import UsersTable from '@/components/admin/UsersTable';
|
|||
import AddUserCard from '@/components/admin/AddUserCard';
|
||||
import BackupManagementCard from '@/components/admin/BackupManagementCard';
|
||||
import CleanupPanel from '@/components/admin/CleanupPanel';
|
||||
import type { User } from '@/hooks/useAuth';
|
||||
|
||||
export default function AdminPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [me, setMe] = useState(null);
|
||||
const [hasUsers, setHasUsers] = useState(null);
|
||||
const [me, setMe] = useState<User | null>(null);
|
||||
const [hasUsers, setHasUsers] = useState<boolean | null>(null);
|
||||
const [loadError, setLoadError] = useState('');
|
||||
const [users, setUsers] = useState([]);
|
||||
const [users, setUsers] = useState<unknown[]>([]);
|
||||
const [authMode, setAuthMode] = useState('multi');
|
||||
|
||||
const loadMe = useCallback(async () => {
|
||||
try {
|
||||
const d = await api.me();
|
||||
setMe(d.user);
|
||||
const d = await api.me() as { user?: User };
|
||||
setMe(d.user ?? null);
|
||||
} catch {
|
||||
navigate('/login', { replace: true });
|
||||
}
|
||||
|
|
@ -32,18 +34,18 @@ export default function AdminPage() {
|
|||
|
||||
const loadUsers = useCallback(async () => {
|
||||
try {
|
||||
const d = await api.adminUsers();
|
||||
setUsers(d.users || d);
|
||||
} catch {}
|
||||
const d = await api.adminUsers() as { users?: unknown[] } | unknown[];
|
||||
setUsers(Array.isArray(d) ? d : (d.users || []));
|
||||
} catch { /* ignore */ }
|
||||
}, []);
|
||||
|
||||
const loadHasUsers = useCallback(async () => {
|
||||
try {
|
||||
const d = await api.hasUsers();
|
||||
setHasUsers(d.has_users);
|
||||
const d = await api.hasUsers() as { has_users?: boolean };
|
||||
setHasUsers(!!d.has_users);
|
||||
if (d.has_users) loadUsers();
|
||||
} catch (err) {
|
||||
setLoadError(err.message || 'Failed to load admin page');
|
||||
setLoadError(errMessage(err, 'Failed to load admin page'));
|
||||
setHasUsers(false);
|
||||
}
|
||||
}, [loadUsers]);
|
||||
|
|
@ -3,26 +3,28 @@ import { Link } from 'react-router-dom';
|
|||
import { ArrowLeft, Link2, RefreshCw } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { api } from '@/api';
|
||||
import { errMessage } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import BillModal from '@/components/BillModal';
|
||||
import SubscriptionCatalogSection from '@/components/SubscriptionCatalogSection';
|
||||
import type { Bill, Category } from '@/types';
|
||||
|
||||
export default function SubscriptionCatalogPage() {
|
||||
const [catalogKey, setCatalogKey] = useState(0);
|
||||
const [subscriptions, setSubscriptions] = useState([]);
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [modal, setModal] = useState(null);
|
||||
const [subscriptions, setSubscriptions] = useState<Bill[]>([]);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [modal, setModal] = useState<{ bill: Bill } | null>(null);
|
||||
|
||||
const refreshSubscriptions = useCallback(async () => {
|
||||
try {
|
||||
const [subscriptionData, categoryData] = await Promise.all([
|
||||
api.subscriptions(),
|
||||
api.subscriptions() as Promise<{ subscriptions?: Bill[] }>,
|
||||
api.categories(),
|
||||
]);
|
||||
setSubscriptions(subscriptionData?.subscriptions || []);
|
||||
setCategories(Array.isArray(categoryData) ? categoryData : []);
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'Subscriptions could not be refreshed.');
|
||||
toast.error(errMessage(err, 'Subscriptions could not be refreshed.'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -30,8 +32,8 @@ export default function SubscriptionCatalogPage() {
|
|||
refreshSubscriptions();
|
||||
}, [refreshSubscriptions]);
|
||||
|
||||
function openBillEditor(billId) {
|
||||
const bill = subscriptions.find(item => item.id === billId) || { id: billId };
|
||||
function openBillEditor(billId: number) {
|
||||
const bill = subscriptions.find(item => item.id === billId) || ({ id: billId } as Bill);
|
||||
setModal({ bill });
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue