refactor: consolidate individual system pages into a single reusable StatusPage component
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import { lazy, Suspense, useEffect } from 'react';
|
import { lazy, Suspense, useEffect } from 'react';
|
||||||
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
|
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
|
||||||
import { ThemeProvider } from '@repo/ui/provider';
|
import { ThemeProvider } from '@repo/ui/provider';
|
||||||
import { NotFound, Forbidden, Maintenance, ComingSoon, AgGridProvider } from '@repo/ui/components';
|
import { StatusPage, AgGridProvider } from '@repo/ui/components';
|
||||||
|
import { useTranslation } from '@repo/core-i18n';
|
||||||
import { LoadingScreen } from '../core/components/loading-screen';
|
import { LoadingScreen } from '../core/components/loading-screen';
|
||||||
import { useThemeStore } from '../core/stores/theme.store';
|
import { useThemeStore } from '../core/stores/theme.store';
|
||||||
import { initializeAndPurgeHistoryBackground } from './main/layouts/hooks/useHistoryTracker';
|
import { initializeAndPurgeHistoryBackground } from './main/layouts/hooks/useHistoryTracker';
|
||||||
@@ -9,13 +10,67 @@ import { initializeAndPurgeHistoryBackground } from './main/layouts/hooks/useHis
|
|||||||
const AuthModule = lazy(() => import('./auth'));
|
const AuthModule = lazy(() => import('./auth'));
|
||||||
const AppModule = lazy(() => import('./main'));
|
const AppModule = lazy(() => import('./main'));
|
||||||
|
|
||||||
|
function NotFoundPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<StatusPage
|
||||||
|
title={t('common:systemPages.notFound.title')}
|
||||||
|
heading={t('common:systemPages.notFound.heading')}
|
||||||
|
description={t('common:systemPages.notFound.description')}
|
||||||
|
showActionsBack
|
||||||
|
showActionsHome
|
||||||
|
homeUrl="/app"
|
||||||
|
backButtonLabel={t('common:systemPages.actions.goBack')}
|
||||||
|
homeButtonLabel={t('common:systemPages.actions.backToHome')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ForbiddenPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<StatusPage
|
||||||
|
title={t('common:systemPages.forbidden.title')}
|
||||||
|
heading={t('common:systemPages.forbidden.heading')}
|
||||||
|
description={t('common:systemPages.forbidden.description')}
|
||||||
|
showActionsBack
|
||||||
|
showActionsHome
|
||||||
|
homeUrl="/app"
|
||||||
|
backButtonLabel={t('common:systemPages.actions.goBack')}
|
||||||
|
homeButtonLabel={t('common:systemPages.actions.backToHome')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MaintenancePage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<StatusPage
|
||||||
|
title={t('common:systemPages.maintenance.title')}
|
||||||
|
heading={t('common:systemPages.maintenance.heading')}
|
||||||
|
description={t('common:systemPages.maintenance.description')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ComingSoonPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<StatusPage
|
||||||
|
title={t('common:systemPages.comingSoon.title')}
|
||||||
|
heading={t('common:systemPages.comingSoon.heading')}
|
||||||
|
description={t('common:systemPages.comingSoon.description')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{ path: '/auth/*', element: <AuthModule /> },
|
{ path: '/auth/*', element: <AuthModule /> },
|
||||||
{ path: '/app/*', element: <AppModule /> },
|
{ path: '/app/*', element: <AppModule /> },
|
||||||
{ path: '/404', element: <NotFound homeUrl="/app" /> },
|
{ path: '/404', element: <NotFoundPage /> },
|
||||||
{ path: '/403', element: <Forbidden homeUrl="/app" /> },
|
{ path: '/403', element: <ForbiddenPage /> },
|
||||||
{ path: '/maintenance', element: <Maintenance /> },
|
{ path: '/maintenance', element: <MaintenancePage /> },
|
||||||
{ path: '/coming-soon', element: <ComingSoon /> },
|
{ path: '/coming-soon', element: <ComingSoonPage /> },
|
||||||
{ path: '/', element: <Navigate to="/app" /> },
|
{ path: '/', element: <Navigate to="/app" /> },
|
||||||
{ path: '*', element: <Navigate to="/404" /> },
|
{ path: '*', element: <Navigate to="/404" /> },
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -195,6 +195,32 @@
|
|||||||
"endDate": "End Date",
|
"endDate": "End Date",
|
||||||
"attachment": "Attachment",
|
"attachment": "Attachment",
|
||||||
"reference": "Reference"
|
"reference": "Reference"
|
||||||
|
},
|
||||||
|
"systemPages": {
|
||||||
|
"comingSoon": {
|
||||||
|
"title": "Coming Soon",
|
||||||
|
"heading": "Feature in Progress",
|
||||||
|
"description": "This feature is currently under development. Stay tuned! We'll have it ready for you very soon."
|
||||||
|
},
|
||||||
|
"forbidden": {
|
||||||
|
"title": "403 Error",
|
||||||
|
"heading": "Access Denied",
|
||||||
|
"description": "Sorry, you don’t have permission to access this page. Please contact your administrator if you believe this is a mistake."
|
||||||
|
},
|
||||||
|
"maintenance": {
|
||||||
|
"title": "Maintenance",
|
||||||
|
"heading": "We'll Be Back Soon",
|
||||||
|
"description": "Our system is currently undergoing scheduled maintenance. We are working hard to bring it back online as soon as possible."
|
||||||
|
},
|
||||||
|
"notFound": {
|
||||||
|
"title": "404 Error",
|
||||||
|
"heading": "Page Not Found",
|
||||||
|
"description": "Sorry, the page you’re looking for doesn’t exist or has been moved."
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"backToHome": "Back to Home",
|
||||||
|
"goBack": "Go Back"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,6 +195,32 @@
|
|||||||
"endDate": "Tanggal Selesai",
|
"endDate": "Tanggal Selesai",
|
||||||
"attachment": "Lampiran",
|
"attachment": "Lampiran",
|
||||||
"reference": "Referensi"
|
"reference": "Referensi"
|
||||||
|
},
|
||||||
|
"systemPages": {
|
||||||
|
"comingSoon": {
|
||||||
|
"title": "Segera Hadir",
|
||||||
|
"heading": "Fitur dalam Pengembangan",
|
||||||
|
"description": "Fitur ini sedang dalam tahap pengembangan. Tetap ikuti pembaruannya! Kami akan segera menyiapkannya untuk Anda."
|
||||||
|
},
|
||||||
|
"forbidden": {
|
||||||
|
"title": "Error 403",
|
||||||
|
"heading": "Akses Ditolak",
|
||||||
|
"description": "Maaf, Anda tidak memiliki izin untuk mengakses halaman ini. Silakan hubungi administrator jika Anda merasa ini adalah kesalahan."
|
||||||
|
},
|
||||||
|
"maintenance": {
|
||||||
|
"title": "Pemeliharaan",
|
||||||
|
"heading": "Kami Akan Segera Kembali",
|
||||||
|
"description": "Sistem kami saat ini sedang menjalani pemeliharaan terjadwal. Kami bekerja keras untuk menghadirkannya kembali sesegera mungkin."
|
||||||
|
},
|
||||||
|
"notFound": {
|
||||||
|
"title": "Error 404",
|
||||||
|
"heading": "Halaman Tidak Ditemukan",
|
||||||
|
"description": "Maaf, halaman yang Anda cari tidak ada atau telah dipindahkan."
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"backToHome": "Kembali ke Beranda",
|
||||||
|
"goBack": "Kembali"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,7 @@ export * from '@mantine/core';
|
|||||||
export { List, TypographyStylesProvider } from '@mantine/core';
|
export { List, TypographyStylesProvider } from '@mantine/core';
|
||||||
|
|
||||||
export * from './Form';
|
export * from './Form';
|
||||||
export * from './system-pages/coming-soon';
|
export * from './system-pages/status-page';
|
||||||
export * from './system-pages/forbidden';
|
|
||||||
export * from './system-pages/maintenance';
|
|
||||||
export * from './system-pages/not-found';
|
|
||||||
export * from './core-app-shell';
|
export * from './core-app-shell';
|
||||||
export * from './actions-tools';
|
export * from './actions-tools';
|
||||||
export * from './status-badge';
|
export * from './status-badge';
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
interface ComingSoonProps {
|
|
||||||
showActions?: boolean;
|
|
||||||
height?: StyleProp<React.CSSProperties['height']>;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { Title, Text, Button, Container, Stack, Center, StyleProp } from '@mantine/core';
|
|
||||||
|
|
||||||
export function ComingSoon({ showActions = false, height = '100vh' }: ComingSoonProps) {
|
|
||||||
return (
|
|
||||||
<Container size="sm" h={height} pos="relative">
|
|
||||||
<Center h="100%">
|
|
||||||
<Stack align="center" ta="center" gap="md">
|
|
||||||
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
|
|
||||||
Coming Soon
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Title order={1} fw={900} c="brand">
|
|
||||||
Feature in Progress
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Text size="md" c="dimmed">
|
|
||||||
This feature is currently under development. Stay tuned! We'll have it ready for you very soon.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{showActions && (
|
|
||||||
<Button component="a" href="/" color="brand" size="sm" mt="xl">
|
|
||||||
Back to Home
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Center>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
interface ForbiddenProps {
|
|
||||||
showActionsBack?: boolean;
|
|
||||||
showActionsHome?: boolean;
|
|
||||||
onClickGoBack?(): void;
|
|
||||||
onClickBackToHome?(): void;
|
|
||||||
homeUrl?: string;
|
|
||||||
height?: StyleProp<React.CSSProperties['height']>;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { Title, Text, Button, Container, Stack, Group, Center, StyleProp } from '@mantine/core';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
export function Forbidden({
|
|
||||||
showActionsBack = true,
|
|
||||||
showActionsHome = true,
|
|
||||||
onClickGoBack,
|
|
||||||
onClickBackToHome,
|
|
||||||
homeUrl,
|
|
||||||
height = '100vh',
|
|
||||||
}: ForbiddenProps) {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
function onBack() {
|
|
||||||
if (onClickGoBack) onClickGoBack();
|
|
||||||
else navigate(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBackHome() {
|
|
||||||
if (onClickBackToHome) onClickBackToHome();
|
|
||||||
else if (homeUrl) navigate(homeUrl, { replace: true });
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Container size="sm" h={height} pos="relative">
|
|
||||||
<Center h="100%">
|
|
||||||
<Stack align="center" ta="center" gap="md">
|
|
||||||
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
|
|
||||||
403 Error
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Title order={1} fw={900} c="brand">
|
|
||||||
Access Denied
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Text size="md" c="dimmed">
|
|
||||||
Sorry, you don’t have permission to access this page. Please contact your administrator if you believe this
|
|
||||||
is a mistake.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{(showActionsBack || showActionsHome) && (
|
|
||||||
<Group mt="xl" justify="center">
|
|
||||||
{showActionsBack && (
|
|
||||||
<Button variant="default" size="sm" onClick={onBack}>
|
|
||||||
Go Back
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showActionsHome && (
|
|
||||||
<Button color="brand" size="sm" onClick={onBackHome}>
|
|
||||||
Back to Home
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Center>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
interface MaintenanceProps {
|
|
||||||
showActions?: boolean;
|
|
||||||
height?: StyleProp<React.CSSProperties['height']>;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { Title, Text, Button, Container, Stack, Center, StyleProp } from '@mantine/core';
|
|
||||||
|
|
||||||
export function Maintenance({ showActions = false, height = '100vh' }: MaintenanceProps) {
|
|
||||||
return (
|
|
||||||
<Container size="sm" h={height} pos="relative">
|
|
||||||
<Center h="100%">
|
|
||||||
<Stack align="center" ta="center" gap="md">
|
|
||||||
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
|
|
||||||
Maintenance
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Title order={1} fw={900} c="brand">
|
|
||||||
We'll Be Back Soon
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Text size="md" c="dimmed">
|
|
||||||
Our system is currently undergoing scheduled maintenance. We are working hard to bring it back online as
|
|
||||||
soon as possible.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{showActions && (
|
|
||||||
<Button component="a" href="/" color="brand" size="sm" mt="xl">
|
|
||||||
Back to Home
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Center>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+41
-18
@@ -1,23 +1,38 @@
|
|||||||
interface NotFoundProps {
|
|
||||||
showActionsBack?: boolean;
|
|
||||||
showActionsHome?: boolean;
|
|
||||||
onClickGoBack?(): void;
|
|
||||||
onClickBackToHome?(): void;
|
|
||||||
homeUrl?: string;
|
|
||||||
height?: StyleProp<React.CSSProperties['height']>;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { Title, Text, Button, Container, Stack, Group, Center, StyleProp } from '@mantine/core';
|
import { Title, Text, Button, Container, Stack, Group, Center, StyleProp } from '@mantine/core';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
export function NotFound({
|
export interface StatusPageProps {
|
||||||
showActionsBack = true,
|
title?: string;
|
||||||
showActionsHome = true,
|
heading?: string;
|
||||||
|
description?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
|
||||||
|
showActionsBack?: boolean;
|
||||||
|
backButtonLabel?: string;
|
||||||
|
onClickGoBack?(): void;
|
||||||
|
|
||||||
|
showActionsHome?: boolean;
|
||||||
|
homeButtonLabel?: string;
|
||||||
|
onClickBackToHome?(): void;
|
||||||
|
homeUrl?: string;
|
||||||
|
|
||||||
|
height?: StyleProp<React.CSSProperties['height']>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatusPage({
|
||||||
|
title,
|
||||||
|
heading,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
showActionsBack = false,
|
||||||
|
backButtonLabel,
|
||||||
onClickGoBack,
|
onClickGoBack,
|
||||||
|
showActionsHome = false,
|
||||||
|
homeButtonLabel,
|
||||||
onClickBackToHome,
|
onClickBackToHome,
|
||||||
homeUrl,
|
homeUrl,
|
||||||
height = '100vh',
|
height = '100vh',
|
||||||
}: NotFoundProps) {
|
}: StatusPageProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
function onBack() {
|
function onBack() {
|
||||||
@@ -34,29 +49,37 @@ export function NotFound({
|
|||||||
<Container size="sm" h={height} pos="relative">
|
<Container size="sm" h={height} pos="relative">
|
||||||
<Center h="100%">
|
<Center h="100%">
|
||||||
<Stack align="center" ta="center" gap="md">
|
<Stack align="center" ta="center" gap="md">
|
||||||
|
{icon && icon}
|
||||||
|
|
||||||
|
{title && (
|
||||||
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
|
<Text size="sm" fw={500} tt="uppercase" c="dimmed" lts="var(--mantine-spacing-xs)">
|
||||||
404 Error
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{heading && (
|
||||||
<Title order={1} fw={900} c="brand">
|
<Title order={1} fw={900} c="brand">
|
||||||
Page Not Found
|
{heading}
|
||||||
</Title>
|
</Title>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{description && (
|
||||||
<Text size="md" c="dimmed">
|
<Text size="md" c="dimmed">
|
||||||
Sorry, the page you’re looking for doesn’t exist or has been moved.
|
{description}
|
||||||
</Text>
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
{(showActionsBack || showActionsHome) && (
|
{(showActionsBack || showActionsHome) && (
|
||||||
<Group mt="xl" justify="center">
|
<Group mt="xl" justify="center">
|
||||||
{showActionsBack && (
|
{showActionsBack && (
|
||||||
<Button variant="default" size="sm" onClick={onBack}>
|
<Button variant="default" size="sm" onClick={onBack}>
|
||||||
Go Back
|
{backButtonLabel || 'Go Back'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showActionsHome && (
|
{showActionsHome && (
|
||||||
<Button color="brand" size="sm" onClick={onBackHome}>
|
<Button color="brand" size="sm" onClick={onBackHome}>
|
||||||
Back to Home
|
{homeButtonLabel || 'Back to Home'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
EnterpriseModalContext,
|
EnterpriseModalContext,
|
||||||
EnterpriseTranslationContext,
|
EnterpriseTranslationContext,
|
||||||
} from '../hooks/use-module.context';
|
} from '../hooks/use-module.context';
|
||||||
import { Forbidden } from '../../../components';
|
import { StatusPage } from '../../../components';
|
||||||
import { useEnterpriseStorage } from '../hooks/enterprise-storage.context';
|
import { useEnterpriseStorage } from '../hooks/enterprise-storage.context';
|
||||||
import { defaultPrivileges, noPrivileges } from '../constant';
|
import { defaultPrivileges, noPrivileges } from '../constant';
|
||||||
|
|
||||||
@@ -176,7 +176,19 @@ export function EnterpriseModuleProvider<
|
|||||||
|
|
||||||
const { ALLOW_VIEW } = configSlice.privileges ?? {};
|
const { ALLOW_VIEW } = configSlice.privileges ?? {};
|
||||||
|
|
||||||
if (!ALLOW_VIEW) return <Forbidden homeUrl="/app" height={500} />;
|
if (!ALLOW_VIEW) {
|
||||||
|
return (
|
||||||
|
<StatusPage
|
||||||
|
title={t('common:systemPages.forbidden.title')}
|
||||||
|
heading={t('common:systemPages.forbidden.heading')}
|
||||||
|
description={t('common:systemPages.forbidden.description')}
|
||||||
|
showActionsHome
|
||||||
|
homeUrl="/app"
|
||||||
|
homeButtonLabel={t('common:systemPages.actions.backToHome')}
|
||||||
|
height={500}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnterpriseConfigContext.Provider value={configSlice}>
|
<EnterpriseConfigContext.Provider value={configSlice}>
|
||||||
|
|||||||
Reference in New Issue
Block a user