Skip to content

Commit

Permalink
feat: prompt submission sound and settings
Browse files Browse the repository at this point in the history
  • Loading branch information
agrattan0820 committed Sep 25, 2023
1 parent cd6ed14 commit 0c158ad
Show file tree
Hide file tree
Showing 18 changed files with 128 additions and 37 deletions.
1 change: 1 addition & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.3",
"typescript": "5.2.2",
"use-sound": "^4.0.1",
"xstate": "^4.38.2"
},
"devDependencies": {
Expand Down
Binary file added apps/client/public/sounds/confirmation.ogg
Binary file not shown.
Binary file added apps/client/public/sounds/error.ogg
Binary file not shown.
5 changes: 4 additions & 1 deletion apps/client/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Analytics } from "@vercel/analytics/react";
import "./globals.css";
import AuthProvider from "@ai/components/auth-provider";
import { cn } from "@ai/utils/cn";
import SoundProvider from "@ai/utils/sound-provider";

const spaceMono = Space_Mono({
subsets: ["latin"],
Expand Down Expand Up @@ -36,7 +37,9 @@ export default function RootLayout({
)}
>
<Toaster containerStyle={{ textAlign: "center" }} />
<AuthProvider>{children}</AuthProvider>
<SoundProvider>
<AuthProvider>{children}</AuthProvider>
</SoundProvider>
<Analytics />
</body>
</html>
Expand Down
10 changes: 3 additions & 7 deletions apps/client/src/app/room/[code]/game/[gameId]/game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import {
} from "@ai/components/game/game-machine";
import { SocketContext } from "@ai/utils/socket-provider";
import { cn } from "@ai/utils/cn";
import { Session } from "next-auth";
import type { Session } from "next-auth";
import Menu from "@ai/components/menu";
import useIsMounted from "@ai/utils/hooks/use-is-mounted";

// ! ----------> TYPES <----------

Expand All @@ -39,7 +40,7 @@ export default function Game({ gameInfo, session }: GameProps) {
const router = useRouter();

// Wait until the client mounts to avoid hydration errors
const [isMounted, setIsMounted] = useState(false);
const isMounted = useIsMounted();

// Id of the user who is the current host of the game
const [hostId, setHostId] = useState<string | null>(gameInfo.hostId);
Expand Down Expand Up @@ -192,11 +193,6 @@ export default function Game({ gameInfo, session }: GameProps) {
session,
]);

// Avoid hydration issues by ensuring app has mounted
useEffect(() => {
setIsMounted(true);
}, []);

return (
<main
className={cn(
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/app/room/[code]/lobby.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { motion } from "framer-motion";
import { useCallback, useContext, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { Session } from "next-auth";
import type { Session } from "next-auth";
import type { User } from "database";

import InviteLink from "./invite-link";
Expand Down
11 changes: 4 additions & 7 deletions apps/client/src/app/room/[code]/start-game.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"use client";

import { useEffect, useState } from "react";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { FiCheckSquare, FiPlusSquare, FiPlay } from "react-icons/fi";
import { Session } from "next-auth";
import type { Session } from "next-auth";
import type { User } from "database";

import Button, { SecondaryButton } from "@ai/components/button";
import Ellipsis from "@ai/components/ellipsis";
import useLinkShare from "@ai/utils/hooks/use-link-share";
import useIsMounted from "@ai/utils/hooks/use-is-mounted";

const StartGame = ({
players,
Expand All @@ -27,7 +28,7 @@ const StartGame = ({
roomIsFull: boolean;
session: Session;
}) => {
const [isMounted, setIsMounted] = useState(false);
const isMounted = useIsMounted();

const { copying, setCopying, onClick } = useLinkShare({
title: "Join My Artificial Unintelligence Room",
Expand All @@ -44,10 +45,6 @@ const StartGame = ({
}
}, [copying, setCopying]);

useEffect(() => {
setIsMounted(true);
}, []);

return (
<>
<div className="mt-8 flex flex-col items-center justify-center gap-x-2 gap-y-4 sm:flex-row">
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/app/room/[code]/user-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { AnimatePresence, motion } from "framer-motion";

import UserCard from "@ai/components/user-card";
import { Session } from "next-auth";
import type { Session } from "next-auth";
import type { User } from "database";

const UserList = ({
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/components/game/game-machine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
assign,
createMachine,
} from "xstate";
import { Session } from "next-auth";
import type { Session } from "next-auth";

import ConnectToMainframe from "./connect-to-mainframe";
import ConnectionEstablished from "./connection-established";
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/components/game/prompt-submitted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { motion } from "framer-motion";
import { FiCheck } from "react-icons/fi";
import { Session } from "next-auth";
import type { Session } from "next-auth";

import { cn } from "@ai/utils/cn";
import { GameInfo } from "@ai/utils/queries";
Expand Down
20 changes: 15 additions & 5 deletions apps/client/src/components/game/prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { AnimatePresence, motion } from "framer-motion";
import { EventFrom, StateFrom } from "xstate";
import toast from "react-hot-toast";
import { FiHelpCircle, FiX } from "react-icons/fi";
import useSound from "use-sound";
import type { Session } from "next-auth";

import Button, { SecondaryButton } from "@ai/components/button";
import Ellipsis from "@ai/components/ellipsis";
Expand All @@ -23,7 +25,7 @@ import {
} from "@ai/utils/queries";
import { gameMachine } from "./game-machine";
import { SocketContext } from "@ai/utils/socket-provider";
import { Session } from "next-auth";
import { SoundContext } from "@ai/utils/sound-provider";

interface FormElementsType extends HTMLFormControlsCollection {
prompt: HTMLInputElement;
Expand All @@ -46,6 +48,8 @@ const Prompt = ({
}) => {
const socket = useContext(SocketContext);
const dialogRef = useRef<HTMLDialogElement>(null);
const { soundEnabled } = useContext(SoundContext);
const [play] = useSound("/sounds/confirmation.ogg", { soundEnabled });

const gameId = gameInfo.game.id;
const userId = session.user.id;
Expand Down Expand Up @@ -172,6 +176,8 @@ const Prompt = ({
return;
}

play();

if (stage === "FIRST") {
setCurrQuestionNumGenerations(0);
setStage("SECOND");
Expand Down Expand Up @@ -200,15 +206,19 @@ const Prompt = ({
<motion.div layout="position" className="max-w-2xl">
<div className="relative mb-14">
<AnimatePresence>
<motion.h2
<motion.div
layout="position"
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 10, opacity: 0 }}
className="text-lg md:text-2xl"
>
{currQuestion ? currQuestion.text : null}
</motion.h2>
<p className="mb-4 text-sm">
Question {stage === "FIRST" ? 1 : 2}/2
</p>
<h2 className="text-lg md:text-2xl">
{currQuestion ? currQuestion.text : null}
</h2>
</motion.div>
</AnimatePresence>
</div>
<ImageChoice
Expand Down
29 changes: 25 additions & 4 deletions apps/client/src/components/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Session } from "next-auth";
import type { Session } from "next-auth";
import { signOut } from "next-auth/react";
import { useContext, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
Expand All @@ -9,7 +9,14 @@ import * as Sentry from "@sentry/nextjs";
import useClickAway from "@ai/utils/hooks/use-click-away";
import { SocketContext } from "@ai/utils/socket-provider";

import { FiSettings, FiLogOut, FiX } from "react-icons/fi";
import {
FiSettings,
FiLogOut,
FiX,
FiVolume2,
FiVolumeX,
} from "react-icons/fi";
import { useStickyState } from "@ai/utils/hooks/use-sticky-state";

type MenuProps = {
session: Session;
Expand All @@ -19,12 +26,17 @@ type MenuProps = {
const Menu = ({ session, roomCode }: MenuProps) => {
const [showMenu, setShowMenu] = useState(false);
const socket = useContext(SocketContext);
const [soundEnabled, setSoundEnabled] = useStickyState(true, "soundEnabled");

const menuRef = useRef<HTMLElement>(null);
useClickAway(menuRef, () => {
setShowMenu(false);
});

const handleToggleSound = () => {
setSoundEnabled(!soundEnabled);
};

const handleSignOutAndLeave = () => {
if (roomCode) {
socket.emit("leaveRoom", {
Expand Down Expand Up @@ -58,18 +70,27 @@ const Menu = ({ session, roomCode }: MenuProps) => {
{showMenu && (
<motion.ul
id="main-menu"
className="mt-4 rounded-md border border-gray-300 bg-slate-900 p-4"
className="mt-4 flex flex-col gap-4 rounded-md border border-gray-300 bg-slate-900 p-4"
initial={{ scale: 0.9, opacity: 0 }}
animate={{
scale: 1,
opacity: 1,
}}
exit={{ scale: 0.9, opacity: 0 }}
>
<li>
<button
onClick={handleToggleSound}
className="flex items-center gap-4 text-sm focus-within:underline hover:underline md:text-base"
>
{soundEnabled ? "Mute" : "Enable"} Sound{" "}
{soundEnabled ? <FiVolumeX /> : <FiVolume2 />}
</button>
</li>
<li>
<button
onClick={handleSignOutAndLeave}
className="flex items-center gap-2 text-sm hover:underline focus:underline md:text-base"
className="flex w-full items-center justify-between gap-4 text-sm focus-within:underline hover:underline md:text-base"
>
Sign Out{roomCode && " and Leave Game"} <FiLogOut />
</button>
Expand Down
11 changes: 4 additions & 7 deletions apps/client/src/components/sign-in-form.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { FormEvent, useEffect, useState } from "react";
import { Session } from "next-auth";
import { FormEvent, useState } from "react";
import type { Session } from "next-auth";
import { signIn } from "next-auth/react";
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
Expand All @@ -11,6 +11,7 @@ import Button, { LinkSecondaryButton } from "./button";
import { RoomInfo } from "@ai/utils/queries";
import Input from "./input";
import Ellipsis from "./ellipsis";
import useIsMounted from "@ai/utils/hooks/use-is-mounted";

interface FormElementsType extends HTMLFormControlsCollection {
nickname: HTMLInputElement;
Expand All @@ -37,11 +38,7 @@ type SignInFormProps =
const SignInForm = ({ session, room, submitLabel, type }: SignInFormProps) => {
const [loading, setLoading] = useState(false);
const router = useRouter();
const [isMounted, setIsMounted] = useState(false);

useEffect(() => {
setIsMounted(true);
}, []);
const isMounted = useIsMounted();

const onSubmit = async (e: FormEvent<NicknameFormType>) => {
e.preventDefault();
Expand Down
25 changes: 25 additions & 0 deletions apps/client/src/utils/hooks/use-sticky-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import { useEffect, useState } from "react";
import useIsMounted from "./use-is-mounted";

export function useStickyState<T>(defaultValue: T, key: string) {
const isMounted = useIsMounted();

const [value, setValue] = useState<T>(defaultValue);

useEffect(() => {
if (isMounted) {
const stickyValue = window.localStorage.getItem(key);
setValue(stickyValue ? JSON.parse(stickyValue) : defaultValue);
}
}, [defaultValue, isMounted, key]);

useEffect(() => {
if (isMounted) {
window.localStorage.setItem(key, JSON.stringify(value));
}
}, [key, value]);

return [value, setValue] as const;
}
1 change: 0 additions & 1 deletion apps/client/src/utils/server-actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import "server-only";

import type { Session } from "next-auth";
import { db, games, usersToRooms, usersToGames, rooms } from "database";
import { sql, eq, and, isNull, desc, gt } from "drizzle-orm";
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/utils/socket-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import toast from "react-hot-toast";

import { socket } from "./socket";
import { useParams } from "next/navigation";
import { Session } from "next-auth";
import type { Session } from "next-auth";

export const SocketContext = createContext(socket);

Expand Down
26 changes: 26 additions & 0 deletions apps/client/src/utils/sound-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { Dispatch, SetStateAction, createContext } from "react";
import { useStickyState } from "./hooks/use-sticky-state";

export const SoundContext = createContext<{
soundEnabled: boolean;
setSoundEnabled: Dispatch<SetStateAction<boolean>>;
}>({
soundEnabled: true,
setSoundEnabled: () => {},
});

export default function SoundProvider({
children,
}: {
children: React.ReactNode;
}) {
const [soundEnabled, setSoundEnabled] = useStickyState(true, "soundEnabled");

return (
<SoundContext.Provider value={{ soundEnabled, setSoundEnabled }}>
{children}
</SoundContext.Provider>
);
}
Loading

0 comments on commit 0c158ad

Please sign in to comment.