Initialize Vyryi front-end project with React, Vite, and TypeScript. Add essential files including configuration, environment variables, and basic structure. Implement authentication, routing, and API interaction for a Telegram Mini App UI. Include README and styling for a cohesive user experience.

This commit is contained in:
Redsandy
2026-08-12 11:27:07 +03:00
commit 59d4a691d6
30 changed files with 3933 additions and 0 deletions

118
src/pages/HomePage.tsx Normal file
View File

@@ -0,0 +1,118 @@
import { useEffect, useState } from 'react'
import { Link, Navigate } from 'react-router-dom'
import { api } from '../api'
import { useAuth } from '../auth'
import type { GameStub } from '../types'
import { GATE_FROM_KEY } from '../vfx/gateCaps'
const DEPOSIT_AMOUNTS = [100, 500, 1000]
export function HomePage() {
const { user, loading, setUser } = useAuth()
const [games, setGames] = useState<GameStub[]>([])
const [depositOpen, setDepositOpen] = useState(false)
const [busy, setBusy] = useState(false)
const [message, setMessage] = useState<string | null>(null)
const [fromGate, setFromGate] = useState(false)
useEffect(() => {
void api.listGames().then(setGames).catch(() => setGames([]))
}, [])
useEffect(() => {
try {
if (sessionStorage.getItem(GATE_FROM_KEY) === '1') {
sessionStorage.removeItem(GATE_FROM_KEY)
setFromGate(true)
}
} catch {
/* ignore */
}
}, [])
if (!loading && user && !user.agreement_accepted) {
return <Navigate to="/" replace />
}
if (!loading && !user) {
return <Navigate to="/" replace />
}
async function deposit(amount: number) {
setBusy(true)
setMessage(null)
try {
const wallet = await api.deposit(amount)
if (user) {
setUser({ ...user, balance: wallet.balance })
}
setMessage(`Зачислено ${amount}`)
setDepositOpen(false)
} catch (err) {
setMessage(err instanceof Error ? err.message : 'Ошибка пополнения')
} finally {
setBusy(false)
}
}
return (
<main className={fromGate ? 'home home--from-gate' : 'home'}>
<header className="home__top">
<div className="home__brand-row">
<img src="/logo.png" alt="" className="home__mark" />
<span className="brand brand--sm">Vyryi</span>
</div>
<div className="balance-panel">
<span className="balance-panel__label">Баланс</span>
<strong className="balance-panel__value">{user?.balance ?? '—'}</strong>
<button type="button" className="btn btn--gold btn--sm" onClick={() => setDepositOpen(true)}>
Пополнить
</button>
</div>
</header>
{message && <p className="toast">{message}</p>}
<section className="games" aria-labelledby="games-title">
<h2 id="games-title">Слоты</h2>
<p className="section-lead">Врата ещё закрыты пока только предвестники.</p>
<ul className="games__list">
{games.map((game) => (
<li key={game.slug}>
<Link to={`/games/${game.slug}`} className="game-link">
<span className="game-link__title">{game.title}</span>
<span className="game-link__status">Скоро</span>
<span className="game-link__desc">{game.description}</span>
</Link>
</li>
))}
</ul>
</section>
{depositOpen && (
<div className="modal" role="dialog" aria-modal="true" aria-labelledby="deposit-title">
<div className="modal__panel">
<h3 id="deposit-title">Пополнение</h3>
<p className="section-lead">Тестовая заглушка. Вывод недоступен.</p>
<div className="deposit-grid">
{DEPOSIT_AMOUNTS.map((amount) => (
<button
key={amount}
type="button"
className="btn btn--outline"
disabled={busy}
onClick={() => void deposit(amount)}
>
+{amount}
</button>
))}
</div>
<button type="button" className="btn btn--ghost" onClick={() => setDepositOpen(false)}>
Закрыть
</button>
</div>
</div>
)}
</main>
)
}