realtime game configuration
This commit is contained in:
parent
f9ad545e45
commit
d1baf097fa
@ -3,56 +3,120 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { gameConfigPatchSchema } from "@/lib/validations/game";
|
import { gameConfigPatchSchema } from "@/lib/validations/game";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { useGameStore } from "@/lib/store/game-store";
|
||||||
|
import { api } from "@/trpc/react";
|
||||||
|
import { useLobbyStore } from "@/lib/store/lobby-store";
|
||||||
|
import { Slider } from "@/components/ui/slider";
|
||||||
|
import { getMinMax } from "@/lib/validations/utils";
|
||||||
|
import { debounce } from "@/lib/utils";
|
||||||
|
import React from "react";
|
||||||
|
import { useSessionPlayerStore } from "@/lib/store/current-player-store";
|
||||||
|
|
||||||
|
const formSchema = gameConfigPatchSchema.pick({
|
||||||
|
allowHints: true,
|
||||||
|
// scoreMultiplier: true,
|
||||||
|
timeLimit: true,
|
||||||
|
});
|
||||||
|
|
||||||
export default function GameConfigForm() {
|
export default function GameConfigForm() {
|
||||||
// ...
|
const updateGameConfig = api.gameConfig.update.useMutation();
|
||||||
const form = useForm<z.infer<typeof gameConfigPatchSchema>>({
|
const gameConfig = useGameStore((state) => state.gameConfig);
|
||||||
resolver: zodResolver(gameConfigPatchSchema),
|
const lobbyId = useLobbyStore((state) => state.lobby.id);
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
allowHints: false,
|
allowHints: gameConfig.allowHints,
|
||||||
scoreMultiplier: 1,
|
timeLimit: gameConfig.timeLimit,
|
||||||
timeLimit: 2,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Define a submit handler.
|
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
function onSubmit(values: z.infer<typeof gameConfigPatchSchema>) {
|
updateGameConfig.mutate({
|
||||||
// Do something with the form values.
|
lobbyId,
|
||||||
// ✅ This will be type-safe and validated.
|
config: {
|
||||||
console.log(values);
|
...gameConfig,
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
const save = form.handleSubmit(onSubmit);
|
||||||
|
React.useEffect(() => {
|
||||||
|
form.reset({
|
||||||
|
allowHints: gameConfig.allowHints,
|
||||||
|
timeLimit: gameConfig.timeLimit,
|
||||||
|
});
|
||||||
|
}, [gameConfig]);
|
||||||
|
const isLobbyAdmin = useSessionPlayerStore((state) => state.isLobbyAdmin);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="allowHints"
|
name="allowHints"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="container-bg flex flex-row items-center justify-between p-3">
|
<FormItem className="container-bg bg-background/10 flex flex-row items-center justify-between p-3">
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>Allow hints</FormLabel>
|
<FormLabel>Allow hints</FormLabel>
|
||||||
</div>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch
|
||||||
|
className="cursor-pointer"
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={(checked) => {
|
||||||
|
field.onChange(checked);
|
||||||
|
save();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{/* <Button type="submit">Submit</Button> */}
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="timeLimit"
|
||||||
|
render={({ field }) => {
|
||||||
|
const { min, max } = getMinMax(formSchema.shape.timeLimit);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<div className="container-bg bg-background/10 space-y-2 p-3">
|
||||||
|
<FormLabel>
|
||||||
|
Time Limit
|
||||||
|
<span className="text-xl font-bold">
|
||||||
|
{`${field.value}`} min
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
{isLobbyAdmin && (
|
||||||
|
<FormControl>
|
||||||
|
<Slider
|
||||||
|
className="w-full max-w-xs"
|
||||||
|
value={[field.value]}
|
||||||
|
max={max ?? 100}
|
||||||
|
min={min ?? 1}
|
||||||
|
step={1}
|
||||||
|
onValueChange={([value]) => {
|
||||||
|
field.onChange(value);
|
||||||
|
debounce(save, 1000)();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,14 +7,16 @@ import GameConfigForm from "./game-config-form";
|
|||||||
import GameVariantSelector from "./game-variant-selector";
|
import GameVariantSelector from "./game-variant-selector";
|
||||||
import { api } from "@/trpc/react";
|
import { api } from "@/trpc/react";
|
||||||
import { useLobbyStore } from "@/lib/store/lobby-store";
|
import { useLobbyStore } from "@/lib/store/lobby-store";
|
||||||
|
import { isCallOrNewExpression } from "typescript";
|
||||||
|
import { useSessionPlayerStore } from "@/lib/store/current-player-store";
|
||||||
|
|
||||||
function GameConfigurator() {
|
function GameConfigurator() {
|
||||||
const updateGameConfig = api.gameConfig.update.useMutation();
|
const updateGameConfig = api.gameConfig.update.useMutation();
|
||||||
const selectGame = useGameStore((state) => state.setSelectedGame);
|
const selectGame = useGameStore((state) => state.setSelectedGame);
|
||||||
const selectedGame = useGameStore((state) => state.selectedGame);
|
const selectedGame = useGameStore((state) => state.selectedGame);
|
||||||
const setGameConfig = useGameStore((state) => state.setGameConfig);
|
|
||||||
const gameConfig = useGameStore((state) => state.gameConfig);
|
const gameConfig = useGameStore((state) => state.gameConfig);
|
||||||
const lobbyId = useLobbyStore((state) => state.lobby.id);
|
const lobbyId = useLobbyStore((state) => state.lobby.id);
|
||||||
|
const isLobbyAdmin = useSessionPlayerStore((state) => state.isLobbyAdmin);
|
||||||
const handleUnselectGame = () => {
|
const handleUnselectGame = () => {
|
||||||
selectGame(undefined);
|
selectGame(undefined);
|
||||||
updateGameConfig.mutate({
|
updateGameConfig.mutate({
|
||||||
@ -26,9 +28,19 @@ function GameConfigurator() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVariantSelect = (gameVariantId?: string) => {
|
||||||
|
updateGameConfig.mutate({
|
||||||
|
lobbyId,
|
||||||
|
config: {
|
||||||
|
...gameConfig,
|
||||||
|
gameVariantId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col justify-between p-6">
|
<div className="flex size-full flex-col justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<h2 className="text-xl font-semibold">
|
<h2 className="text-xl font-semibold">
|
||||||
{selectedGame?.name ?? "Select a game to get started"}
|
{selectedGame?.name ?? "Select a game to get started"}
|
||||||
@ -42,14 +54,14 @@ function GameConfigurator() {
|
|||||||
|
|
||||||
{selectedGame && (
|
{selectedGame && (
|
||||||
<>
|
<>
|
||||||
<GameVariantSelector />
|
<GameVariantSelector onSelect={handleVariantSelect} />
|
||||||
<GameConfigForm />
|
<GameConfigForm />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedGame && (
|
{selectedGame && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="mt-4 flex items-center gap-2">
|
||||||
<Button>Start Game</Button>
|
<Button>Start Game</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
|
|||||||
@ -25,17 +25,38 @@ const getGameVariants = (id: string) => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function GameVariantSelector() {
|
function GameVariantSelector({
|
||||||
const selectedGame = useGameStore((state) => state.selectedGame);
|
onSelect,
|
||||||
const variants = getGameVariants(selectedGame?.id!);
|
}: {
|
||||||
|
onSelect: (gameVariantId?: string) => void;
|
||||||
|
}) {
|
||||||
|
const gameId = useGameStore((state) => state.gameConfig.gameId);
|
||||||
|
const selectedVariant = useGameStore(
|
||||||
|
(state) => state.gameConfig.gameVariantId,
|
||||||
|
);
|
||||||
|
const setSelectedVariant = useGameStore((state) => state.updateGameConfig);
|
||||||
|
const variants = getGameVariants(gameId!);
|
||||||
if (!variants.length) return null;
|
if (!variants.length) return null;
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!selectedVariant)
|
||||||
|
setSelectedVariant({ gameVariantId: variants[0]?.value });
|
||||||
|
}, [selectedVariant]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Combobox
|
<Combobox
|
||||||
className="w-full rounded-2xl"
|
className="bg-background/10 w-full rounded-2xl"
|
||||||
onSelect={() => {}}
|
onSelect={(value) => {
|
||||||
initialValue={variants[0]?.value}
|
if (!value?.length) return;
|
||||||
|
onSelect(value);
|
||||||
|
setSelectedVariant({
|
||||||
|
gameVariantId: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
initialValue={selectedVariant}
|
||||||
data={variants}
|
data={variants}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { IMinigame } from "@/game-engien";
|
import type { IMinigame } from "@/game-engien";
|
||||||
import { gameLibary } from "@/game-engien/games";
|
import { gameLibary } from "@/game-engien/games";
|
||||||
|
import { useSessionPlayerStore } from "@/lib/store/current-player-store";
|
||||||
import { useGameStore } from "@/lib/store/game-store";
|
import { useGameStore } from "@/lib/store/game-store";
|
||||||
import { useLobbyStore } from "@/lib/store/lobby-store";
|
import { useLobbyStore } from "@/lib/store/lobby-store";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@ -8,21 +9,23 @@ import Image from "next/image";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
function GameSelector({ isAdmin }: { isAdmin: boolean }) {
|
function GameSelector() {
|
||||||
const updateGameConfig = api.gameConfig.update.useMutation();
|
const updateGameConfig = api.gameConfig.update.useMutation();
|
||||||
|
const setGameConfig = useGameStore((state) => state.updateGameConfig);
|
||||||
const selectedGame = useGameStore((state) => state.selectedGame);
|
const selectedGame = useGameStore((state) => state.selectedGame);
|
||||||
const setSelectedGame = useGameStore((state) => state.setSelectedGame);
|
const setSelectedGame = useGameStore((state) => state.setSelectedGame);
|
||||||
const gameConfig = useGameStore((state) => state.gameConfig);
|
const gameConfig = useGameStore((state) => state.gameConfig);
|
||||||
const lobbyId = useLobbyStore((state) => state.lobby.id);
|
const lobbyId = useLobbyStore((state) => state.lobby.id);
|
||||||
|
const isLobbyAdmin = useSessionPlayerStore((state) => state.isLobbyAdmin);
|
||||||
|
|
||||||
const handleGameSelect = (game: IMinigame) => {
|
const handleGameSelect = (game: IMinigame) => {
|
||||||
if (!isAdmin) return toast.error("Only admins can change games");
|
if (!isLobbyAdmin) return toast.error("Only admins can change games");
|
||||||
setSelectedGame(game);
|
setSelectedGame(game);
|
||||||
|
const newConfig = { ...gameConfig, gameId: game.id };
|
||||||
|
setGameConfig(newConfig);
|
||||||
updateGameConfig.mutate({
|
updateGameConfig.mutate({
|
||||||
lobbyId,
|
lobbyId,
|
||||||
config: {
|
config: newConfig,
|
||||||
...gameConfig,
|
|
||||||
gameId: game.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -24,13 +24,13 @@ function LobbyPage({ initialLobby }: { initialLobby: Lobby }) {
|
|||||||
const isJoined = sessionPlayer
|
const isJoined = sessionPlayer
|
||||||
? members.find((m) => m.playerId === sessionPlayer.id)
|
? members.find((m) => m.playerId === sessionPlayer.id)
|
||||||
: null;
|
: null;
|
||||||
|
const isAdmin = useSessionPlayerStore((state) => state.isLobbyAdmin);
|
||||||
const isAdmin = isJoined?.role === "admin";
|
|
||||||
const gameConfig = useGameStore((state) => state.gameConfig);
|
const gameConfig = useGameStore((state) => state.gameConfig);
|
||||||
return (
|
return (
|
||||||
|
<div className="size-full h-screen pb-40">
|
||||||
<LobbyProvider initialLobby={initialLobby}>
|
<LobbyProvider initialLobby={initialLobby}>
|
||||||
<div className="grid w-full max-w-4xl grid-cols-3 grid-rows-2 gap-4">
|
<div className="mx-auto grid size-full max-w-4xl grid-cols-5 grid-rows-4 gap-4">
|
||||||
<div className="container-bg col-span-2 w-full space-y-4 p-6">
|
<div className="container-bg col-span-3 row-span-3 size-full space-y-4 overflow-y-auto p-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CopyToClip
|
<CopyToClip
|
||||||
target="invite link"
|
target="invite link"
|
||||||
@ -95,14 +95,16 @@ function LobbyPage({ initialLobby }: { initialLobby: Lobby }) {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="container-bg col-span-1 size-full">
|
<div className="container-bg col-span-2 row-span-3 size-full overflow-y-auto p-6">
|
||||||
<GameConfigurator />
|
<GameConfigurator />
|
||||||
</div>
|
</div>
|
||||||
<div className="container-bg col-span-3 h-max w-full p-4">
|
<div className="container-bg col-span-full h-max w-full p-4">
|
||||||
<GameSelector isAdmin={isAdmin} />
|
<GameSelector />
|
||||||
|
{/* {JSON.stringify(gameConfig)} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LobbyProvider>
|
</LobbyProvider>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { getGame } from "@/game-engien/games";
|
|||||||
import { useSessionPlayerStore } from "@/lib/store/current-player-store";
|
import { useSessionPlayerStore } from "@/lib/store/current-player-store";
|
||||||
import { useGameStore } from "@/lib/store/game-store";
|
import { useGameStore } from "@/lib/store/game-store";
|
||||||
import { useLobbyStore } from "@/lib/store/lobby-store";
|
import { useLobbyStore } from "@/lib/store/lobby-store";
|
||||||
|
import type { GameConfig } from "@/lib/validations/game";
|
||||||
import type { Lobby, LobbyMember } from "@/server/db/schema";
|
import type { Lobby, LobbyMember } from "@/server/db/schema";
|
||||||
import { api } from "@/trpc/react";
|
import { api } from "@/trpc/react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@ -23,14 +24,16 @@ function LobbyProvider({
|
|||||||
const setMembers = useLobbyStore((state) => state.setMembers);
|
const setMembers = useLobbyStore((state) => state.setMembers);
|
||||||
const addMember = useLobbyStore((state) => state.addMember);
|
const addMember = useLobbyStore((state) => state.addMember);
|
||||||
const removeMember = useLobbyStore((state) => state.removeMember);
|
const removeMember = useLobbyStore((state) => state.removeMember);
|
||||||
|
const setGameConfig = useGameStore((state) => state.updateGameConfig);
|
||||||
const setGameConfig = useGameStore((state) => state.setGameConfig);
|
|
||||||
const setSelectedGame = useGameStore((state) => state.setSelectedGame);
|
const setSelectedGame = useGameStore((state) => state.setSelectedGame);
|
||||||
|
const setIsLobbyAdmin = useSessionPlayerStore(
|
||||||
|
(state) => state.setIsLobbyAdmin,
|
||||||
|
);
|
||||||
const selectedGame = useGameStore((state) => state.selectedGame);
|
const selectedGame = useGameStore((state) => state.selectedGame);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const lobbyId = lobby.id ?? "";
|
const lobbyId = lobby.id ?? "";
|
||||||
|
|
||||||
api.lobby.onMemberUpdate.useSubscription(
|
api.lobby.onMemberUpdate.useSubscription(
|
||||||
{ lobbyId },
|
{ lobbyId },
|
||||||
{
|
{
|
||||||
@ -71,6 +74,8 @@ function LobbyProvider({
|
|||||||
{
|
{
|
||||||
onData({ data: config }) {
|
onData({ data: config }) {
|
||||||
if (config) {
|
if (config) {
|
||||||
|
console.log("Game Config Update", config);
|
||||||
|
|
||||||
setGameConfig(config);
|
setGameConfig(config);
|
||||||
if (config.gameId !== selectedGame?.id) {
|
if (config.gameId !== selectedGame?.id) {
|
||||||
const game = getGame(config.gameId!);
|
const game = getGame(config.gameId!);
|
||||||
@ -80,11 +85,17 @@ function LobbyProvider({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const isAdmin =
|
||||||
|
initialLobby?.members?.find((m) => m.playerId === sessionPlayer?.id)
|
||||||
|
?.role === "admin";
|
||||||
|
setIsLobbyAdmin(isAdmin);
|
||||||
|
}, [initialLobby, sessionPlayer, setIsLobbyAdmin]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const setConfigWithGame = () => {
|
const setConfigWithGame = () => {
|
||||||
const initialConfig = initialLobby?.gameConfig?.config;
|
const initialConfig = initialLobby?.gameConfig?.config;
|
||||||
setGameConfig(initialConfig ?? {});
|
setGameConfig(initialConfig ?? ({} as GameConfig));
|
||||||
const game = initialConfig?.gameId?.length
|
const game = initialConfig?.gameId?.length
|
||||||
? getGame(initialConfig?.gameId!)
|
? getGame(initialConfig?.gameId!)
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -93,7 +104,7 @@ function LobbyProvider({
|
|||||||
if (initialLobby?.gameConfig?.config) setConfigWithGame();
|
if (initialLobby?.gameConfig?.config) setConfigWithGame();
|
||||||
resetLobby(initialLobby);
|
resetLobby(initialLobby);
|
||||||
setMembers(initialLobby?.members ?? []);
|
setMembers(initialLobby?.members ?? []);
|
||||||
}, [initialLobby]);
|
}, [initialLobby, setGameConfig, setSelectedGame, getGame]);
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,17 @@
|
|||||||
|
"use client";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import type { Player } from "@/server/db/schema";
|
import type { Player } from "@/server/db/schema";
|
||||||
|
|
||||||
type SessionPlayerStore = {
|
type SessionPlayerStore = {
|
||||||
sessionPlayer?: Player | null;
|
sessionPlayer?: Player | null;
|
||||||
setSessionPlayer: (player?: Player | null) => void;
|
setSessionPlayer: (player?: Player | null) => void;
|
||||||
|
isLobbyAdmin: boolean;
|
||||||
|
setIsLobbyAdmin: (isAdmin: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSessionPlayerStore = create<SessionPlayerStore>((set) => ({
|
export const useSessionPlayerStore = create<SessionPlayerStore>((set) => ({
|
||||||
sessionPlayer: undefined,
|
sessionPlayer: undefined,
|
||||||
setSessionPlayer: (player) => set({ sessionPlayer: player }),
|
setSessionPlayer: (player) => set({ sessionPlayer: player }),
|
||||||
|
isLobbyAdmin: false,
|
||||||
|
setIsLobbyAdmin: (isAdmin) => set({ isLobbyAdmin: isAdmin }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
"use client";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import type { IMinigame } from "@/game-engien";
|
import type { IMinigame } from "@/game-engien";
|
||||||
import type { GameConfig } from "../validations/game";
|
import type { GameConfig } from "../validations/game";
|
||||||
@ -7,11 +8,14 @@ type GameStore = {
|
|||||||
setSelectedGame: (game?: IMinigame) => void;
|
setSelectedGame: (game?: IMinigame) => void;
|
||||||
gameConfig: GameConfig;
|
gameConfig: GameConfig;
|
||||||
setGameConfig: (config: GameConfig) => void;
|
setGameConfig: (config: GameConfig) => void;
|
||||||
|
updateGameConfig: (config: Partial<GameConfig>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGameStore = create<GameStore>((set) => ({
|
export const useGameStore = create<GameStore>((set, get) => ({
|
||||||
selectedGame: undefined,
|
selectedGame: undefined,
|
||||||
setSelectedGame: (game) => set({ selectedGame: game }),
|
setSelectedGame: (game) => set({ selectedGame: game }),
|
||||||
gameConfig: {} as GameConfig,
|
gameConfig: {} as GameConfig,
|
||||||
setGameConfig: (config) => set({ gameConfig: config }),
|
setGameConfig: (config) => set({ gameConfig: config }),
|
||||||
|
updateGameConfig: (config) =>
|
||||||
|
set({ gameConfig: { ...get().gameConfig, ...config } }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import type { Lobby, LobbyMember } from "@/server/db/schema";
|
import type { Lobby, LobbyMember } from "@/server/db/schema";
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,23 @@ export const formatDate = (date: Date) =>
|
|||||||
minute: "numeric",
|
minute: "numeric",
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getBaseUrl() {
|
export const getBaseUrl = () => {
|
||||||
if (typeof window !== "undefined") return window.location.origin;
|
if (typeof window !== "undefined") return window.location.origin;
|
||||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const debounce = <T extends (...args: any[]) => any>(
|
||||||
|
callback: T,
|
||||||
|
waitFor: number,
|
||||||
|
) => {
|
||||||
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
|
return (...args: Parameters<T>): ReturnType<T> => {
|
||||||
|
let result: any;
|
||||||
|
timeout && clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
result = callback(...args);
|
||||||
|
}, waitFor);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export const gameConfigPatchSchema = z.object({
|
export const gameConfigPatchSchema = z.object({
|
||||||
gameId: z.string().optional(),
|
gameId: z.string().optional(),
|
||||||
timeLimit: z.number().default(2).optional(),
|
gameVariantId: z.string().optional(),
|
||||||
|
timeLimit: z.number().min(2).max(120).default(2),
|
||||||
scoreMultiplier: z.number().optional().optional(),
|
scoreMultiplier: z.number().optional().optional(),
|
||||||
allowHints: z.boolean().default(false).optional(),
|
allowHints: z.boolean().default(false).optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user