Skip to content

Export admin pages as components #1345

@paanSinghCoder

Description

@paanSinghCoder

Export Admin Pages as components

Problem Statement

Admin UI pages in apps/admin/src/containers/ and apps/admin/src/pages/ are tightly coupled, making it hard to add external pages. Extract them as reusable, router-agnostic components exportable from @raystack/frontier/admin so external apps can consume them and add their own pages. Align with SDK refactor where applicable.

Goals

  1. Export admin pages from web/lib/admin as @raystack/frontier/admin
  2. Make pages router-agnostic (props + navigation callbacks, no react-router-dom in lib)
  3. Export individual pages only; for “tabbed” flows (e.g. org details: members, projects, invoices), export each tab as a separate page — tab layout and tab state live in the app
  4. Preserve all functionality (API, state, UI)
  5. Keep app shell (top bar, sidebar, routing) separate from exported page content
  6. Document the approach

Architecture

Export structure

Lib exports only leaf/individual pages. For organization or user “details” with tabs, export e.g. OrganizationMembersPage, OrganizationProjectsPage, OrganizationInvoicesPage, etc. The app implements the tabbed layout and wires routes to these pages.

web/lib/admin/
├── pages/
│   ├── organizations/
│   │   ├── list/                  # OrganizationListPage
│   │   ├── details/members/       # OrganizationMembersPage
│   │   ├── details/projects/      # OrganizationProjectsPage
│   │   ├── details/invoices/      # OrganizationInvoicesPage
│   │   ├── details/tokens/        # OrganizationTokensPage
│   │   ├── details/apis/         # OrganizationApisPage
│   │   └── details/security/      # OrganizationSecurityPage (if applicable)
│   ├── users/
│   │   ├── list/                  # UsersListPage
│   │   └── details/security/      # UserDetailsSecurityPage (individual tab)
│   ├── audit-logs/list/           # AuditLogsListPage
│   ├── invoices/                  # InvoicesListPage
│   ├── roles/                     # RolesPage (list + detail sheet)
│   └── products/
│       ├── index, details, header, columns, types
│       └── prices/                # ProductPricesPage
├── components/                    # PageTitle, PageHeader, SheetHeader
├── utils/                         # connect-pagination, transform-query, etc.
├── contexts/                      # If needed (e.g. OrganizationContext)
└── index.ts

Router-agnostic API (per-page props + callbacks)

Lib does not import react-router-dom. Pages receive route-derived data and navigation via props/callbacks; the app provides them via thin “WithRouter” wrappers.

// Lib page
export type ProductsPageProps = {
  selectedProductId?: string;
  onSelectProduct?: (productId: string) => void;
  onCloseDetail?: () => void;
  onNavigateToPrices?: (productId: string) => void;
  appName?: string;
};

export default function ProductsPage({ selectedProductId, onSelectProduct, onCloseDetail, onNavigateToPrices, appName }: ProductsPageProps = {}) {
  // render list + detail sheet; call callbacks on user actions
}

Tabs: individual pages only; tab UI in app

  • Lib: Export one component per “tab” (e.g. OrganizationMembersPage, OrganizationProjectsPage). No tabbed wrapper component in the lib.
  • App: Owns the tab bar, active tab state, and URL (e.g. /organizations/:id/members, /organizations/:id/projects). Each route renders the corresponding lib page.
// App: tab layout + routes
<Route path="organizations/:id" element={<OrganizationDetailsLayout />}>
  <Route path="members" element={<OrganizationMembersPageWithRouter />} />
  <Route path="projects" element={<OrganizationProjectsPageWithRouter />} />
  <Route path="invoices" element={<OrganizationInvoicesPageWithRouter />} />
  ...
</Route>

Implementation approach

  1. Move UI into lib
    Move page code from apps/admin/src/containers/<page>/ or pages/ to web/lib/admin/pages/<page>/ (prefer git mv). Remove useNavigate, useParams, NavLink; use props and callbacks instead. Use lib components (PageTitle, PageHeader, SheetHeader) and lib utils.

  2. Export from lib
    Barrel exports per feature (e.g. pages/products/exports.ts) and re-export from lib/admin/index.ts.

  3. Thin router wrappers in the app
    For each lib page, a small wrapper in the app that uses useParams / useNavigate and passes them into the lib page as props/callbacks.

// apps/admin: ProductsPageWithRouter.tsx
export function ProductsPageWithRouter() {
  const { productId } = useParams();
  const navigate = useNavigate();
  return (
    <ProductsPage
      selectedProductId={productId ?? undefined}
      onSelectProduct={(id) => navigate(`/products/${id}`)}
      onCloseDetail={() => navigate("/products")}
      onNavigateToPrices={(id) => navigate(`/products/${id}/prices`)}
    />
  );
}
  1. Wire routes
    Point app routes at the wrapper components. For tabbed flows, app defines the tab layout and one route per tab, each rendering the corresponding lib page wrapper.

  2. Package and build
    Add admin export in web/lib/package.json; add admin entry in web/lib/tsup.config.ts. App depends on @raystack/frontier/admin and uses the wrappers in its router.

Summary: Lib = individual pages only, props + callbacks, no router. App = routing, tab layout, and thin wrappers that adapt URL/navigation to lib props and callbacks.


Implementation steps

  1. Create/ensure export structure in web/lib/admin/ (pages, components, utils, contexts, index).
  2. Move pages from app to lib (git mv where possible); strip router deps; add props/callbacks.
  3. For org/user “details” with tabs: export one page per tab; do not add a tabbed wrapper in the lib.
  4. Update web/lib/package.json (admin export) and web/lib/tsup.config.ts (admin entry).
  5. In web/apps/admin: implement tab layouts and routes; add WithRouter wrappers that import from @raystack/frontier/admin and pass navigation into lib pages.
  6. Test: all pages work in the app; API, state, and UI unchanged.

Success criteria

  • All admin pages (including each “tab” as its own page) exportable from @raystack/frontier/admin
  • Pages are router-agnostic (props + callbacks only)
  • Tabbed UX is implemented in the app; lib exports only individual pages
  • All functionality preserved
  • web/apps/admin consumes lib via thin router wrappers

Metadata

Metadata

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions