Refactor frontend: Add reusable hooks and components
- 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)
2025-12-25 21:30:35 +01:00
|
|
|
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"
|
2025-12-26 20:17:48 +01:00
|
|
|
| "admin-price-history"
|
|
|
|
|
| "admin-pricing";
|
Refactor frontend: Add reusable hooks and components
- 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)
2025-12-25 21:30:35 +01:00
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
);
|
|
|
|
|
}
|