From 7ad18d53f34cb298407d89bea0a611ec66c1bc4b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 6 Jan 2026 23:12:36 +0300 Subject: [PATCH] admin and qr --- .claude/settings.local.json | 3 +- admin/.dockerignore | 9 + admin/.gitignore | 24 + admin/@/components/ui/alert-dialog.tsx | 139 + admin/@/components/ui/button.tsx | 57 + admin/@/components/ui/card.tsx | 79 + admin/@/components/ui/dialog.tsx | 120 + admin/@/components/ui/form.tsx | 177 + admin/@/components/ui/input.tsx | 22 + admin/@/components/ui/label.tsx | 24 + admin/@/components/ui/select.tsx | 160 + admin/@/components/ui/sonner.tsx | 43 + admin/@/components/ui/table.tsx | 117 + admin/@/components/ui/textarea.tsx | 22 + admin/Dockerfile | 37 + admin/README.md | 73 + admin/components.json | 18 + admin/eslint.config.js | 23 + admin/index.html | 13 + admin/nginx.conf | 41 + admin/package-lock.json | 7436 +++++++++++++++++ admin/package.json | 59 + admin/postcss.config.cjs | 6 + admin/public/vite.svg | 1 + admin/src/App.css | 42 + admin/src/App.tsx | 47 + admin/src/api/analytics.ts | 55 + admin/src/api/auth.test.ts | 105 + admin/src/api/auth.ts | 57 + admin/src/api/cards.ts | 131 + admin/src/api/cardsWithVoice.test.ts | 87 + admin/src/api/cardsWithVoice.ts | 59 + admin/src/api/client.test.ts | 127 + admin/src/api/client.ts | 200 + admin/src/api/media.ts | 109 + admin/src/api/packs.ts | 215 + admin/src/api/tests.ts | 131 + admin/src/api/users.ts | 44 + admin/src/api/voices.ts | 45 + admin/src/assets/react.svg | 1 + admin/src/components/BulkCardEditor.test.tsx | 95 + admin/src/components/BulkCardEditor.tsx | 412 + admin/src/components/BulkCardUpload.tsx | 372 + admin/src/components/CardEditorPreview.tsx | 358 + admin/src/components/CardVoicesManager.tsx | 311 + admin/src/components/JSONQuestionEditor.tsx | 182 + admin/src/components/PackCardsManager.tsx | 209 + admin/src/components/PackTestsManager.tsx | 214 + admin/src/components/QuestionEditorDialog.tsx | 300 + .../src/components/TestPacksManager.test.tsx | 89 + admin/src/components/TestPacksManager.tsx | 260 + admin/src/components/TestQuestionsManager.tsx | 183 + admin/src/components/TokenRefreshProvider.tsx | 52 + .../forms/InputButtonsQuestionForm.tsx | 251 + .../components/forms/MatrixQuestionForm.tsx | 75 + .../components/forms/SimpleQuestionForm.tsx | 213 + admin/src/components/layout/Layout.tsx | 131 + admin/src/components/ui/alert-dialog.tsx | 138 + admin/src/components/ui/alert.tsx | 59 + admin/src/components/ui/audio-upload.tsx | 335 + admin/src/components/ui/badge.tsx | 37 + admin/src/components/ui/button.tsx | 59 + admin/src/components/ui/card.tsx | 79 + admin/src/components/ui/checkbox.tsx | 28 + .../ui/color-palette-input.test.tsx | 72 + .../src/components/ui/color-palette-input.tsx | 85 + admin/src/components/ui/dialog.tsx | 120 + admin/src/components/ui/image-upload.tsx | 242 + admin/src/components/ui/input.tsx | 24 + admin/src/components/ui/label.tsx | 24 + admin/src/components/ui/select.tsx | 160 + admin/src/components/ui/sonner.tsx | 25 + admin/src/components/ui/table.tsx | 117 + admin/src/components/ui/textarea.tsx | 23 + admin/src/index.css | 59 + admin/src/main.tsx | 20 + admin/src/pages/CardsPage.tsx | 721 ++ admin/src/pages/DashboardPage.tsx | 249 + admin/src/pages/LoginPage.tsx | 438 + admin/src/pages/PacksPage.tsx | 673 ++ admin/src/pages/TestsPage.tsx | 639 ++ admin/src/pages/UsersPage.tsx | 548 ++ admin/src/stores/authStore.ts | 146 + admin/src/test/setup.ts | 21 + admin/src/types/models.ts | 198 + admin/src/types/questions.test.ts | 41 + admin/src/types/questions.ts | 161 + admin/tailwind.config.js | 77 + admin/tsconfig.app.json | 34 + admin/tsconfig.json | 7 + admin/tsconfig.node.json | 26 + admin/vite.config.ts | 22 + backend/prisma/schema.prisma | 41 +- backend/src/app.module.ts | 2 + backend/src/rooms/rooms.controller.ts | 12 +- backend/src/rooms/rooms.service.ts | 22 +- package-lock.json | 7 + package.json | 1 + src/components/QRModal.css | 182 + src/components/QRModal.jsx | 63 + src/index.css | 8 + src/pages/Home.jsx | 4 + src/pages/JoinRoom.css | 87 + src/pages/JoinRoom.jsx | 190 +- src/pages/RoomPage.jsx | 34 +- 105 files changed, 20181 insertions(+), 44 deletions(-) create mode 100644 admin/.dockerignore create mode 100644 admin/.gitignore create mode 100644 admin/@/components/ui/alert-dialog.tsx create mode 100644 admin/@/components/ui/button.tsx create mode 100644 admin/@/components/ui/card.tsx create mode 100644 admin/@/components/ui/dialog.tsx create mode 100644 admin/@/components/ui/form.tsx create mode 100644 admin/@/components/ui/input.tsx create mode 100644 admin/@/components/ui/label.tsx create mode 100644 admin/@/components/ui/select.tsx create mode 100644 admin/@/components/ui/sonner.tsx create mode 100644 admin/@/components/ui/table.tsx create mode 100644 admin/@/components/ui/textarea.tsx create mode 100644 admin/Dockerfile create mode 100644 admin/README.md create mode 100644 admin/components.json create mode 100644 admin/eslint.config.js create mode 100644 admin/index.html create mode 100644 admin/nginx.conf create mode 100644 admin/package-lock.json create mode 100644 admin/package.json create mode 100644 admin/postcss.config.cjs create mode 100644 admin/public/vite.svg create mode 100644 admin/src/App.css create mode 100644 admin/src/App.tsx create mode 100644 admin/src/api/analytics.ts create mode 100644 admin/src/api/auth.test.ts create mode 100644 admin/src/api/auth.ts create mode 100644 admin/src/api/cards.ts create mode 100644 admin/src/api/cardsWithVoice.test.ts create mode 100644 admin/src/api/cardsWithVoice.ts create mode 100644 admin/src/api/client.test.ts create mode 100644 admin/src/api/client.ts create mode 100644 admin/src/api/media.ts create mode 100644 admin/src/api/packs.ts create mode 100644 admin/src/api/tests.ts create mode 100644 admin/src/api/users.ts create mode 100644 admin/src/api/voices.ts create mode 100644 admin/src/assets/react.svg create mode 100644 admin/src/components/BulkCardEditor.test.tsx create mode 100644 admin/src/components/BulkCardEditor.tsx create mode 100644 admin/src/components/BulkCardUpload.tsx create mode 100644 admin/src/components/CardEditorPreview.tsx create mode 100644 admin/src/components/CardVoicesManager.tsx create mode 100644 admin/src/components/JSONQuestionEditor.tsx create mode 100644 admin/src/components/PackCardsManager.tsx create mode 100644 admin/src/components/PackTestsManager.tsx create mode 100644 admin/src/components/QuestionEditorDialog.tsx create mode 100644 admin/src/components/TestPacksManager.test.tsx create mode 100644 admin/src/components/TestPacksManager.tsx create mode 100644 admin/src/components/TestQuestionsManager.tsx create mode 100644 admin/src/components/TokenRefreshProvider.tsx create mode 100644 admin/src/components/forms/InputButtonsQuestionForm.tsx create mode 100644 admin/src/components/forms/MatrixQuestionForm.tsx create mode 100644 admin/src/components/forms/SimpleQuestionForm.tsx create mode 100644 admin/src/components/layout/Layout.tsx create mode 100644 admin/src/components/ui/alert-dialog.tsx create mode 100644 admin/src/components/ui/alert.tsx create mode 100644 admin/src/components/ui/audio-upload.tsx create mode 100644 admin/src/components/ui/badge.tsx create mode 100644 admin/src/components/ui/button.tsx create mode 100644 admin/src/components/ui/card.tsx create mode 100644 admin/src/components/ui/checkbox.tsx create mode 100644 admin/src/components/ui/color-palette-input.test.tsx create mode 100644 admin/src/components/ui/color-palette-input.tsx create mode 100644 admin/src/components/ui/dialog.tsx create mode 100644 admin/src/components/ui/image-upload.tsx create mode 100644 admin/src/components/ui/input.tsx create mode 100644 admin/src/components/ui/label.tsx create mode 100644 admin/src/components/ui/select.tsx create mode 100644 admin/src/components/ui/sonner.tsx create mode 100644 admin/src/components/ui/table.tsx create mode 100644 admin/src/components/ui/textarea.tsx create mode 100644 admin/src/index.css create mode 100644 admin/src/main.tsx create mode 100644 admin/src/pages/CardsPage.tsx create mode 100644 admin/src/pages/DashboardPage.tsx create mode 100644 admin/src/pages/LoginPage.tsx create mode 100644 admin/src/pages/PacksPage.tsx create mode 100644 admin/src/pages/TestsPage.tsx create mode 100644 admin/src/pages/UsersPage.tsx create mode 100644 admin/src/stores/authStore.ts create mode 100644 admin/src/test/setup.ts create mode 100644 admin/src/types/models.ts create mode 100644 admin/src/types/questions.test.ts create mode 100644 admin/src/types/questions.ts create mode 100644 admin/tailwind.config.js create mode 100644 admin/tsconfig.app.json create mode 100644 admin/tsconfig.json create mode 100644 admin/tsconfig.node.json create mode 100644 admin/vite.config.ts create mode 100644 src/components/QRModal.css create mode 100644 src/components/QRModal.jsx create mode 100644 src/pages/JoinRoom.css diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a1e7edd..6da3c8b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,7 +6,8 @@ "Bash(npx prisma init)", "Bash(npx prisma generate:*)", "Bash(npm run build:*)", - "Bash(docker-compose up:*)" + "Bash(docker-compose up:*)", + "Bash(npx prisma migrate:*)" ] } } diff --git a/admin/.dockerignore b/admin/.dockerignore new file mode 100644 index 0000000..a01fef3 --- /dev/null +++ b/admin/.dockerignore @@ -0,0 +1,9 @@ +.git +.gitignore +node_modules +dist +.vscode +.idea +.cursor +*.log +**/.DS_Store \ No newline at end of file diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/admin/@/components/ui/alert-dialog.tsx b/admin/@/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..8722561 --- /dev/null +++ b/admin/@/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/admin/@/components/ui/button.tsx b/admin/@/components/ui/button.tsx new file mode 100644 index 0000000..38ecac7 --- /dev/null +++ b/admin/@/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +// eslint-disable-next-line react-refresh/only-export-components +export { Button, buttonVariants } diff --git a/admin/@/components/ui/card.tsx b/admin/@/components/ui/card.tsx new file mode 100644 index 0000000..f62edea --- /dev/null +++ b/admin/@/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/admin/@/components/ui/dialog.tsx b/admin/@/components/ui/dialog.tsx new file mode 100644 index 0000000..c680b9d --- /dev/null +++ b/admin/@/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/admin/@/components/ui/form.tsx b/admin/@/components/ui/form.tsx new file mode 100644 index 0000000..d7bf186 --- /dev/null +++ b/admin/@/components/ui/form.tsx @@ -0,0 +1,177 @@ +/* eslint-disable react-refresh/only-export-components */ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + FormProvider, + useFormContext, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext(null) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + if (!itemContext) { + throw new Error("useFormField should be used within ") + } + + const fieldState = getFieldState(fieldContext.name, formState) + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext(null) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +