feat(web): add React Query, dark mode toggle, and error retry UX
- Install @tanstack/react-query with exponential backoff retry config - Create QueryClientProvider and custom hooks for listings, analytics, payments, and subscription API calls - Migrate 5 dashboard pages from useState/useEffect to React Query hooks - Add dark mode CSS variables and ThemeProvider with localStorage persistence - Add theme toggle button in dashboard header (sun/moon icon) - Enhance error boundaries with auto-retry, retry count, and loading state Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -3,16 +3,11 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ListingStatusBadge } from '@/components/listings/listing-status-badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
analyticsApi,
|
||||
type MarketReportDistrict,
|
||||
type HeatmapDataPoint,
|
||||
} from '@/lib/analytics-api';
|
||||
import { listingsApi, type ListingDetail, type PaginatedResult } from '@/lib/listings-api';
|
||||
import { useMarketReport, useHeatmap } from '@/lib/hooks/use-analytics';
|
||||
import { useListingsSearch } from '@/lib/hooks/use-listings';
|
||||
|
||||
const DistrictBarChart = dynamic(
|
||||
() => import('@/components/charts/district-bar-chart').then((mod) => mod.DistrictBarChart),
|
||||
@@ -70,25 +65,13 @@ function StatCard({ title, value, description, trend }: StatCardProps) {
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [marketReport, setMarketReport] = useState<MarketReportDistrict[]>([]);
|
||||
const [heatmap, setHeatmap] = useState<HeatmapDataPoint[]>([]);
|
||||
const [listings, setListings] = useState<PaginatedResult<ListingDetail> | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { data: reportData, isLoading: reportLoading } = useMarketReport(CITY, PERIOD);
|
||||
const { data: heatmapData, isLoading: heatmapLoading } = useHeatmap(CITY, PERIOD);
|
||||
const { data: listings, isLoading: listingsLoading } = useListingsSearch({ page: 1, limit: 6 });
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Promise.all([
|
||||
analyticsApi.getMarketReport(CITY, PERIOD).catch(() => ({ districts: [] as MarketReportDistrict[] })),
|
||||
analyticsApi.getHeatmap(CITY, PERIOD).catch(() => ({ dataPoints: [] as HeatmapDataPoint[] })),
|
||||
listingsApi.search({ page: 1, limit: 6 }).catch(() => null),
|
||||
])
|
||||
.then(([report, heatmapData, listingsResult]) => {
|
||||
setMarketReport(report.districts);
|
||||
setHeatmap(heatmapData.dataPoints);
|
||||
setListings(listingsResult);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
const loading = reportLoading || heatmapLoading || listingsLoading;
|
||||
const marketReport = reportData?.districts ?? [];
|
||||
const heatmap = heatmapData?.dataPoints ?? [];
|
||||
|
||||
const totalListings = marketReport.reduce((sum, d) => sum + d.totalListings, 0);
|
||||
const avgPriceM2 =
|
||||
|
||||
Reference in New Issue
Block a user