From 57d5eff47fce68d9bb2ee9722bf40fac2bcf03f8 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 19:10:40 +0100 Subject: [PATCH 1/8] refactor connect page --- components/Connect/SelectedDeck.tsx | 83 +++++++++ components/Connect/index.tsx | 180 ++++++++++++++++++ components/Connect/useCreateLobby.ts | 0 pages/connect.tsx | 265 +-------------------------- 4 files changed, 266 insertions(+), 262 deletions(-) create mode 100644 components/Connect/SelectedDeck.tsx create mode 100644 components/Connect/index.tsx create mode 100644 components/Connect/useCreateLobby.ts diff --git a/components/Connect/SelectedDeck.tsx b/components/Connect/SelectedDeck.tsx new file mode 100644 index 0000000..f5e7e2e --- /dev/null +++ b/components/Connect/SelectedDeck.tsx @@ -0,0 +1,83 @@ +import { Flex, Tag, Text } from "@chakra-ui/react"; + +import { useLocalDeckStorage } from "@/lib/hooks/useLocalStorage"; +import Link from "next/link"; + +export const SelectedDeckContainer = () => { + const { starredDeck } = useLocalDeckStorage(); + return ( + + {starredDeck === undefined ? ( + <> + + + + + + + + + + No deck selected!
Star a deck from your bag +
+ + ) : ( + <> + + + {starredDeck.name.substring(0, 2)} + + + + {starredDeck.name} + + + )} +
+ ); +}; diff --git a/components/Connect/index.tsx b/components/Connect/index.tsx new file mode 100644 index 0000000..c66ef9e --- /dev/null +++ b/components/Connect/index.tsx @@ -0,0 +1,180 @@ +import axios from "axios"; +import { + Box, + Button, + Flex, + HStack, + Input, + Spinner, + Tag, + Text, + VStack, + useDisclosure, +} from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import { useEffect, useRef, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { + useLocalDeckStorage, + useLocalServerStorage, +} from "@/lib/hooks/useLocalStorage"; +import { SettingsModal } from "@/components/Settings/settings.modal"; +import { toast } from "react-hot-toast"; +import { useLoadRouterDeck } from "@/lib/hooks"; +import { Navbar } from "@/components/Navbar"; +import { SelectedDeckContainer } from "./SelectedDeck"; + +export const ConnectPage = () => { + const router = useRouter(); + const nameRef = useRef(null); + const gidRef = useRef(null); + const { isOpen, onClose, onOpen } = useDisclosure(); + + useLoadRouterDeck(); + + // the useQuery loading props not working on repeat visits + const [loading, setLoading] = useState(false); + + const { starredDeck } = useLocalDeckStorage(); + const { activeServer, setActiveServer, serverList } = useLocalServerStorage(); + + // This serverURL should come from somewhere else. + const serverURL = new URL(activeServer); + + // We need to create the lobby before a websocket can be made. + // We do this on the connect, as an error from a GET request is easier + // to handle than an error from a websocket. So make sure the server is + // responding before we go to a connected state. + const { refetch } = useQuery( + ["create-lobby"], + async () => { + if (!gidRef?.current?.value) return; + const createLobbyURL = new URL( + `/lobby/${gidRef.current.value}`, + serverURL, + ); + try { + const result = await axios.get(createLobbyURL.toString()); + return result.data; + } catch (err) { + console.error(err); + throw err; + } + }, + { + // We manually call refetch. + enabled: false, + refetchOnWindowFocus: false, + onSuccess: () => { + // If the lobby was created, we can move to the game state and + // connect to the websocket. + if (!nameRef?.current || !gidRef?.current) return; + router.push( + "/game?name=" + + nameRef.current.value + + "&gid=" + + gidRef.current.value, + ); + + // HACK: the loading props are borked, so manually setting and resetting state + setTimeout(() => { + setLoading(false); + toast.success( + `Successfully connected to GameServer: \n ${activeServer}`, + ); + }, 10000); + }, + onError: () => { + setLoading(false); + onOpen(); + toast.error(`Failed to connect to GameServer: \n ${activeServer}`); + }, + }, + ); + + useEffect(() => { + router.prefetch("/game"); + }, [router]); + + return ( + + + + + + + Connect + + + + + + + + + + + Active GameServer URL: + {activeServer} + + + ); +}; diff --git a/components/Connect/useCreateLobby.ts b/components/Connect/useCreateLobby.ts new file mode 100644 index 0000000..e69de29 diff --git a/pages/connect.tsx b/pages/connect.tsx index 040b954..ab47a69 100644 --- a/pages/connect.tsx +++ b/pages/connect.tsx @@ -1,264 +1,5 @@ -import axios from "axios"; -import { - Box, - Button, - Flex, - HStack, - Input, - Spinner, - Tag, - Text, - VStack, - useDisclosure, -} from "@chakra-ui/react"; -import { useRouter } from "next/router"; -import { useEffect, useRef, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { - useLocalDeckStorage, - useLocalServerStorage, -} from "@/lib/hooks/useLocalStorage"; -import Link from "next/link"; -import { SettingsModal } from "@/components/Settings/settings.modal"; -import { toast } from "react-hot-toast"; -import { useLoadRouterDeck } from "@/lib/hooks"; -import { Navbar } from "@/components/Navbar"; +import { ConnectPage } from "@/components/Connect"; -const ConnectToGamePage = () => { +export default function ConnectToGamePage() { return ; -}; -export default ConnectToGamePage; - -const ConnectPage = () => { - const router = useRouter(); - const nameRef = useRef(null); - const gidRef = useRef(null); - const { isOpen, onClose, onOpen } = useDisclosure(); - - useLoadRouterDeck(); - - // the useQuery loading props not working on repeat visits - const [loading, setLoading] = useState(false); - - const { starredDeck } = useLocalDeckStorage(); - const { activeServer, setActiveServer, serverList } = useLocalServerStorage(); - - // This serverURL should come from somewhere else. - const serverURL = new URL(activeServer); - - // We need to create the lobby before a websocket can be made. - // We do this on the connect, as an error from a GET request is easier - // to handle than an error from a websocket. So make sure the server is - // responding before we go to a connected state. - const { refetch } = useQuery( - ["create-lobby"], - async () => { - if (!gidRef?.current?.value) return; - const createLobbyURL = new URL( - `/lobby/${gidRef.current.value}`, - serverURL, - ); - try { - const result = await axios.get(createLobbyURL.toString()); - return result.data; - } catch (err) { - console.error(err); - throw err; - } - }, - { - // We manually call refetch. - enabled: false, - refetchOnWindowFocus: false, - onSuccess: () => { - // If the lobby was created, we can move to the game state and - // connect to the websocket. - if (!nameRef?.current || !gidRef?.current) return; - router.push( - "/game?name=" + - nameRef.current.value + - "&gid=" + - gidRef.current.value, - ); - - // HACK: the loading props are borked, so manually setting and resetting state - setTimeout(() => { - setLoading(false); - toast.success( - `Successfully connected to GameServer: \n ${activeServer}`, - ); - }, 10000); - }, - onError: () => { - setLoading(false); - onOpen(); - toast.error(`Failed to connect to GameServer: \n ${activeServer}`); - }, - }, - ); - - useEffect(() => { - router.prefetch("/game"); - }, [router]); - - return ( - - - - - - - Connect - - - - - - - - - - - Active GameServer URL: - {activeServer} - - - ); -}; - -const SelectedDeckContainer = () => { - const { starredDeck } = useLocalDeckStorage(); - return ( - - {starredDeck === undefined ? ( - <> - - - - + - - - - - No deck selected!
Star a deck from your bag -
- - ) : ( - <> - - - {starredDeck.name.substring(0, 2)} - - - - {starredDeck.name} - - - )} -
- ); -}; +} From 8998f8835afe38346a8e4bbc450f8cc1920dcffe Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 19:26:13 +0100 Subject: [PATCH 2/8] move the create lobby to its own hook --- components/Connect/index.tsx | 76 ++++----------------------- components/Connect/useCreateLobby.ts | 78 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 65 deletions(-) diff --git a/components/Connect/index.tsx b/components/Connect/index.tsx index c66ef9e..feb6758 100644 --- a/components/Connect/index.tsx +++ b/components/Connect/index.tsx @@ -1,4 +1,3 @@ -import axios from "axios"; import { Box, Button, @@ -9,88 +8,35 @@ import { Tag, Text, VStack, - useDisclosure, } from "@chakra-ui/react"; import { useRouter } from "next/router"; -import { useEffect, useRef, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; +import { useEffect, useRef } from "react"; import { useLocalDeckStorage, useLocalServerStorage, } from "@/lib/hooks/useLocalStorage"; import { SettingsModal } from "@/components/Settings/settings.modal"; -import { toast } from "react-hot-toast"; import { useLoadRouterDeck } from "@/lib/hooks"; import { Navbar } from "@/components/Navbar"; import { SelectedDeckContainer } from "./SelectedDeck"; +import { useCreateLobby } from "./useCreateLobby"; export const ConnectPage = () => { const router = useRouter(); const nameRef = useRef(null); const gidRef = useRef(null); - const { isOpen, onClose, onOpen } = useDisclosure(); + // uses router props to load new decks useLoadRouterDeck(); - // the useQuery loading props not working on repeat visits - const [loading, setLoading] = useState(false); - const { starredDeck } = useLocalDeckStorage(); const { activeServer, setActiveServer, serverList } = useLocalServerStorage(); - // This serverURL should come from somewhere else. - const serverURL = new URL(activeServer); - - // We need to create the lobby before a websocket can be made. - // We do this on the connect, as an error from a GET request is easier - // to handle than an error from a websocket. So make sure the server is - // responding before we go to a connected state. - const { refetch } = useQuery( - ["create-lobby"], - async () => { - if (!gidRef?.current?.value) return; - const createLobbyURL = new URL( - `/lobby/${gidRef.current.value}`, - serverURL, - ); - try { - const result = await axios.get(createLobbyURL.toString()); - return result.data; - } catch (err) { - console.error(err); - throw err; - } - }, - { - // We manually call refetch. - enabled: false, - refetchOnWindowFocus: false, - onSuccess: () => { - // If the lobby was created, we can move to the game state and - // connect to the websocket. - if (!nameRef?.current || !gidRef?.current) return; - router.push( - "/game?name=" + - nameRef.current.value + - "&gid=" + - gidRef.current.value, - ); - - // HACK: the loading props are borked, so manually setting and resetting state - setTimeout(() => { - setLoading(false); - toast.success( - `Successfully connected to GameServer: \n ${activeServer}`, - ); - }, 10000); - }, - onError: () => { - setLoading(false); - onOpen(); - toast.error(`Failed to connect to GameServer: \n ${activeServer}`); - }, - }, - ); + // create the lobby and connect to it + const { refetch, loading, setLoading, disclosure } = useCreateLobby({ + gidRef, + nameRef, + }); useEffect(() => { router.prefetch("/game"); @@ -109,8 +55,8 @@ export const ConnectPage = () => { position="relative" > @@ -169,7 +115,7 @@ export const ConnectPage = () => { bottom="0.5rem" flexDir={"column"} alignItems="center" - onClick={onOpen} + onClick={disclosure.onOpen} cursor="pointer" > Active GameServer URL: diff --git a/components/Connect/useCreateLobby.ts b/components/Connect/useCreateLobby.ts index e69de29..a6efef7 100644 --- a/components/Connect/useCreateLobby.ts +++ b/components/Connect/useCreateLobby.ts @@ -0,0 +1,78 @@ +import axios from "axios"; +import { useLocalServerStorage } from "@/lib/hooks"; +import { useQuery } from "@tanstack/react-query"; +import { RefObject, useState } from "react"; +import { useRouter } from "next/router"; +import { toast } from "react-hot-toast"; +import { useDisclosure } from "@chakra-ui/react"; + +interface Props { + gidRef: RefObject; + nameRef: RefObject; +} + +export function useCreateLobby({ gidRef, nameRef }: Props) { + const router = useRouter(); + const { activeServer } = useLocalServerStorage(); + const serverURL = new URL(activeServer); + + // the useQuery loading props not working on repeat visits + const [loading, setLoading] = useState(false); + + const disclosure = useDisclosure(); + + const { refetch } = useQuery( + ["create-lobby"], + async () => { + if (!gidRef?.current?.value) return; + const createLobbyURL = new URL( + `/lobby/${gidRef.current.value}`, + serverURL, + ); + try { + const result = await axios.get(createLobbyURL.toString()); + return result.data; + } catch (err) { + console.error(err); + throw err; + } + }, + { + // We manually call refetch. + enabled: false, + refetchOnWindowFocus: false, + onSuccess: () => { + // If the lobby was created, we can move to the game state and + // connect to the websocket. + if (!nameRef?.current || !gidRef?.current) return; + router.push( + "/game?name=" + + nameRef.current.value + + "&gid=" + + gidRef.current.value, + ); + + // HACK: the loading props are borked, so manually setting and resetting state + setTimeout(() => { + setLoading(false); + toast.success( + `Successfully connected to GameServer: \n ${activeServer}`, + ); + }, 10000); + }, + onError: () => { + // if there's an error connecting, prompt the Gameserver Settings modal so they can input a new server url + setLoading(false); + disclosure.onOpen(); + toast.error(`Failed to connect to GameServer: \n ${activeServer}`); + }, + }, + ); + + return { + refetch, + loading, + setLoading, + disclosure, + }; +} From 71bcc289799366ba712eae08bf0147073613ee24 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 19:44:39 +0100 Subject: [PATCH 3/8] refactor --- components/Connect/index.tsx | 52 +++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/components/Connect/index.tsx b/components/Connect/index.tsx index feb6758..38c8b3c 100644 --- a/components/Connect/index.tsx +++ b/components/Connect/index.tsx @@ -20,6 +20,7 @@ import { useLoadRouterDeck } from "@/lib/hooks"; import { Navbar } from "@/components/Navbar"; import { SelectedDeckContainer } from "./SelectedDeck"; import { useCreateLobby } from "./useCreateLobby"; +import styled from "@emotion/styled"; export const ConnectPage = () => { const router = useRouter(); @@ -43,17 +44,7 @@ export const ConnectPage = () => { }, [router]); return ( - + { - + Connect @@ -109,7 +89,7 @@ export const ConnectPage = () => { Connect to Game - + { Active GameServer URL: {activeServer} - + ); }; + +const Wrapper = styled(Flex)` + height: 100svh; + background-color: slategray; + background-position: center; + background-size: cover; + justify-content: center; + align-items: center; + position: center; +`; + +const ConnectContainer = styled(Flex)` + flex-direction: column; + height: 50%; + width: 95%; + max-width: 600px; + background-color: rgba(255, 255, 255, 0.9); + border-radius: 1rem; + padding: 2rem; + justify-content: space-evenly; + align-items: center; +`; From 3bb5d11e424fab2368b2b7ef39abc4b701f2b5e5 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 19:58:13 +0100 Subject: [PATCH 4/8] update the card back --- components/Connect/SelectedDeck.tsx | 19 ++++++++++++------- components/Connect/index.tsx | 4 +++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/components/Connect/SelectedDeck.tsx b/components/Connect/SelectedDeck.tsx index f5e7e2e..7bef7c8 100644 --- a/components/Connect/SelectedDeck.tsx +++ b/components/Connect/SelectedDeck.tsx @@ -43,20 +43,25 @@ export const SelectedDeckContainer = () => { ) : ( <> { - Connect + + Connect + From b6042eaf8a8d47f0231bf15e9a428ac8453f8fd9 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 22:26:30 +0100 Subject: [PATCH 5/8] sharable deck link --- components/Connect/index.tsx | 106 ++++++++++++++++++++++++-------- lib/hooks/useCopyToClipboard.ts | 29 +++++++++ 2 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 lib/hooks/useCopyToClipboard.ts diff --git a/components/Connect/index.tsx b/components/Connect/index.tsx index 1767c53..dc137ce 100644 --- a/components/Connect/index.tsx +++ b/components/Connect/index.tsx @@ -2,15 +2,18 @@ import { Box, Button, Flex, + FormLabel, + Grid, HStack, Input, + Select, Spinner, Tag, Text, VStack, } from "@chakra-ui/react"; import { useRouter } from "next/router"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useLocalDeckStorage, useLocalServerStorage, @@ -21,16 +24,21 @@ import { Navbar } from "@/components/Navbar"; import { SelectedDeckContainer } from "./SelectedDeck"; import { useCreateLobby } from "./useCreateLobby"; import styled from "@emotion/styled"; +import { PlusSquareIcon } from "@chakra-ui/icons"; +import { useCopyToClipboard } from "@/lib/hooks/useCopyToClipboard"; +import { toast } from "react-hot-toast"; export const ConnectPage = () => { const router = useRouter(); const nameRef = useRef(null); const gidRef = useRef(null); + const [sharedDeckId, setSharedDeckId] = useState(); + // uses router props to load new decks useLoadRouterDeck(); - const { starredDeck } = useLocalDeckStorage(); + const { starredDeck, decks } = useLocalDeckStorage(); const { activeServer, setActiveServer, serverList } = useLocalServerStorage(); // create the lobby and connect to it @@ -39,6 +47,8 @@ export const ConnectPage = () => { nameRef, }); + const [_, copy] = useCopyToClipboard(); + useEffect(() => { router.prefetch("/game"); }, [router]); @@ -54,12 +64,17 @@ export const ConnectPage = () => { - - Connect + + Connect to a Lobby - - + + {sharedDeckId === undefined &&
} + { - - - + + + {sharedDeckId === undefined && ( + setSharedDeckId("")} + fontSize="2rem" + alignSelf="center" + justifySelf="center" + /> + )} + + {sharedDeckId !== undefined && ( + + Share a link with the deck: + + + + )} + Promise; + +export function useCopyToClipboard(): [CopiedValue, CopyFn] { + const [copiedText, setCopiedText] = useState(null); + + const copy: CopyFn = useCallback(async (text) => { + if (!navigator?.clipboard) { + console.warn("Clipboard not supported"); + return false; + } + + // Try to save to clipboard then save it in the state if worked + try { + await navigator.clipboard.writeText(text); + setCopiedText(text); + return true; + } catch (error) { + console.warn("Copy failed", error); + setCopiedText(null); + return false; + } + }, []); + + return [copiedText, copy]; +} From 55fc6df800d25cb5c868dc712ddc6e645fb5fb81 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 22:35:08 +0100 Subject: [PATCH 6/8] reload page to see new deck --- lib/hooks/useUnmatchedDeck.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/hooks/useUnmatchedDeck.ts b/lib/hooks/useUnmatchedDeck.ts index c143e2d..5556b54 100644 --- a/lib/hooks/useUnmatchedDeck.ts +++ b/lib/hooks/useUnmatchedDeck.ts @@ -51,11 +51,11 @@ export const useUnmatchedDeck = () => { * and manipulates localstorage * */ export const useLoadRouterDeck = () => { - const { query } = useRouter(); + const { query, reload } = useRouter(); const deckId = query.deckId as string | undefined; const { data, setDeckId } = useUnmatchedDeck(); - const { decks, pushDeck, setStar, star } = useLocalDeckStorage(); + const { decks, pushDeck, setStar } = useLocalDeckStorage(); useEffect(() => { if (!deckId) return; @@ -85,11 +85,11 @@ export const useLoadRouterDeck = () => { useEffect(() => { if (!data) return; if (!deckId) return; - console.log({ data, deckId }); // once api data is available, push the deck to local storage and star it pushDeck(data); setStar(data.id); - toast.success("Refresh the page if you do not see your new deck"); + toast.success("Success! Refreshing page to load new deck"); + reload(); }, [data]); }; From 3f9c982ed12386a333044b253f299be51683dd89 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 22:55:13 +0100 Subject: [PATCH 7/8] reload automatically to get the starred deck --- lib/hooks/useUnmatchedDeck.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/hooks/useUnmatchedDeck.ts b/lib/hooks/useUnmatchedDeck.ts index 5556b54..85d905a 100644 --- a/lib/hooks/useUnmatchedDeck.ts +++ b/lib/hooks/useUnmatchedDeck.ts @@ -72,6 +72,7 @@ export const useLoadRouterDeck = () => { if (localDeck?.version_id !== deckId) { toast.success("Refresh the page if you do not see your new deck"); + reload() } // star the local deck if (localDeck) setStar(localDeck.id); From 74595c97e41c5a2c777a7d7e47b66e601d692a02 Mon Sep 17 00:00:00 2001 From: jollygrin Date: Tue, 26 Mar 2024 22:55:35 +0100 Subject: [PATCH 8/8] add ability to share lobby with a preloaded deck --- components/Connect/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/Connect/index.tsx b/components/Connect/index.tsx index dc137ce..e9bbd30 100644 --- a/components/Connect/index.tsx +++ b/components/Connect/index.tsx @@ -33,6 +33,7 @@ export const ConnectPage = () => { const nameRef = useRef(null); const gidRef = useRef(null); + const [lobby, setLobby] = useState(gidRef?.current?.value ?? ""); const [sharedDeckId, setSharedDeckId] = useState(); // uses router props to load new decks @@ -84,6 +85,10 @@ export const ConnectPage = () => { { + setLobby(e.target.value); + if (e.target.value === "") setSharedDeckId(undefined); + }} placeholder="lobby name" bg="white" /> @@ -108,10 +113,16 @@ export const ConnectPage = () => { {sharedDeckId === undefined && ( setSharedDeckId("")} fontSize="2rem" alignSelf="center" justifySelf="center" + cursor="pointer" + _hover={{ + transform: "scale(1.2)", + }} /> )}