-
Notifications
You must be signed in to change notification settings - Fork 42
Description
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
- Export admin pages from
web/lib/adminas@raystack/frontier/admin - Make pages router-agnostic (props + navigation callbacks, no
react-router-domin lib) - 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
- Preserve all functionality (API, state, UI)
- Keep app shell (top bar, sidebar, routing) separate from exported page content
- 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
-
Move UI into lib
Move page code fromapps/admin/src/containers/<page>/orpages/toweb/lib/admin/pages/<page>/(prefergit mv). RemoveuseNavigate,useParams,NavLink; use props and callbacks instead. Use lib components (PageTitle,PageHeader,SheetHeader) and lib utils. -
Export from lib
Barrel exports per feature (e.g.pages/products/exports.ts) and re-export fromlib/admin/index.ts. -
Thin router wrappers in the app
For each lib page, a small wrapper in the app that usesuseParams/useNavigateand 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`)}
/>
);
}-
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. -
Package and build
Addadminexport inweb/lib/package.json; add admin entry inweb/lib/tsup.config.ts. App depends on@raystack/frontier/adminand 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
- Create/ensure export structure in
web/lib/admin/(pages, components, utils, contexts, index). - Move pages from app to lib (git mv where possible); strip router deps; add props/callbacks.
- For org/user “details” with tabs: export one page per tab; do not add a tabbed wrapper in the lib.
- Update
web/lib/package.json(admin export) andweb/lib/tsup.config.ts(admin entry). - In
web/apps/admin: implement tab layouts and routes; add WithRouter wrappers that import from@raystack/frontier/adminand pass navigation into lib pages. - 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/adminconsumes lib via thin router wrappers