297 lines
7.9 KiB
TypeScript
297 lines
7.9 KiB
TypeScript
'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',
|
||
},
|
||
};
|