diff --git a/assets/pasted-20260407-150901-3e5110b3.png b/assets/pasted-20260407-150901-3e5110b3.png new file mode 100644 index 0000000..d8475d0 Binary files /dev/null and b/assets/pasted-20260407-150901-3e5110b3.png differ diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index b8c2ff0..8b3ed57 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -135,6 +135,11 @@ const menuAside: MenuAsideItem[] = [ icon: 'mdiFolderMultipleImage' in icon ? icon['mdiFolderMultipleImage' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_ASSETS' }, + { + href: '/mai-server', + label: 'MAi Server', + icon: icon.mdiServer, + }, { href: '/profile', label: 'Profile', diff --git a/frontend/src/pages/mai-server.tsx b/frontend/src/pages/mai-server.tsx new file mode 100644 index 0000000..c2d6ec9 --- /dev/null +++ b/frontend/src/pages/mai-server.tsx @@ -0,0 +1,336 @@ +import * as icon from '@mdi/js'; +import Head from 'next/head'; +import React from 'react'; +import axios from 'axios'; +import type { ReactElement } from 'react'; +import BaseButton from '../components/BaseButton'; +import BaseIcon from '../components/BaseIcon'; +import CardBox from '../components/CardBox'; +import FormField from '../components/FormField'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../config'; +import { hasPermission } from '../helpers/userPermissions'; +import LayoutAuthenticated from '../layouts/Authenticated'; +import { useAppSelector } from '../stores/hooks'; + +type MetricState = Record<'profiles' | 'conversations' | 'messages' | 'announcements', string>; + +type MetricConfig = { + key: keyof MetricState; + title: string; + detail: string; + endpoint: string; + permission: string; + iconPath: string; +}; + +const moduleLabels = [ + 'Ai', + 'AS Ai', + 'MAI', + 'MAI SERVER ALIYO MOMOT', + 'D MOTHERBOARD', + 'D RAM', + 'D ROM', + 'D WLAN', + 'ALIYO MOMOT BANK ALIYO MOMOT', +]; + +const readinessItems = [ + { label: 'Register', description: 'Account onboarding is available through the existing sign up flow.' }, + { label: 'Authentification', description: 'Session access can be enabled directly from this control page.' }, + { label: 'Profile', description: 'Profile data stays synced with the authenticated application user.' }, + { label: 'Password', description: 'Password reset and recovery reuse the built-in auth endpoints.' }, + { label: 'Country', description: 'Regional profile details can be completed from the profile screen.' }, +]; + +const adminItems = [ + { label: 'Admin', description: 'Administrator-level visibility is preserved through the role model.' }, + { label: 'Profile database', description: 'Profile records are available through the existing user management module.' }, + { label: 'iSecurity', description: 'Email verification and JWT session protection stay active for secure access.' }, + { label: 'iManager', description: 'Operational shortcuts stay routed through dashboard, profile, and entity pages.' }, + { label: 'iAuthentifier', description: 'Authentication status is surfaced clearly for the current session.' }, +]; + +const metricConfigs: MetricConfig[] = [ + { + key: 'profiles', + title: 'Profiles', + detail: 'Registered profile records', + endpoint: '/users/count', + permission: 'READ_USERS', + iconPath: icon.mdiAccountGroup, + }, + { + key: 'conversations', + title: 'Marketing conversations', + detail: 'Lead and outreach threads', + endpoint: '/conversations/count', + permission: 'READ_CONVERSATIONS', + iconPath: icon.mdiChartTimelineVariant, + }, + { + key: 'messages', + title: 'Message flow', + detail: 'Communication volume', + endpoint: '/messages/count', + permission: 'READ_MESSAGES', + iconPath: icon.mdiMessageTextOutline, + }, + { + key: 'announcements', + title: 'Marketing broadcasts', + detail: 'Announcement and campaign records', + endpoint: '/announcements/count', + permission: 'READ_ANNOUNCEMENTS', + iconPath: icon.mdiBullhornOutline, + }, +]; + +const MaiServerPage = () => { + const { currentUser } = useAppSelector((state) => state.auth); + const iconsColor = useAppSelector((state) => state.style.iconsColor); + const corners = useAppSelector((state) => state.style.corners); + const cardsStyle = useAppSelector((state) => state.style.cardsStyle); + + const [isAuthenticationEnabled, setIsAuthenticationEnabled] = React.useState(false); + const [metrics, setMetrics] = React.useState({ + profiles: '—', + conversations: '—', + messages: '—', + announcements: '—', + }); + + React.useEffect(() => { + if (!currentUser) return; + + const loadMetrics = async () => { + const requests = metricConfigs.map(async (metric) => { + if (!hasPermission(currentUser, metric.permission)) { + return { key: metric.key, value: 'Private' }; + } + + try { + const response = await axios.get(metric.endpoint); + return { key: metric.key, value: String(response.data?.count ?? 0) }; + } catch (error) { + console.error(`Failed to load ${metric.key} metric`, error); + return { key: metric.key, value: 'Unavailable' }; + } + }); + + const results = await Promise.all(requests); + + setMetrics((previous) => { + const next = { ...previous }; + + results.forEach((result) => { + next[result.key] = result.value; + }); + + return next; + }); + }; + + loadMetrics(); + }, [currentUser]); + + const fullName = [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' '); + const displayName = fullName || currentUser?.email || 'Authenticated user'; + const emailAddress = currentUser?.email || 'No email connected'; + const provider = currentUser?.provider || 'local'; + const roleName = currentUser?.app_role?.name || 'Workspace member'; + const countryValueRaw = currentUser?.country || currentUser?.organization?.country; + const countryName = typeof countryValueRaw === 'string' && countryValueRaw ? countryValueRaw : 'Not configured'; + const emailVerificationStatus = currentUser?.emailVerified ? 'Verified' : 'Pending'; + const authenticationStatus = isAuthenticationEnabled ? 'Enabled for this session' : 'Standby'; + const authenticationTone = isAuthenticationEnabled ? 'text-green-600 dark:text-green-400' : 'text-amber-600 dark:text-amber-400'; + + return ( + <> + + {getPageTitle('Aliyo Momot MAi Server')} + + + + {''} + + + +
+
+

+ MAi server Aliyo Momot 1 and 2 · Server login +

+

+ Branded authentication, profiles, email access, and marketing visibility in one place. +

+

+ This separate in-app dashboard keeps the existing login flow intact while giving you a dedicated + Aliyo Momot MAi Server experience for server entry readiness, profile context, email visibility, and + high-level marketing statistics. +

+
+
+ setIsAuthenticationEnabled((previous) => !previous)} + /> + +
+
+ +
+ {moduleLabels.map((label) => ( +
+ {label} +
+ ))} +
+
+ +
+ {metricConfigs.map((metric) => ( + +
+
+

{metric.title}

+

{metrics[metric.key]}

+

{metric.detail}

+
+ +
+
+ ))} +
+ +
+ +
+ +
+

Register & profile

+

Profile, password, and regional setup blocks

+
+
+ +
+ {readinessItems.map((item) => ( +
+ +
+

{item.label}

+

{item.description}

+
+
+ ))} +
+ +
+

Current user

+

{displayName}

+

{roleName}

+

Country / region: {countryName}

+
+
+ + +
+ +
+

Admin profile database

+

Administrative controls inspired by the shared mockup

+
+
+ +
+ {adminItems.map((item) => ( +
+ +
+

{item.label}

+

{item.description}

+
+
+ ))} +
+ +
+
+

Server entry database

+

Protected

+

Access remains tied to your authenticated app session.

+
+
+

Email database

+

{emailVerificationStatus}

+

Primary email readiness for outreach and verification.

+
+
+
+ + +
+ +
+

Server login

+

Email and authentication details for the current operator

+
+
+ + + + + + + + + + + + + + +
+
+
+

Live status

+

{authenticationStatus}

+
+ +
+
+ +
+ setIsAuthenticationEnabled(true)} /> + + +
+
+
+
+ + ); +}; + +MaiServerPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default MaiServerPage;