From c242ea1b430a0bea2275d45986738cdcfceb4ee2 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Mon, 6 Jul 2026 23:30:51 +0530 Subject: [PATCH] fix: support platform admin environment login --- src/App.jsx | 76 ++++++++++++++++++++++++++++++++++++++++++++++++----- src/api.js | 5 ++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 9e70718..252af61 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,7 @@ import { adminApi, messageFrom } from './api.js' const tokenKey = 'metatron-org-admin-token' const organizationKey = 'metatron-org-admin-organization' +const sessionModeKey = 'metatron-admin-session-mode' function adminMemberships(user) { return (user?.memberships || []).filter((item) => item.status === 'APPROVED' && ['OWNER', 'ADMIN'].includes(item.role) && item.organization?.status === 'ACTIVE') @@ -16,36 +17,51 @@ function adminMemberships(user) { export default function App() { const [status, setStatus] = useState(() => sessionStorage.getItem(tokenKey) ? 'loading' : 'signedOut') const [user, setUser] = useState(null) + const [platformAdmin, setPlatformAdmin] = useState(null) const [error, setError] = useState('') const acceptSession = useCallback((result) => { const memberships = adminMemberships(result.user) if (!memberships.length) throw new Error('This account is not an administrator of an active organization.') sessionStorage.setItem(tokenKey, result.token || sessionStorage.getItem(tokenKey)) + sessionStorage.setItem(sessionModeKey, 'organization') const saved = sessionStorage.getItem(organizationKey) if (!memberships.some((item) => item.organization.id === saved)) sessionStorage.setItem(organizationKey, memberships[0].organization.id) setUser(result.user) setStatus('signedIn') }, []) + const acceptPlatformSession = useCallback((result) => { + sessionStorage.setItem(tokenKey, result.token || sessionStorage.getItem(tokenKey)) + sessionStorage.setItem(sessionModeKey, 'platform') + setPlatformAdmin(result.admin) + setStatus('platform') + }, []) + useEffect(() => { if (!sessionStorage.getItem(tokenKey)) return - adminApi.me().then((nextUser) => acceptSession({ user: nextUser })).catch((requestError) => { + const platform = sessionStorage.getItem(sessionModeKey) === 'platform' + const request = platform + ? adminApi.platformMe().then((admin) => acceptPlatformSession({ admin })) + : adminApi.me().then((nextUser) => acceptSession({ user: nextUser })) + request.catch((requestError) => { sessionStorage.removeItem(tokenKey) setError(messageFrom(requestError)) setStatus('signedOut') }) - }, [acceptSession]) + }, [acceptPlatformSession, acceptSession]) function logout() { sessionStorage.removeItem(tokenKey) sessionStorage.removeItem(organizationKey) + sessionStorage.removeItem(sessionModeKey) setUser(null) setStatus('signedOut') } if (status === 'loading') return - if (status === 'signedOut') return + if (status === 'signedOut') return + if (status === 'platform') return return } @@ -53,7 +69,7 @@ function LoadingScreen() { return
} -function AuthScreen({ onAuthenticated, initialError }) { +function AuthScreen({ onAuthenticated, onPlatformAuthenticated, initialError }) { const [mode, setMode] = useState('login') const [login, setLogin] = useState({ username: '', password: '' }) const [form, setForm] = useState({ organizationName: '', name: '', username: '', email: '', phone: '', password: '', age: '18', gender: 'Prefer not to say' }) @@ -62,8 +78,15 @@ function AuthScreen({ onAuthenticated, initialError }) { async function submitLogin(event) { event.preventDefault(); setBusy(true); setError('') - try { onAuthenticated(await adminApi.login(login.username.trim(), login.password)) } - catch (requestError) { setError(messageFrom(requestError)) } + try { + onAuthenticated(await adminApi.login(login.username.trim(), login.password)) + } catch (requestError) { + if (requestError?.response?.status !== 401) setError(messageFrom(requestError)) + else { + try { onPlatformAuthenticated(await adminApi.platformLogin(login.username.trim(), login.password)) } + catch (platformError) { setError(messageFrom(platformError)) } + } + } finally { setBusy(false) } } @@ -85,7 +108,7 @@ function AuthScreen({ onAuthenticated, initialError }) {
{mode === 'login' ?
-

Organization admin

Sign in with your global Metatron.Drive account.

+

Administrator sign in

Organization accounts and the backend super-admin credentials are both supported.

setLogin({ ...login, username: value })} /> setLogin({ ...login, password: value })} />
{error && } @@ -101,6 +124,45 @@ function AuthScreen({ onAuthenticated, initialError }) { } +function PlatformDashboard({ admin, onLogout }) { + const [stats, setStats] = useState(null) + const [users, setUsers] = useState([]) + const [pagination, setPagination] = useState({ page: 1, pages: 1, total: 0 }) + const [searchInput, setSearchInput] = useState('') + const [search, setSearch] = useState('') + const [filter, setFilter] = useState('all') + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + + const load = useCallback(async (page = 1) => { + setLoading(true); setError('') + try { + const [nextStats, result] = await Promise.all([ + adminApi.platformStats(), + adminApi.platformUsers({ page, limit: 25, search, status: filter }), + ]) + setStats(nextStats); setUsers(result.users); setPagination(result.pagination) + } catch (requestError) { setError(messageFrom(requestError)) } + finally { setLoading(false) } + }, [filter, search]) + + useEffect(() => { const timer = setTimeout(() => load(1), 0); return () => clearTimeout(timer) }, [load]) + + async function toggle(user) { + try { await adminApi.platformUpdateUser(user.id, { isSuspended: !user.isSuspended }); await load(pagination.page) } + catch (requestError) { setError(messageFrom(requestError)) } + } + + return
+ +

Platform administration

Global users and system totals

{[{ label: 'Users', value: stats?.users }, { label: 'Active', value: stats?.activeUsers }, { label: 'Suspended', value: stats?.suspendedUsers }, { label: 'Files', value: stats?.files }, { label: 'Folders', value: stats?.folders }].map((item) =>

{item.value ?? '—'}

{item.label}

)}
+

Global users

{pagination.total} registered accounts

{ event.preventDefault(); setSearch(searchInput.trim()) }}> setSearchInput(event.target.value)} />
+ {error ? load(pagination.page)} /> : loading ? :
{users.map((user) => )}
UserContactContentStatusAction

{user.name}

@{user.username}

{user.email}

{user.phone}

{user.counts?.folders || 0} folders · {user.counts?.files || 0} files
} +

Page {pagination.page} of {pagination.pages}

+
+
+} + function Dashboard({ user, onLogout }) { const organizations = adminMemberships(user) const [organizationId, setOrganizationId] = useState(() => sessionStorage.getItem(organizationKey) || organizations[0].organization.id) diff --git a/src/api.js b/src/api.js index 25d115e..0b03928 100644 --- a/src/api.js +++ b/src/api.js @@ -20,6 +20,11 @@ export function messageFrom(error) { export const adminApi = { async login(username, password) { return (await client.post('/auth/login', { username, password })).data }, async me() { return (await client.get('/auth/me')).data.user }, + async platformLogin(username, password) { return (await client.post('/admin/login', { username, password })).data }, + async platformMe() { return (await client.get('/admin/me')).data.admin }, + async platformStats() { return (await client.get('/admin/stats')).data.stats }, + async platformUsers(params) { return (await client.get('/admin/users', { params })).data }, + async platformUpdateUser(id, input) { return (await client.patch(`/admin/users/${id}`, input)).data.user }, async registerOrganization(input) { return (await client.post('/organizations/register', input)).data }, async currentOrganization() { return (await client.get('/organizations/current')).data }, async drive() { return (await client.get('/organizations/drive')).data.connection },