front to consta
clean dotnet backend
This commit is contained in:
@@ -1,113 +1,208 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import './App.css'
|
||||
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 [userInfo, setUserInfo] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
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(() => {
|
||||
// Получаем информацию о пользователе от бэкенда
|
||||
fetch('/api/user-info')
|
||||
.then(response => {
|
||||
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(`HTTP error! status: ${response.status}`)
|
||||
throw new Error(`Ошибка загрузки: ${response.status}`);
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then(data => {
|
||||
setUserInfo(data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err.message)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
load();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
controller.abort();
|
||||
};
|
||||
}, [reloadKey]);
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="loading">Загрузка...</div>
|
||||
<div className="app">
|
||||
<Card className="section status">
|
||||
<Text size="l" weight="bold">
|
||||
Загрузка профиля
|
||||
</Text>
|
||||
<Text size="s" view="secondary">
|
||||
Получаем данные о пользователе и доступных ресурсах.
|
||||
</Text>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (status === 'error') {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="error">
|
||||
<h2>Ошибка</h2>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
<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="container">
|
||||
<header className="header">
|
||||
<h1>Dex Authentication Demo</h1>
|
||||
<p className="subtitle">Демонстрация аутентификации через DexAuthenticator</p>
|
||||
</header>
|
||||
|
||||
<div className="user-card">
|
||||
<h2>Информация о пользователе</h2>
|
||||
<div className="info-grid">
|
||||
<div className="info-item">
|
||||
<span className="label">Email:</span>
|
||||
<span className="value">{userInfo.email || 'Не указан'}</span>
|
||||
<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="info-item">
|
||||
<span className="label">Полное имя:</span>
|
||||
<span className="value">{userInfo.full_name || 'Не указано'}</span>
|
||||
<div className="profile-meta">
|
||||
<Text size="s" view="secondary">
|
||||
Организация
|
||||
</Text>
|
||||
<Text weight="bold">
|
||||
{organization?.name || 'Не указана'}
|
||||
</Text>
|
||||
</div>
|
||||
{userInfo.organization && (
|
||||
<div className="info-item">
|
||||
<span className="label">Организация:</span>
|
||||
<span className="value">{userInfo.organization.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="roles-section">
|
||||
<h3>Роли</h3>
|
||||
<div className="roles-list">
|
||||
{userInfo.roles && userInfo.roles.map(role => (
|
||||
<div key={role.id} className="role-badge">
|
||||
<span className="role-name">{role.name}</span>
|
||||
{role.description && (
|
||||
<span className="role-description">{role.description}</span>
|
||||
)}
|
||||
<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>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
title="Роли не назначены"
|
||||
description="Свяжитесь с администратором для выдачи доступа."
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<div className="links-section">
|
||||
<h3>Доступные ресурсы</h3>
|
||||
{userInfo.available_links && userInfo.available_links.length > 0 ? (
|
||||
<div className="links-grid">
|
||||
{userInfo.available_links.map(link => (
|
||||
<a
|
||||
key={link.id}
|
||||
href={link.url}
|
||||
className="link-card"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h4>{link.title}</h4>
|
||||
{link.description && <p>{link.description}</p>}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="no-links">У вас нет доступных ресурсов</p>
|
||||
)}
|
||||
<Card className="section">
|
||||
<div className="section-header">
|
||||
<Text size="m" weight="bold">
|
||||
Доступные сервисы
|
||||
</Text>
|
||||
<Text size="s" view="secondary">
|
||||
Быстрые ссылки на инструменты и ресурсы.
|
||||
</Text>
|
||||
</div>
|
||||
</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
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user