diff --git a/packages/web/src/app/(auth)/manager/page.tsx b/packages/web/src/app/(auth)/manager/page.tsx index 7394158..b937c70 100644 --- a/packages/web/src/app/(auth)/manager/page.tsx +++ b/packages/web/src/app/(auth)/manager/page.tsx @@ -5,6 +5,7 @@ import { STATUS } from "@rahoot/common/types/game/status" import ManagerPassword from "@rahoot/web/components/game/create/ManagerPassword" import QuizEditor from "@rahoot/web/components/game/create/QuizEditor" import MediaLibrary from "@rahoot/web/components/game/create/MediaLibrary" +import ThemeEditor from "@rahoot/web/components/game/create/ThemeEditor" import SelectQuizz from "@rahoot/web/components/game/create/SelectQuizz" import { useEvent, useSocket } from "@rahoot/web/contexts/socketProvider" import { useManagerStore } from "@rahoot/web/stores/manager" @@ -20,6 +21,7 @@ const Manager = () => { const [quizzList, setQuizzList] = useState([]) const [showEditor, setShowEditor] = useState(false) const [showMedia, setShowMedia] = useState(false) + const [showTheme, setShowTheme] = useState(false) useEvent("manager:quizzList", (quizzList) => { setIsAuth(true) @@ -69,12 +71,17 @@ const Manager = () => { ) } + if (showTheme) { + return setShowTheme(false)} /> + } + return ( setShowEditor(true)} onMedia={() => setShowMedia(true)} + onTheme={() => setShowTheme(true)} /> ) } diff --git a/packages/web/src/components/game/GameWrapper.tsx b/packages/web/src/components/game/GameWrapper.tsx index 50ea048..c08ec88 100644 --- a/packages/web/src/components/game/GameWrapper.tsx +++ b/packages/web/src/components/game/GameWrapper.tsx @@ -7,9 +7,9 @@ import Loader from "@rahoot/web/components/Loader" import { useEvent, useSocket } from "@rahoot/web/contexts/socketProvider" import { usePlayerStore } from "@rahoot/web/stores/player" import { useQuestionStore } from "@rahoot/web/stores/question" +import { useThemeStore } from "@rahoot/web/stores/theme" import { MANAGER_SKIP_BTN } from "@rahoot/web/utils/constants" import clsx from "clsx" -import Image from "next/image" import { PropsWithChildren, useEffect, useState } from "react" type Props = PropsWithChildren & { @@ -37,6 +37,7 @@ const GameWrapper = ({ const { isConnected } = useSocket() const { player } = usePlayerStore() const { questionStates, setQuestionStates } = useQuestionStore() + const { backgroundUrl } = useThemeStore() const [isDisabled, setIsDisabled] = useState(false) const next = statusName ? MANAGER_SKIP_BTN[statusName] : null @@ -56,14 +57,21 @@ const GameWrapper = ({ onNext?.() } + const resolvedBackground = backgroundUrl || background.src + return (
-
- background +
+
{!isConnected && !statusName ? ( diff --git a/packages/web/src/components/game/create/SelectQuizz.tsx b/packages/web/src/components/game/create/SelectQuizz.tsx index c6cfb09..cfe6936 100644 --- a/packages/web/src/components/game/create/SelectQuizz.tsx +++ b/packages/web/src/components/game/create/SelectQuizz.tsx @@ -9,9 +9,16 @@ type Props = { onSelect: (_id: string) => void onManage?: () => void onMedia?: () => void + onTheme?: () => void } -const SelectQuizz = ({ quizzList, onSelect, onManage, onMedia }: Props) => { +const SelectQuizz = ({ + quizzList, + onSelect, + onManage, + onMedia, + onTheme, +}: Props) => { const [selected, setSelected] = useState(null) const handleSelect = (id: string) => () => { @@ -45,6 +52,14 @@ const SelectQuizz = ({ quizzList, onSelect, onManage, onMedia }: Props) => { Media )} + {onTheme && ( + + )} {onManage && ( +

Theme editor

+
+ +
+
+

Preview

+
+ Background preview +
+
+ + Current background + +
+
+
+ + +
+ setCustomUrl(e.target.value)} + placeholder="https://example.com/background.webp or /media/your-file" + className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm" + /> +

+ Paste any reachable image URL (including your uploaded media path). Changes apply immediately to the game background. +

+
+ +
+
+
+

Images from media library

+

Pick any uploaded image as the background.

+
+ +
+ +
+ {items.map((item) => ( + + ))} + {!loading && items.length === 0 && ( +
+ No images uploaded yet. Upload an image in the Media page, then pick it here. +
+ )} +
+
+
+
+ ) +} + +export default ThemeEditor diff --git a/packages/web/src/stores/theme.tsx b/packages/web/src/stores/theme.tsx new file mode 100644 index 0000000..d8f32d0 --- /dev/null +++ b/packages/web/src/stores/theme.tsx @@ -0,0 +1,19 @@ +import { create } from "zustand" +import { persist } from "zustand/middleware" + +type ThemeState = { + backgroundUrl: string | null + setBackground: (_url: string | null) => void + reset: () => void +} + +export const useThemeStore = create()( + persist( + (set) => ({ + backgroundUrl: null, + setBackground: (backgroundUrl) => set({ backgroundUrl }), + reset: () => set({ backgroundUrl: null }), + }), + { name: "theme-preferences" }, + ), +)