Files
python-oauth2-proxy-k8s/frontend/src/App.jsx
tactile 2b1357e981 front to consta
clean dotnet backend
2026-01-20 17:17:22 +05:00

209 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback, useEffect, useState } from 'react';
import { Card } from '@consta/uikit/Card';
import { Text } from '@consta/uikit/Text';
import { Badge } from '@consta/uikit/Badge';
import { Button } from '@consta/uikit/Button';
const API_URL = '/api/user-info';
const formatError = (error) => {
if (error instanceof Error && error.message) {
return error.message;
}
return 'Не удалось получить данные профиля.';
};
const EmptyState = ({ title, description }) => (
<div className="empty-state">
<Text weight="bold">{title}</Text>
{description ? (
<Text size="s" view="secondary">
{description}
</Text>
) : null}
</div>
);
function App() {
const [data, setData] = useState(null);
const [status, setStatus] = useState('loading');
const [error, setError] = useState('');
const [reloadKey, setReloadKey] = useState(0);
const reload = useCallback(() => {
setReloadKey((value) => value + 1);
}, []);
useEffect(() => {
let mounted = true;
const controller = new AbortController();
const load = async () => {
setStatus('loading');
setError('');
try {
const response = await fetch(API_URL, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Ошибка загрузки: ${response.status}`);
}
const payload = await response.json();
if (!mounted) {
return;
}
setData(payload);
setStatus('success');
} catch (err) {
if (!mounted || err?.name === 'AbortError') {
return;
}
setError(formatError(err));
setStatus('error');
}
};
load();
return () => {
mounted = false;
controller.abort();
};
}, [reloadKey]);
if (status === 'loading') {
return (
<div className="app">
<Card className="section status">
<Text size="l" weight="bold">
Загрузка профиля
</Text>
<Text size="s" view="secondary">
Получаем данные о пользователе и доступных ресурсах.
</Text>
</Card>
</div>
);
}
if (status === 'error') {
return (
<div className="app">
<Card className="section status">
<Text size="l" weight="bold">
Не удалось загрузить данные
</Text>
<Text size="s" view="secondary">
{error}
</Text>
<div className="status-actions">
<Button label="Повторить" onClick={reload} />
</div>
</Card>
</div>
);
}
const roles = data?.roles ?? [];
const links = data?.available_links ?? [];
const organization = data?.organization;
return (
<div className="app">
<Card className="section">
<div className="profile-summary">
<div className="profile-info">
<Text size="l" weight="bold">
{data?.full_name || 'Пользователь'}
</Text>
<Text size="s" view="secondary">
{data?.email || 'Email не указан'}
</Text>
</div>
<div className="profile-meta">
<Text size="s" view="secondary">
Организация
</Text>
<Text weight="bold">
{organization?.name || 'Не указана'}
</Text>
</div>
</div>
</Card>
<Card className="section">
<div className="section-header">
<Text size="m" weight="bold">
Роли и доступ
</Text>
<Text size="s" view="secondary">
Список доступов пользователя внутри организации.
</Text>
</div>
{roles.length ? (
<div className="roles">
{roles.map((role) => (
<div className="role-item" key={role.id || role.name}>
<Badge label={role.name} />
<div className="role-content">
<Text size="s" view="secondary">
{role.description || 'Описание роли отсутствует.'}
</Text>
</div>
</div>
))}
</div>
) : (
<EmptyState
title="Роли не назначены"
description="Свяжитесь с администратором для выдачи доступа."
/>
)}
</Card>
<Card className="section">
<div className="section-header">
<Text size="m" weight="bold">
Доступные сервисы
</Text>
<Text size="s" view="secondary">
Быстрые ссылки на инструменты и ресурсы.
</Text>
</div>
{links.length ? (
<div className="links-grid">
{links.map((link) => (
<Card className="link-card" key={link.id || link.url}>
<div className="link-header">
<Text weight="bold">{link.title}</Text>
<Text size="s" view="secondary">
{link.description || 'Описание отсутствует'}
</Text>
</div>
<div className="link-action">
<Text
as="a"
href={link.url}
view="link"
size="s"
target="_blank"
rel="noreferrer"
>
Перейти
</Text>
</div>
</Card>
))}
</div>
) : (
<EmptyState
title="Пока нет доступных ссылок"
description="Ресурсы появятся после подключения сервисов."
/>
)}
</Card>
</div>
);
}
export default App;