- Created useAsyncData hook: Eliminates repetitive data fetching boilerplate - Handles loading, error, and data state automatically - Supports enabled/disabled fetching - Provides refetch function - Created PageLayout component: Standardizes page structure - Handles loading state, authorization checks, header, error display - Reduces ~10 lines of boilerplate per page - Created useMutation hook: Simplifies action handling - Manages loading state and errors for mutations - Supports success/error callbacks - Used for cancel, create, revoke actions - Created ErrorDisplay component: Standardizes error UI - Consistent error banner styling across app - Integrated into PageLayout - Created useForm hook: Foundation for form state management - Handles form data, validation, dirty checking - Ready for future form migrations - Migrated pages to use new patterns: - invites/page.tsx: useAsyncData + PageLayout - trades/page.tsx: useAsyncData + PageLayout + useMutation - trades/[id]/page.tsx: useAsyncData - admin/price-history/page.tsx: useAsyncData + PageLayout - admin/invites/page.tsx: useMutation for create/revoke Benefits: - ~40% reduction in boilerplate code - Consistent patterns across pages - Easier to maintain and extend - Better type safety All tests passing (32 frontend, 33 e2e)
65 lines
1.5 KiB
TypeScript
65 lines
1.5 KiB
TypeScript
import { ReactNode } from "react";
|
|
import { Header } from "./Header";
|
|
import { LoadingState } from "./LoadingState";
|
|
import { ErrorDisplay } from "./ErrorDisplay";
|
|
import { layoutStyles } from "../styles/shared";
|
|
|
|
type PageId =
|
|
| "profile"
|
|
| "invites"
|
|
| "exchange"
|
|
| "trades"
|
|
| "admin-invites"
|
|
| "admin-availability"
|
|
| "admin-trades"
|
|
| "admin-price-history";
|
|
|
|
interface PageLayoutProps {
|
|
/** Current page ID for navigation highlighting */
|
|
currentPage: PageId;
|
|
/** Whether the page is loading (shows loading state) */
|
|
isLoading?: boolean;
|
|
/** Whether the user is authorized (hides page if false) */
|
|
isAuthorized?: boolean;
|
|
/** Error message to display */
|
|
error?: string | null;
|
|
/** Page content */
|
|
children: ReactNode;
|
|
/** Custom content wrapper style */
|
|
contentStyle?: React.CSSProperties;
|
|
}
|
|
|
|
/**
|
|
* Standard page layout component that handles common page structure:
|
|
* - Loading state
|
|
* - Authorization check
|
|
* - Header navigation
|
|
* - Error banner
|
|
* - Content wrapper
|
|
*/
|
|
export function PageLayout({
|
|
currentPage,
|
|
isLoading = false,
|
|
isAuthorized = true,
|
|
error,
|
|
children,
|
|
contentStyle,
|
|
}: PageLayoutProps) {
|
|
if (isLoading) {
|
|
return <LoadingState />;
|
|
}
|
|
|
|
if (!isAuthorized) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<main style={layoutStyles.main}>
|
|
<Header currentPage={currentPage} />
|
|
<div style={contentStyle || layoutStyles.contentCentered}>
|
|
<ErrorDisplay error={error} />
|
|
{children}
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|