voice
This commit is contained in:
parent
44c7fce2b7
commit
e5d2e96a38
9 changed files with 188 additions and 236 deletions
|
|
@ -7,7 +7,8 @@
|
|||
"Bash(npx prisma generate:*)",
|
||||
"Bash(npm run build:*)",
|
||||
"Bash(docker-compose up:*)",
|
||||
"Bash(npx prisma migrate:*)"
|
||||
"Bash(npx prisma migrate:*)",
|
||||
"Bash(tree:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,21 @@
|
|||
import { adminApiClient } from './client'
|
||||
|
||||
// Dashboard stats from backend /api/admin/analytics/dashboard
|
||||
export interface DashboardStats {
|
||||
users: number
|
||||
cards: number
|
||||
packs: number
|
||||
enabledPacks: number
|
||||
payments: number
|
||||
}
|
||||
|
||||
export interface RecentUser {
|
||||
id: number
|
||||
name?: string
|
||||
email?: string
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
export interface TopPack {
|
||||
id: number
|
||||
title: string
|
||||
cards: number
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface ChartDataPoint {
|
||||
date: string
|
||||
registrations?: number
|
||||
revenue?: number
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
stats: DashboardStats
|
||||
recentUsers: RecentUser[]
|
||||
topPacks: TopPack[]
|
||||
activeUsers: number
|
||||
rooms: number
|
||||
activeRooms: number
|
||||
questionPacks: number
|
||||
publicPacks: number
|
||||
gamesPlayed: number
|
||||
gamesToday: number
|
||||
}
|
||||
|
||||
export const analyticsApi = {
|
||||
// Get dashboard analytics
|
||||
getDashboard: async (): Promise<DashboardData> => {
|
||||
const response = await adminApiClient.get('/api/v2/admin/analytics/dashboard')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get user registration chart data
|
||||
getUsersChart: async (): Promise<{ data: ChartDataPoint[]; period: string }> => {
|
||||
const response = await adminApiClient.get('/api/v2/admin/analytics/users/chart')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get revenue chart data
|
||||
getRevenueChart: async (): Promise<{ data: ChartDataPoint[]; period: string; currency: string }> => {
|
||||
const response = await adminApiClient.get('/api/v2/admin/analytics/revenue/chart')
|
||||
getDashboard: async (): Promise<DashboardStats> => {
|
||||
const response = await adminApiClient.get('/api/admin/analytics/dashboard')
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const packsApi = {
|
|||
showDisabled?: boolean
|
||||
}): Promise<PaginatedResponse<CardPackPreviewDto>> => {
|
||||
try {
|
||||
const response = await adminApiClient.get('/api/v2/admin/packs', {
|
||||
const response = await adminApiClient.get('/api/admin/packs', {
|
||||
params: {
|
||||
page: params?.page || 1,
|
||||
limit: params?.limit || 20,
|
||||
|
|
@ -82,7 +82,7 @@ export const packsApi = {
|
|||
// Get pack details by ID for editing
|
||||
getPack: async (packId: string): Promise<EditCardPackDto> => {
|
||||
try {
|
||||
const response = await adminApiClient.get(`/api/v2/admin/packs/${packId}`)
|
||||
const response = await adminApiClient.get(`/api/admin/packs/${packId}`)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError<{ error?: string; message?: string }>
|
||||
|
|
@ -110,7 +110,7 @@ export const packsApi = {
|
|||
// Create or update pack
|
||||
upsertPack: async (pack: EditCardPackDto): Promise<{ success: boolean; pack: EditCardPackDto }> => {
|
||||
try {
|
||||
const response = await adminApiClient.post('/api/v2/admin/packs', pack)
|
||||
const response = await adminApiClient.post('/api/admin/packs', pack)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError<{ error?: string; message?: string; field?: string; details?: string }>
|
||||
|
|
@ -146,7 +146,7 @@ export const packsApi = {
|
|||
// Delete pack
|
||||
deletePack: async (packId: string): Promise<{ success: boolean; message: string }> => {
|
||||
try {
|
||||
const response = await adminApiClient.delete(`/api/v2/admin/packs/${packId}`)
|
||||
const response = await adminApiClient.delete(`/api/admin/packs/${packId}`)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError<{ error?: string; message?: string }>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,36 @@
|
|||
import { adminApiClient } from './client'
|
||||
import type { UserDto, PaginatedResponse, PaymentDto } from '@/types/models'
|
||||
import type { UserDto, PaginatedResponse } from '@/types/models'
|
||||
|
||||
export const usersApi = {
|
||||
// Get all users with pagination
|
||||
getUsers: async (params?: {
|
||||
page?: number
|
||||
limit?: number
|
||||
ids?: string[]
|
||||
}): Promise<PaginatedResponse<UserDto>> => {
|
||||
const response = await adminApiClient.get('/api/v2/admin/users', {
|
||||
const response = await adminApiClient.get('/api/admin/users', {
|
||||
params: {
|
||||
page: params?.page || 1,
|
||||
limit: params?.limit || 20,
|
||||
ids: params?.ids?.join(','),
|
||||
},
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get user IDs list
|
||||
getUserIds: async (): Promise<string[]> => {
|
||||
const response = await adminApiClient.get('/api/v2/admin/users/ids')
|
||||
return response.data.ids
|
||||
// Get single user by ID
|
||||
getUser: async (userId: string): Promise<UserDto> => {
|
||||
const response = await adminApiClient.get(`/api/admin/users/${userId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get user purchases
|
||||
getUserPurchases: async (userId: string): Promise<PaymentDto[]> => {
|
||||
const response = await adminApiClient.get(`/api/v2/admin/users/${userId}/purchases`)
|
||||
return response.data.payments
|
||||
},
|
||||
|
||||
// Create or update user
|
||||
upsertUser: async (user: UserDto): Promise<{ result: boolean }> => {
|
||||
const response = await adminApiClient.post('/api/v2/admin/users', user)
|
||||
// Update user
|
||||
updateUser: async (userId: string, user: Partial<UserDto>): Promise<UserDto> => {
|
||||
const response = await adminApiClient.patch(`/api/admin/users/${userId}`, user)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Delete user
|
||||
deleteUser: async (userId: string): Promise<{ result: boolean }> => {
|
||||
const response = await adminApiClient.delete(`/api/v2/admin/users/${userId}`)
|
||||
deleteUser: async (userId: string): Promise<{ message: string }> => {
|
||||
const response = await adminApiClient.delete(`/api/admin/users/${userId}`)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Users, FileText, Package, DollarSign, TrendingUp, Clock } from 'lucide-react'
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts'
|
||||
import { Users, Package, TrendingUp, Activity } from 'lucide-react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { analyticsApi, type DashboardData, type ChartDataPoint } from '@/api/analytics'
|
||||
import { analyticsApi, type DashboardStats } from '@/api/analytics'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
|
||||
export default function DashboardPage() {
|
||||
|
|
@ -12,24 +10,12 @@ export default function DashboardPage() {
|
|||
// Only make requests if authenticated and token exists
|
||||
const isReady = isAuthenticated && !!token && !!localStorage.getItem('admin_token')
|
||||
|
||||
const { data: dashboardData, isLoading: dashboardLoading } = useQuery<DashboardData>({
|
||||
const { data: dashboardData, isLoading: dashboardLoading } = useQuery<DashboardStats>({
|
||||
queryKey: ['dashboard'],
|
||||
queryFn: analyticsApi.getDashboard,
|
||||
enabled: isReady,
|
||||
})
|
||||
|
||||
const { data: usersChartData } = useQuery<{ data: ChartDataPoint[] }>({
|
||||
queryKey: ['users-chart'],
|
||||
queryFn: analyticsApi.getUsersChart,
|
||||
enabled: isReady,
|
||||
})
|
||||
|
||||
const { data: revenueChartData } = useQuery<{ data: ChartDataPoint[] }>({
|
||||
queryKey: ['revenue-chart'],
|
||||
queryFn: analyticsApi.getRevenueChart,
|
||||
enabled: isReady,
|
||||
})
|
||||
|
||||
if (dashboardLoading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
|
@ -72,7 +58,7 @@ export default function DashboardPage() {
|
|||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{dashboardData?.stats.users || 0}</div>
|
||||
<div className="text-2xl font-bold">{dashboardData?.users || 0}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Registered users
|
||||
</p>
|
||||
|
|
@ -81,165 +67,96 @@ export default function DashboardPage() {
|
|||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Cards</CardTitle>
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
<CardTitle className="text-sm font-medium">Active Users</CardTitle>
|
||||
<Activity className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{dashboardData?.stats.cards || 0}</div>
|
||||
<div className="text-2xl font-bold">{dashboardData?.activeUsers || 0}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Game cards created
|
||||
Active in last 7 days
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Active Packs</CardTitle>
|
||||
<CardTitle className="text-sm font-medium">Question Packs</CardTitle>
|
||||
<Package className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{dashboardData?.stats.enabledPacks || 0}</div>
|
||||
<div className="text-2xl font-bold">{dashboardData?.publicPacks || 0}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
of {dashboardData?.stats.packs || 0} total packs
|
||||
of {dashboardData?.questionPacks || 0} total packs
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Payments</CardTitle>
|
||||
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
||||
<CardTitle className="text-sm font-medium">Games Today</CardTitle>
|
||||
<TrendingUp className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{dashboardData?.stats.payments || 0}</div>
|
||||
<div className="text-2xl font-bold">{dashboardData?.gamesToday || 0}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Successful transactions
|
||||
of {dashboardData?.gamesPlayed || 0} total games
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Charts */}
|
||||
{/* Rooms Stats */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>User Registrations</CardTitle>
|
||||
<CardTitle>Room Statistics</CardTitle>
|
||||
<CardDescription>
|
||||
Daily user registrations for the last 30 days
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{usersChartData?.data && (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={usersChartData.data}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(value) => new Date(value).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
/>
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
labelFormatter={(value) => new Date(value).toLocaleDateString()}
|
||||
formatter={(value: number | undefined) => [value ?? 0, 'Registrations']}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="registrations"
|
||||
stroke="#8884d8"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#8884d8' }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Revenue Trend</CardTitle>
|
||||
<CardDescription>
|
||||
Daily revenue for the last 30 days
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{revenueChartData?.data && (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={revenueChartData.data}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(value) => new Date(value).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
/>
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
labelFormatter={(value) => new Date(value).toLocaleDateString()}
|
||||
formatter={(value: number | undefined) => [`$${value ?? 0}`, 'Revenue']}
|
||||
/>
|
||||
<Bar dataKey="revenue" fill="#82ca9d" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Recent Users and Top Packs */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Clock className="h-5 w-5" />
|
||||
Recent Users
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Latest user registrations
|
||||
Current room status overview
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{dashboardData?.recentUsers.slice(0, 5).map((user) => (
|
||||
<div key={user.id} className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">{user.name || 'No name'}</p>
|
||||
<p className="text-sm text-muted-foreground">{user.email || 'No email'}</p>
|
||||
<p className="font-medium">Total Rooms</p>
|
||||
<p className="text-sm text-muted-foreground">All time</p>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{user.createdAt ? new Date(user.createdAt).toLocaleDateString() : 'N/A'}
|
||||
<div className="text-2xl font-bold">{dashboardData?.rooms || 0}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">Active Rooms</p>
|
||||
<p className="text-sm text-muted-foreground">Currently playing or waiting</p>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-green-600">{dashboardData?.activeRooms || 0}</div>
|
||||
</div>
|
||||
)) || (
|
||||
<p className="text-muted-foreground">No recent users</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
Popular Packs
|
||||
</CardTitle>
|
||||
<CardTitle>Game Statistics</CardTitle>
|
||||
<CardDescription>
|
||||
Most popular card packs
|
||||
Overall game activity
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{dashboardData?.topPacks.map((pack) => (
|
||||
<div key={pack.id} className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">{pack.title}</p>
|
||||
<p className="text-sm text-muted-foreground">{pack.cards} cards</p>
|
||||
<p className="font-medium">Total Games</p>
|
||||
<p className="text-sm text-muted-foreground">All time completed games</p>
|
||||
</div>
|
||||
<Badge variant={pack.enabled ? "default" : "secondary"}>
|
||||
{pack.enabled ? 'Active' : 'Disabled'}
|
||||
</Badge>
|
||||
<div className="text-2xl font-bold">{dashboardData?.gamesPlayed || 0}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">Today's Games</p>
|
||||
<p className="text-sm text-muted-foreground">Completed today</p>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-blue-600">{dashboardData?.gamesToday || 0}</div>
|
||||
</div>
|
||||
)) || (
|
||||
<p className="text-muted-foreground">No packs available</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -29,28 +29,20 @@ export interface CardPackPreviewDto {
|
|||
order?: number
|
||||
}
|
||||
|
||||
// User model matching backend Prisma schema
|
||||
export interface UserDto {
|
||||
id?: number
|
||||
name?: string
|
||||
id: string
|
||||
email?: string
|
||||
admin: boolean
|
||||
packs: string[]
|
||||
purchases: string[]
|
||||
subscription?: boolean
|
||||
subscriptionFeatures: string[]
|
||||
userDataDto?: unknown
|
||||
userSettingsDto?: unknown
|
||||
name?: string
|
||||
role: 'USER' | 'ADMIN'
|
||||
telegramId?: string
|
||||
createdAt: string
|
||||
gamesPlayed: number
|
||||
gamesWon: number
|
||||
totalPoints: number
|
||||
}
|
||||
|
||||
export interface PaymentDto {
|
||||
id: string
|
||||
userId: string
|
||||
amount: number
|
||||
currency: string
|
||||
status: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
// Note: PaymentDto removed - no payment system exists in the backend
|
||||
|
||||
export interface SubscriptionPlanAdminDto {
|
||||
id: string
|
||||
|
|
|
|||
|
|
@ -362,7 +362,30 @@ export class AdminAuthService implements OnModuleInit {
|
|||
});
|
||||
|
||||
if (!user) {
|
||||
// Create admin user for password login
|
||||
// Check if email is already taken by another user
|
||||
const existingUserWithEmail = await this.prisma.user.findUnique({
|
||||
where: { email: adminUsername },
|
||||
});
|
||||
|
||||
if (existingUserWithEmail) {
|
||||
// If email exists but with different telegramId, update it
|
||||
user = await this.prisma.user.update({
|
||||
where: { id: existingUserWithEmail.id },
|
||||
data: {
|
||||
telegramId: passwordAdminTelegramId,
|
||||
role: 'ADMIN',
|
||||
name: 'Admin',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
role: true,
|
||||
telegramId: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Create new admin user for password login
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
telegramId: passwordAdminTelegramId,
|
||||
|
|
@ -378,6 +401,7 @@ export class AdminAuthService implements OnModuleInit {
|
|||
telegramId: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (user.role !== 'ADMIN') {
|
||||
// Upgrade to admin if needed
|
||||
await this.prisma.user.update({
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import { useNavigate } from 'react-router-dom';
|
|||
import { useAuth } from '../context/AuthContext';
|
||||
import { useRoom } from '../hooks/useRoom';
|
||||
import { questionsApi } from '../services/api';
|
||||
import NameInputModal from '../components/NameInputModal';
|
||||
|
||||
const CreateRoom = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { user, loginAnonymous, loading: authLoading } = useAuth();
|
||||
const { createRoom, loading: roomLoading } = useRoom();
|
||||
|
||||
const [questionPacks, setQuestionPacks] = useState([]);
|
||||
|
|
@ -18,6 +19,27 @@ const CreateRoom = () => {
|
|||
timerDuration: 30,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isNameModalOpen, setIsNameModalOpen] = useState(false);
|
||||
|
||||
// Проверка авторизации и показ модального окна для ввода имени
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user) {
|
||||
setIsNameModalOpen(true);
|
||||
} else if (user) {
|
||||
setIsNameModalOpen(false);
|
||||
}
|
||||
}, [authLoading, user]);
|
||||
|
||||
// Обработка ввода имени и авторизация
|
||||
const handleNameSubmit = async (name) => {
|
||||
try {
|
||||
await loginAnonymous(name);
|
||||
setIsNameModalOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
alert('Ошибка при авторизации. Попробуйте еще раз.');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPacks = async () => {
|
||||
|
|
@ -31,12 +53,16 @@ const CreateRoom = () => {
|
|||
}
|
||||
};
|
||||
|
||||
if (user) {
|
||||
fetchPacks();
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleCreateRoom = async () => {
|
||||
if (!user) {
|
||||
alert('Войдите в систему для создания комнаты');
|
||||
setIsNameModalOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +168,12 @@ const CreateRoom = () => {
|
|||
<button onClick={() => navigate('/')}>Назад</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NameInputModal
|
||||
isOpen={isNameModalOpen}
|
||||
onSubmit={handleNameSubmit}
|
||||
onCancel={null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import { useRoom } from '../hooks/useRoom';
|
|||
import { questionsApi } from '../services/api';
|
||||
import QRCode from 'qrcode';
|
||||
import QRModal from '../components/QRModal';
|
||||
import NameInputModal from '../components/NameInputModal';
|
||||
|
||||
const RoomPage = () => {
|
||||
const { roomCode } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { user, loginAnonymous, loading: authLoading } = useAuth();
|
||||
const {
|
||||
room,
|
||||
participants,
|
||||
|
|
@ -22,6 +23,7 @@ const RoomPage = () => {
|
|||
const [qrCode, setQrCode] = useState('');
|
||||
const [joined, setJoined] = useState(false);
|
||||
const [isQRModalOpen, setIsQRModalOpen] = useState(false);
|
||||
const [isNameModalOpen, setIsNameModalOpen] = useState(false);
|
||||
const [questionPacks, setQuestionPacks] = useState([]);
|
||||
const [selectedPackId, setSelectedPackId] = useState('');
|
||||
const [loadingPacks, setLoadingPacks] = useState(false);
|
||||
|
|
@ -51,6 +53,26 @@ const RoomPage = () => {
|
|||
}
|
||||
}, [roomCode]);
|
||||
|
||||
// Проверка авторизации и показ модального окна для ввода имени
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user && room && !loading) {
|
||||
setIsNameModalOpen(true);
|
||||
} else if (user) {
|
||||
setIsNameModalOpen(false);
|
||||
}
|
||||
}, [authLoading, user, room, loading]);
|
||||
|
||||
// Обработка ввода имени и авторизация
|
||||
const handleNameSubmit = async (name) => {
|
||||
try {
|
||||
await loginAnonymous(name);
|
||||
setIsNameModalOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
alert('Ошибка при авторизации. Попробуйте еще раз.');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleJoin = async () => {
|
||||
if (room && user && !joined) {
|
||||
|
|
@ -247,6 +269,12 @@ const RoomPage = () => {
|
|||
qrCode={qrCode}
|
||||
roomCode={roomCode}
|
||||
/>
|
||||
|
||||
<NameInputModal
|
||||
isOpen={isNameModalOpen}
|
||||
onSubmit={handleNameSubmit}
|
||||
onCancel={null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue