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/admin as @raystack/frontier/admin
- Make pages router-agnostic (props + navigation callbacks, no
react-router-dom in 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 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.
-
Export from lib
Barrel exports per feature (e.g. pages/products/exports.ts) and re-export from lib/admin/index.ts.
-
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`)}
/>
);
}
-
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
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
- 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) and web/lib/tsup.config.ts (admin entry).
- In
web/apps/admin: implement tab layouts and routes; add WithRouter wrappers that import from @raystack/frontier/admin and 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/admin consumes lib via thin router wrappers
Export Admin Pages as components
Problem Statement
Admin UI pages in
apps/admin/src/containers/andapps/admin/src/pages/are tightly coupled, making it hard to add external pages. Extract them as reusable, router-agnostic components exportable from@raystack/frontier/adminso external apps can consume them and add their own pages. Align with SDK refactor where applicable.Goals
web/lib/adminas@raystack/frontier/adminreact-router-domin lib)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.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.Tabs: individual pages only; tab UI in app
OrganizationMembersPage,OrganizationProjectsPage). No tabbed wrapper component in the lib./organizations/:id/members,/organizations/:id/projects). Each route renders the corresponding lib page.Implementation approach
Move UI into lib
Move page code from
apps/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 uses
useParams/useNavigateand passes them into the lib page as props/callbacks.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
Add
adminexport 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
web/lib/admin/(pages, components, utils, contexts, index).web/lib/package.json(admin export) andweb/lib/tsup.config.ts(admin entry).web/apps/admin: implement tab layouts and routes; add WithRouter wrappers that import from@raystack/frontier/adminand pass navigation into lib pages.Success criteria
@raystack/frontier/adminweb/apps/adminconsumes lib via thin router wrappers