This commit is contained in:
Dmitry 2026-01-10 00:42:18 +03:00
parent 96577926c8
commit 8967c9bac2
10 changed files with 171 additions and 11 deletions

View file

@ -15,6 +15,7 @@
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.11",
"autoprefixer": "^10.4.22",
"axios": "^1.13.2",
@ -1844,6 +1845,37 @@
}
}
},
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
"integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-collection": "1.1.7",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
@ -1923,6 +1955,36 @@
}
}
},
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
"integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-roving-focus": "1.1.11",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",

View file

@ -18,6 +18,7 @@
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.11",
"autoprefixer": "^10.4.22",
"axios": "^1.13.2",

View file

@ -60,8 +60,6 @@ export function CreateAdminRoomDialog({
},
})
const [showAdvanced, setShowAdvanced] = useState(false)
// Fetch data
const { data: usersData } = useQuery({
queryKey: ['users', 1],

View file

@ -19,7 +19,6 @@ import {
DialogTitle,
} from '@/components/ui/dialog'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Eye, EyeOff } from 'lucide-react'
interface ThemeEditorDialogProps {
open: boolean
@ -81,7 +80,6 @@ export function ThemeEditorDialog({
const [isPublic, setIsPublic] = useState(false)
const [colors, setColors] = useState<ThemeColors>(DEFAULT_THEME_COLORS)
const [settings, setSettings] = useState<ThemeSettings>(DEFAULT_THEME_SETTINGS)
const [showPreview, setShowPreview] = useState(false)
useEffect(() => {
if (theme) {

View file

@ -0,0 +1,56 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View file

@ -4,8 +4,6 @@ import { toast } from 'sonner'
import {
themesApi,
isThemesApiError,
DEFAULT_THEME_COLORS,
DEFAULT_THEME_SETTINGS,
type ThemePreview,
type ThemeColors,
type ThemeSettings,
@ -35,7 +33,7 @@ import {
} from '@/components/ui/alert-dialog'
import { Label } from '@/components/ui/label'
import { Checkbox } from '@/components/ui/checkbox'
import { Plus, Search, Edit, Trash2, ChevronLeft, ChevronRight, Eye } from 'lucide-react'
import { Plus, Search, Edit, Trash2, ChevronLeft, ChevronRight } from 'lucide-react'
import { ThemeEditorDialog } from '@/components/ThemeEditorDialog'
export default function ThemesPage() {

View file

@ -13,6 +13,7 @@
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/mapped-types": "^2.1.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-socket.io": "^11.1.11",
@ -2179,6 +2180,26 @@
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/mapped-types": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz",
"integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==",
"license": "MIT",
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"class-transformer": "^0.4.0 || ^0.5.0",
"class-validator": "^0.13.0 || ^0.14.0",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/passport": {
"version": "11.0.5",
"license": "MIT",

View file

@ -27,6 +27,7 @@
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/mapped-types": "^2.1.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-socket.io": "^11.1.11",

View file

@ -4,6 +4,7 @@ import { RoomFiltersDto } from './dto/room-filters.dto';
import { CreateAdminRoomDto } from './dto/create-admin-room.dto';
import { RoomPackService } from '../../room-pack/room-pack.service';
import { customAlphabet } from 'nanoid';
import { Prisma } from '@prisma/client';
const nanoid = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 6);
@ -175,7 +176,9 @@ export class AdminRoomsService {
activeFrom: dto.activeFrom ? new Date(dto.activeFrom) : null,
activeTo: dto.activeTo ? new Date(dto.activeTo) : null,
themeId: dto.themeId || null,
uiControls: dto.uiControls || null,
uiControls: dto.uiControls
? (dto.uiControls as Prisma.InputJsonValue)
: undefined,
questionPackId: dto.questionPackId || null,
maxPlayers: maxPlayers || 10,
allowSpectators: allowSpectators !== undefined ? allowSpectators : true,
@ -208,7 +211,8 @@ export class AdminRoomsService {
});
// Создать участника-хоста
const hostName = dto.hostName || room.host.name || 'Ведущий';
const hostName =
dto.hostName || (room.host ? room.host.name : null) || 'Ведущий';
await this.prisma.participant.create({
data: {
userId: dto.hostId,

View file

@ -3,6 +3,7 @@ import { PrismaService } from '../../prisma/prisma.service';
import { ThemeFiltersDto } from './dto/theme-filters.dto';
import { CreateThemeDto } from './dto/create-theme.dto';
import { UpdateThemeDto } from './dto/update-theme.dto';
import { Prisma } from '@prisma/client';
@Injectable()
export class AdminThemesService {
@ -81,8 +82,11 @@ export class AdminThemesService {
async create(createThemeDto: CreateThemeDto, createdBy: string) {
return this.prisma.theme.create({
data: {
...createThemeDto,
name: createThemeDto.name,
isPublic: createThemeDto.isPublic,
createdBy,
colors: JSON.parse(JSON.stringify(createThemeDto.colors)) as Prisma.InputJsonValue,
settings: JSON.parse(JSON.stringify(createThemeDto.settings)) as Prisma.InputJsonValue,
},
include: {
creator: {
@ -105,9 +109,26 @@ export class AdminThemesService {
throw new NotFoundException('Theme not found');
}
const updateData: Prisma.ThemeUpdateInput = {
...(updateThemeDto.name !== undefined && { name: updateThemeDto.name }),
...(updateThemeDto.isPublic !== undefined && {
isPublic: updateThemeDto.isPublic,
}),
...(updateThemeDto.colors && {
colors: JSON.parse(
JSON.stringify(updateThemeDto.colors),
) as Prisma.InputJsonValue,
}),
...(updateThemeDto.settings && {
settings: JSON.parse(
JSON.stringify(updateThemeDto.settings),
) as Prisma.InputJsonValue,
}),
};
return this.prisma.theme.update({
where: { id },
data: updateThemeDto,
data: updateData,
include: {
creator: {
select: {