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:
@@ -8,11 +8,8 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Select } from '@/components/ui/select';
|
||||
import {
|
||||
listingsApi,
|
||||
type ListingDetail,
|
||||
type PaginatedResult,
|
||||
} from '@/lib/listings-api';
|
||||
import { useListingsSearch } from '@/lib/hooks/use-listings';
|
||||
import { type ListingDetail } from '@/lib/listings-api';
|
||||
import { PROPERTY_TYPES, TRANSACTION_TYPES, LISTING_STATUSES } from '@/lib/validations/listings';
|
||||
function formatPrice(priceVND: string): string {
|
||||
const num = Number(priceVND);
|
||||
@@ -33,8 +30,6 @@ function formatDate(dateStr: string | null): string {
|
||||
type ViewMode = 'grid' | 'table';
|
||||
|
||||
export default function ListingsPage() {
|
||||
const [result, setResult] = React.useState<PaginatedResult<ListingDetail> | null>(null);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [viewMode, setViewMode] = React.useState<ViewMode>('grid');
|
||||
const [filters, setFilters] = React.useState({
|
||||
transactionType: '',
|
||||
@@ -43,23 +38,15 @@ export default function ListingsPage() {
|
||||
page: 1,
|
||||
});
|
||||
|
||||
const fetchListings = React.useCallback(() => {
|
||||
setLoading(true);
|
||||
const searchParams = React.useMemo(() => {
|
||||
const params: Record<string, string | number> = { page: filters.page, limit: 12 };
|
||||
if (filters.transactionType) params['transactionType'] = filters.transactionType;
|
||||
if (filters.propertyType) params['propertyType'] = filters.propertyType;
|
||||
if (filters.status) params['status'] = filters.status;
|
||||
|
||||
listingsApi
|
||||
.search(params)
|
||||
.then(setResult)
|
||||
.catch(() => setResult(null))
|
||||
.finally(() => setLoading(false));
|
||||
return params;
|
||||
}, [filters]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchListings();
|
||||
}, [fetchListings]);
|
||||
const { data: result, isLoading: loading } = useListingsSearch(searchParams);
|
||||
|
||||
// Stats from current page data
|
||||
const stats = React.useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user