Skip to content

Commit c0ec19b

Browse files
committed
feat: add maintenance mode
1 parent f684091 commit c0ec19b

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

src/pages/index.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { lazy } from '@loadable/component';
44
import { useMaybeProject } from 'src/shared/contexts/ProjectContext';
55
import { useUserQuery } from 'src/entities/user/queries';
66
import { useProjectsQuery } from 'src/shared/queries/projects';
7+
import { isMaintenanceActive } from 'src/shared/config/maintenance';
8+
import { MaintenancePage } from 'src/shared/ui/MaintenancePage';
79
import AppInitialization from 'src/processes/AppInitialization';
810
import { Layout } from './layouts';
911
import { LayoutSolid } from 'src/pages/layouts/LayoutSolid';
@@ -74,6 +76,15 @@ const Routing: FC = () => {
7476
);
7577
}
7678

79+
// Maintenance mode — show maintenance page for authenticated users
80+
if (isMaintenanceActive()) {
81+
return (
82+
<AppInitialization isLoading={isAppLoading}>
83+
<MaintenancePage />
84+
</AppInitialization>
85+
);
86+
}
87+
7788
// Authenticated user but no project selected - show create project page
7889
if (!project) {
7990
const currentPath = location.pathname + location.search;

src/shared/config/maintenance.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
interface MaintenanceConfig {
2+
enabled: boolean;
3+
/** ISO 8601 date string for estimated recovery, e.g. "2026-02-14T10:00:00Z" */
4+
estimatedEndTime?: string;
5+
/** Custom message to display instead of the default */
6+
message?: string;
7+
}
8+
9+
const BYPASS_KEY = 'bypass_maintenance';
10+
11+
export const maintenanceConfig: MaintenanceConfig = {
12+
enabled: true,
13+
estimatedEndTime: "2026-02-16T08:30:00Z",
14+
message: "We are performing scheduled maintenance to improve our services."
15+
};
16+
17+
export function isMaintenanceActive(): boolean {
18+
return maintenanceConfig.enabled && localStorage.getItem(BYPASS_KEY) !== '1';
19+
}

src/shared/ui/MaintenancePage.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { FC, useMemo } from 'react';
2+
import { Box, Button, Flex, Text } from '@chakra-ui/react';
3+
import { TonConsoleIcon } from 'src/shared/ui/icons/TonConsoleIcon';
4+
import { H4 } from 'src/shared/ui/typography';
5+
import { maintenanceConfig } from 'src/shared/config/maintenance';
6+
import { useLogoutMutation } from 'src/entities/user/queries';
7+
8+
function formatEndTime(isoString: string): string {
9+
const date = new Date(isoString);
10+
11+
const local = date.toLocaleString(undefined, {
12+
month: 'short',
13+
day: 'numeric',
14+
hour: '2-digit',
15+
minute: '2-digit'
16+
});
17+
18+
const utc = date.toLocaleString('en-GB', {
19+
month: 'short',
20+
day: 'numeric',
21+
hour: '2-digit',
22+
minute: '2-digit',
23+
timeZone: 'UTC'
24+
});
25+
26+
return `${local} (${utc} UTC)`;
27+
}
28+
29+
export const MaintenancePage: FC = () => {
30+
const { mutate: logout, isPending: isLoggingOut } = useLogoutMutation();
31+
32+
const estimatedEnd = useMemo(() => {
33+
const { estimatedEndTime } = maintenanceConfig;
34+
if (!estimatedEndTime) {
35+
return null;
36+
}
37+
return formatEndTime(estimatedEndTime);
38+
}, []);
39+
40+
return (
41+
<Flex
42+
align="center"
43+
justify="center"
44+
direction="column"
45+
h="100vh"
46+
px={4}
47+
bgColor="background.page"
48+
>
49+
<Flex align="center" gap={2} mb={10}>
50+
<TonConsoleIcon w="40px" h="40px" />
51+
<H4>Ton Console</H4>
52+
</Flex>
53+
54+
<Box textStyle="h2" mb={4} color="text.primary">
55+
Under Maintenance
56+
</Box>
57+
58+
<Text sx={{ textWrap: 'balance' }} textStyle="body1" maxW="480px" mb={2} color="text.secondary" textAlign="center">
59+
{maintenanceConfig.message ??
60+
'We are performing scheduled maintenance. The service will be back shortly.'}
61+
</Text>
62+
63+
{estimatedEnd && (
64+
<Text textStyle="body2" mb={8} color="text.tertiary">
65+
Expected back by {estimatedEnd}
66+
</Text>
67+
)}
68+
69+
{!estimatedEnd && <Box mb={8} />}
70+
71+
<Button
72+
isLoading={isLoggingOut}
73+
onClick={() => logout()}
74+
size="sm"
75+
variant="outline"
76+
>
77+
Log out
78+
</Button>
79+
</Flex>
80+
);
81+
};

0 commit comments

Comments
 (0)