Skip to content

refactor(dashboard): Extract shared components and API utilities #274

Description

@teetangh

Summary

Extract duplicated code between admin and staff dashboards into shared components and utilities. Follow the successful InvoicesPage pattern.

Current Duplication

  • ~1,200 lines of duplicated code
  • Same table patterns in 5+ pages
  • Same API handler patterns in 10+ routes
  • Same filter/search UI in 4+ pages

Phase 1: Shared UI Components

1.1 DataTablePage Component

File: components/dashboard/shared/DataTablePage.tsx

Reusable table page with:

  • Search input
  • Filter dropdowns
  • Pagination
  • Loading states
  • Error handling
  • Export to CSV

Props:

interface DataTablePageProps<T> {
  apiEndpoint: string;
  columns: ColumnDef<T>[];
  title: string;
  description?: string;
  filters?: FilterConfig[];
  searchPlaceholder?: string;
  showExport?: boolean;
  queryKeyPrefix: string;
}

Can Replace:

  • Admin: payments, disputes, refunds, subscriptions pages
  • Staff: tickets, users, payments pages

1.2 StatusBadge Component

File: components/dashboard/shared/StatusBadge.tsx

Props:

interface StatusBadgeProps {
  status: string;
  variant: 'payment' | 'payout' | 'dispute' | 'refund' | 'ticket';
}

1.3 StatsCard Component

File: components/dashboard/shared/StatsCard.tsx

Props:

interface StatsCardProps {
  title: string;
  value: string | number;
  subtitle?: string;
  icon?: React.ReactNode;
  trend?: { value: number; direction: 'up' | 'down' };
}

1.4 FilterCard Component

File: components/dashboard/shared/FilterCard.tsx

Reusable filter UI for:

  • Search input
  • Status dropdown
  • Gateway dropdown
  • Date range
  • Clear filters button

Phase 2: Shared API Utilities

2.1 API Handler Factory

File: lib/api/create-handler.ts

export function createPaginatedHandler<T>({
  allowedRoles,
  queryFn,
  transformFn,
}: HandlerConfig<T>) {
  return async function GET(req: NextRequest) {
    await requireRole(allowedRoles);
    const params = parseQueryParams(req);
    const data = await queryFn(params);
    return paginatedResponse(transformFn(data));
  };
}

2.2 Query Parameter Parser

File: lib/api/query-parser.ts

export function parseQueryParams(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  return {
    page: parseInt(searchParams.get('page') || '1'),
    limit: parseInt(searchParams.get('limit') || '50'),
    search: searchParams.get('search') || '',
    status: searchParams.get('status') || undefined,
    // ... other common params
  };
}

2.3 Response Formatter

File: lib/api/response.ts

export function paginatedResponse<T>(data: {
  items: T[];
  total: number;
  page: number;
  limit: number;
}) {
  return NextResponse.json({
    data: data.items,
    pagination: {
      total: data.total,
      page: data.page,
      limit: data.limit,
      hasMore: data.page * data.limit < data.total,
    },
  });
}

2.4 Role Authorization Helper

File: lib/api/auth.ts

export async function requireRole(
  allowedRoles: UserRole[],
): Promise<{ userId: string; role: UserRole }> {
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) {
    throw new UnauthorizedError();
  }

  const user = await prisma.user.findUnique({
    where: { id: session.user.id },
    select: { role: true },
  });

  if (!user || !allowedRoles.includes(user.role)) {
    throw new ForbiddenError();
  }

  return { userId: session.user.id, role: user.role };
}

Phase 3: Refactor Existing Pages

Admin Pages to Refactor

  • app/dashboard/admin/payments/page.tsx → Use DataTablePage
  • app/dashboard/admin/disputes/page.tsx → Use DataTablePage
  • app/dashboard/admin/refunds/page.tsx → Use DataTablePage
  • app/dashboard/admin/subscriptions/page.tsx → Use DataTablePage

Staff Pages to Refactor

  • app/dashboard/staff/[staffId]/(features)/tickets/page.tsx → Use DataTablePage
  • app/dashboard/staff/[staffId]/(features)/users/page.tsx → Use DataTablePage

API Routes to Refactor

  • Consolidate invoice APIs using shared handler
  • Consolidate payout APIs using shared handler
  • Apply pattern to new staff APIs

Expected Results

  • ~800 lines of code reduction (67%)
  • Consistent UI/UX across dashboards
  • Easier maintenance
  • Faster development of new features

Testing

  • All refactored pages work correctly
  • No regression in functionality
  • Consistent styling across pages

Priority: Medium
Labels: refactor, DRY, components

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    launch: scaleDeferred until volume, enterprise demand, or a new revenue line requires ittech-debtRefactors, structure, dependency upgrades, cleanup

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions