Files
uno-click/site/app/dashboard/page.tsx
T
2026-05-13 14:20:41 +00:00

297 lines
7.9 KiB
TypeScript
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.
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
interface User {
id: number;
email: string;
displayName: string;
role: string;
status: string;
balance: number;
createdAt: string;
sessionId: string;
}
export default function DashboardPage() {
const router = useRouter();
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [uniqueizerLoading, setUniqueizerLoading] = useState(false);
useEffect(() => {
async function loadUser() {
try {
const response = await fetch('/api/auth/me', { credentials: 'same-origin' });
if (!response.ok) {
router.push('/');
return;
}
const data = await response.json();
setUser(data.user);
} catch (err) {
router.push('/');
} finally {
setLoading(false);
}
}
loadUser();
}, [router]);
async function handleLogout() {
try {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'same-origin',
});
router.push('/');
} catch (err) {
console.error('Logout error:', err);
router.push('/');
}
}
async function getCsrfToken(): Promise<string> {
const response = await fetch('/api/auth/csrf', { credentials: 'same-origin' });
const data = await response.json();
return data.csrfToken || '';
}
async function startUniqueizer() {
setUniqueizerLoading(true);
try {
const csrfToken = await getCsrfToken();
const response = await fetch('/api/scenario/uniqueizer/start', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'x-csrf-token': csrfToken,
},
});
if (response.ok) {
const data = await response.json();
// Передаём generationUuid в URL
router.push(`/uniqueizer?generationUuid=${data.generationUuid}`);
} else {
const data = await response.json();
alert('Ошибка запуска сценария: ' + (data.message || response.statusText));
}
} catch (err: unknown) {
alert('Ошибка: ' + (err instanceof Error ? err.message : String(err)));
} finally {
setUniqueizerLoading(false);
}
}
if (loading) {
return <div style={styles.loading}>Загрузка...</div>;
}
return (
<div style={styles.page}>
<div style={styles.container}>
<div style={styles.header}>
<h1 style={styles.headerTitle}>Uno Click</h1>
<button onClick={handleLogout} style={styles.logoutBtn}>
Выйти
</button>
</div>
<div style={styles.welcome}>
<h2 style={styles.welcomeTitle}>
Добро пожаловать, {user?.displayName || user?.email}!
</h2>
<p style={styles.welcomeText}>Выберите сценарий для запуска</p>
</div>
<div style={styles.scenariosGrid}>
{/* Nano Banana */}
<div style={styles.scenarioCard}>
<div style={{ ...styles.scenarioIcon, ...styles.scenarioIconBlue }}>
🎨
</div>
<div style={styles.scenarioName}>Nano Banana</div>
<div style={styles.scenarioDesc}>
Генерация изображений по текстовому описанию. Введите промпт и получите уникальную иллюстрацию.
</div>
<a href="/prompt?scenario=nano-banana" style={styles.btnPrimary}>
Запустить
</a>
</div>
{/* Demo Scenario */}
<div style={styles.scenarioCard}>
<div style={{ ...styles.scenarioIcon, ...styles.scenarioIconGreen }}>
🧪
</div>
<div style={styles.scenarioName}>Demo Scenario</div>
<div style={styles.scenarioDesc}>
Тестовый сценарий для проверки функциональности. Многоступенчатая генерация с подтверждением.
</div>
<a href="/prompt?scenario=demo-scenario" style={styles.btnSecondary}>
Запустить
</a>
</div>
{/* Уникализация */}
<div style={styles.scenarioCard}>
<div style={{ ...styles.scenarioIcon, ...styles.scenarioIconBlue }}>
</div>
<div style={styles.scenarioName}>Уникализация</div>
<div style={styles.scenarioDesc}>
Запуск сценария уникализации контента.
</div>
<button
onClick={startUniqueizer}
disabled={uniqueizerLoading}
style={{
...styles.btnPrimary,
...(uniqueizerLoading ? styles.btnDisabled : {}),
}}
>
{uniqueizerLoading ? 'Запуск...' : 'Запустить'}
</button>
</div>
</div>
</div>
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
page: {
minHeight: '100vh',
padding: '40px 20px',
background: '#f5f5f5',
},
container: {
maxWidth: '800px',
margin: '0 auto',
},
header: {
background: 'white',
padding: '20px 30px',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
marginBottom: '20px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
headerTitle: {
fontSize: '20px',
fontWeight: 600,
color: '#333',
},
logoutBtn: {
background: 'none',
border: '1px solid #ddd',
padding: '8px 16px',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px',
color: '#666',
},
welcome: {
background: 'white',
padding: '20px 30px',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
marginBottom: '20px',
},
welcomeTitle: {
fontSize: '18px',
fontWeight: 600,
color: '#333',
marginBottom: '8px',
},
welcomeText: {
color: '#666',
fontSize: '14px',
},
scenariosGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
gap: '20px',
},
scenarioCard: {
background: 'white',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
padding: '24px',
transition: 'box-shadow 0.2s',
},
scenarioIcon: {
width: '48px',
height: '48px',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '24px',
marginBottom: '16px',
},
scenarioIconBlue: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
},
scenarioIconGreen: {
background: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)',
},
scenarioName: {
fontSize: '16px',
fontWeight: 600,
color: '#333',
marginBottom: '8px',
},
scenarioDesc: {
fontSize: '14px',
color: '#666',
marginBottom: '20px',
lineHeight: 1.5,
},
btnPrimary: {
width: '100%',
padding: '12px',
background: '#007bff',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '14px',
fontWeight: 500,
cursor: 'pointer',
textAlign: 'center',
textDecoration: 'none',
display: 'inline-block',
},
btnSecondary: {
width: '100%',
padding: '12px',
background: '#f5f5f5',
color: '#333',
border: '1px solid #ddd',
borderRadius: '6px',
fontSize: '14px',
fontWeight: 500,
cursor: 'pointer',
textAlign: 'center',
textDecoration: 'none',
display: 'inline-block',
},
btnDisabled: {
background: '#ccc',
cursor: 'not-allowed',
},
loading: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
fontSize: '18px',
color: '#666',
},
};