frontend phase 15
This commit is contained in:
@@ -2,26 +2,39 @@
|
||||
import { FunctionComponent, PropsWithChildren, useMemo } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { ROUTES } from '@/constants';
|
||||
import { useAdminCapabilities } from '@/hooks';
|
||||
import { LinkToPage } from '@/utils';
|
||||
import TopBarAndSideBarLayout from './TopBarAndSideBarLayout';
|
||||
|
||||
/**
|
||||
* Admin / backoffice shell — desktop-oriented ops console (f15). Uses the shared
|
||||
* TopBar + SideBar engine with a persistent sidebar on desktop.
|
||||
* Admin / backoffice shell — the desktop ops console (f15). The sidebar is **role-gated**: each console
|
||||
* appears only when the current admin role can act on it (`useAdminCapabilities`). This is a display
|
||||
* convenience — the server enforces every command's scope — so a `support` admin never sees the payout or
|
||||
* refund controls, a `moderation` admin only sees moderation, etc. (phase §3 "Routing & RBAC", §5).
|
||||
* @layout AdminLayout
|
||||
*/
|
||||
const AdminLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
const t = useTranslations('nav');
|
||||
const tShell = useTranslations('shell');
|
||||
const caps = useAdminCapabilities();
|
||||
|
||||
const sidebarItems: Array<LinkToPage> = useMemo(
|
||||
() => [
|
||||
{ title: t('overview'), path: ROUTES.ADMIN, icon: 'admin' },
|
||||
{ title: t('users'), path: ROUTES.ADMIN_USERS, icon: 'users' },
|
||||
{ title: t('notifications'), path: ROUTES.ADMIN_NOTIFICATIONS, icon: 'notifications' },
|
||||
],
|
||||
[t]
|
||||
);
|
||||
const sidebarItems: Array<LinkToPage> = useMemo(() => {
|
||||
const items: Array<LinkToPage & { show: boolean }> = [
|
||||
{ title: t('overview'), path: ROUTES.ADMIN, icon: 'dashboard', show: true },
|
||||
{ title: t('verification'), path: ROUTES.ADMIN_VERIFICATION, icon: 'verification', show: caps.canVerify },
|
||||
{ title: t('tickets'), path: ROUTES.ADMIN_TICKETS, icon: 'support', show: caps.canManageTickets },
|
||||
{ title: t('payouts'), path: ROUTES.ADMIN_PAYOUTS, icon: 'earnings', show: caps.canPayout },
|
||||
{ title: t('reviews'), path: ROUTES.ADMIN_REVIEWS, icon: 'moderation', show: caps.canModerate },
|
||||
{ title: t('config'), path: ROUTES.ADMIN_CONFIG, icon: 'config', show: caps.canConfig },
|
||||
{ title: t('holidays'), path: ROUTES.ADMIN_HOLIDAYS, icon: 'calendar', show: caps.canConfig },
|
||||
{ title: t('alerts'), path: ROUTES.ADMIN_ALERTS, icon: 'alerts', show: caps.canManageAlerts },
|
||||
{ title: t('audit'), path: ROUTES.ADMIN_AUDIT, icon: 'audit', show: caps.canViewAudit },
|
||||
{ title: t('partners'), path: ROUTES.ADMIN_PARTNERS, icon: 'partners', show: caps.canManagePartners },
|
||||
{ title: t('roles'), path: ROUTES.ADMIN_ROLES, icon: 'roles', show: caps.canManageRoles },
|
||||
{ title: t('notifications'), path: ROUTES.ADMIN_NOTIFICATIONS, icon: 'notifications', show: true },
|
||||
];
|
||||
return items.filter((i) => i.show).map(({ show: _show, ...rest }) => rest);
|
||||
}, [t, caps]);
|
||||
|
||||
return (
|
||||
<TopBarAndSideBarLayout
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client';
|
||||
import { FunctionComponent, PropsWithChildren, useMemo } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { ROUTES } from '@/constants';
|
||||
import { LinkToPage } from '@/utils';
|
||||
import TopBarAndSideBarLayout from './TopBarAndSideBarLayout';
|
||||
|
||||
/**
|
||||
* Partner-center portal shell (f15) — a **separate authz scope** from the Balinyaar admin console. A
|
||||
* center admin is not a Balinyaar admin; they see only their own center's data (server-enforced tenancy).
|
||||
* Same desktop sidebar engine as the admin shell, its own nav. The settlement tab always appears; the
|
||||
* settlement page itself renders the "via Balinyaar" state for a non-merchant-of-record center.
|
||||
* @layout PartnerLayout
|
||||
*/
|
||||
const PartnerLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
const t = useTranslations('nav');
|
||||
const tShell = useTranslations('shell');
|
||||
|
||||
const sidebarItems: Array<LinkToPage> = useMemo(
|
||||
() => [
|
||||
{ title: t('partner_home'), path: ROUTES.PARTNER, icon: 'partners' },
|
||||
{ title: t('partner_nurses'), path: ROUTES.PARTNER_NURSES, icon: 'patients' },
|
||||
{ title: t('partner_bookings'), path: ROUTES.PARTNER_BOOKINGS, icon: 'bookings' },
|
||||
{ title: t('partner_settlement'), path: ROUTES.PARTNER_SETTLEMENT, icon: 'earnings' },
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
return (
|
||||
<TopBarAndSideBarLayout
|
||||
sidebarItems={sidebarItems}
|
||||
title={tShell('partner_console')}
|
||||
variant="sidebarPersistentOnDesktop"
|
||||
>
|
||||
{children}
|
||||
</TopBarAndSideBarLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default PartnerLayout;
|
||||
@@ -3,5 +3,6 @@ import PublicLayout from './PublicLayout';
|
||||
import CustomerLayout from './CustomerLayout';
|
||||
import NurseLayout from './NurseLayout';
|
||||
import AdminLayout from './AdminLayout';
|
||||
import PartnerLayout from './PartnerLayout';
|
||||
|
||||
export { PublicLayout, PrivateLayout, CustomerLayout, NurseLayout, AdminLayout };
|
||||
export { PublicLayout, PrivateLayout, CustomerLayout, NurseLayout, AdminLayout, PartnerLayout };
|
||||
|
||||
Reference in New Issue
Block a user