Starting to implement full-stack logic
This commit is contained in:
parent
06f0eacc78
commit
a32b40395e
13 changed files with 211 additions and 57 deletions
|
@ -1,24 +1,20 @@
|
||||||
// import Bluetooth from './Bluetooth'
|
// import Bluetooth from './Bluetooth'
|
||||||
import BorderTiles from "./BorderTiles"
|
|
||||||
import EventBar from "./EventBar"
|
|
||||||
// import FogImages from './FogImages'
|
// import FogImages from './FogImages'
|
||||||
import HitElems from "./HitElems"
|
|
||||||
import Labeling from "./Labeling"
|
import Labeling from "./Labeling"
|
||||||
import Ships from "./Ships"
|
import Ships from "./Ships"
|
||||||
import Targets from "./Targets"
|
|
||||||
import useGameEvent from "@lib/hooks/useGameEvent"
|
import useGameEvent from "@lib/hooks/useGameEvent"
|
||||||
import { CSSProperties } from "react"
|
import { CSSProperties } from "react"
|
||||||
|
|
||||||
function Gamefield() {
|
function Gamefield() {
|
||||||
const count = 12
|
const count = 12
|
||||||
const { pointersProps, targetsProps, tilesProps, hits } = useGameEvent(count)
|
const { BorderTiles, HitElems, Targets, EventBar } = useGameEvent(count)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="gamefield">
|
<div id="gamefield">
|
||||||
{/* <Bluetooth /> */}
|
{/* <Bluetooth /> */}
|
||||||
<div id="game-frame" style={{ "--i": count } as CSSProperties}>
|
<div id="game-frame" style={{ "--i": count } as CSSProperties}>
|
||||||
{/* Bordes */}
|
{/* Bordes */}
|
||||||
<BorderTiles props={tilesProps} />
|
<BorderTiles />
|
||||||
|
|
||||||
{/* Collumn lettes and row numbers */}
|
{/* Collumn lettes and row numbers */}
|
||||||
<Labeling count={count} />
|
<Labeling count={count} />
|
||||||
|
@ -26,14 +22,14 @@ function Gamefield() {
|
||||||
{/* Ships */}
|
{/* Ships */}
|
||||||
<Ships />
|
<Ships />
|
||||||
|
|
||||||
<HitElems hits={hits} />
|
<HitElems />
|
||||||
|
|
||||||
{/* Fog images */}
|
{/* Fog images */}
|
||||||
{/* <FogImages /> */}
|
{/* <FogImages /> */}
|
||||||
<Targets props={pointersProps} />
|
<Targets />
|
||||||
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
|
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
|
||||||
</div>
|
</div>
|
||||||
<EventBar props={targetsProps} />
|
<EventBar />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import { gameContext } from "../../pages/_app"
|
||||||
import Icon from "./Icon"
|
import Icon from "./Icon"
|
||||||
import Player from "./Player"
|
import Player from "./Player"
|
||||||
import { Fragment, useEffect, useState } from "react"
|
import { Fragment, useContext, useEffect, useState } from "react"
|
||||||
|
|
||||||
function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
||||||
|
const [gameProps, setGameProps] = useContext(gameContext)
|
||||||
const [enemy, setEnemy] = useState(false)
|
const [enemy, setEnemy] = useState(false)
|
||||||
const [dots, setDots] = useState(1)
|
const [dots, setDots] = useState(1)
|
||||||
|
|
||||||
|
@ -17,7 +19,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
||||||
<div className="flex items-center justify-between border-b-2 border-slate-900">
|
<div className="flex items-center justify-between border-b-2 border-slate-900">
|
||||||
<Icon src="speech_bubble.png">Chat</Icon>
|
<Icon src="speech_bubble.png">Chat</Icon>
|
||||||
<h1 className="font-farro text-5xl font-medium">
|
<h1 className="font-farro text-5xl font-medium">
|
||||||
Game-PIN: <span className="underline">3169</span>
|
Game-PIN: <span className="underline">{gameProps.pin}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<Icon src="gear.png" onClick={openSettings}>
|
<Icon src="gear.png" onClick={openSettings}>
|
||||||
Settings
|
Settings
|
||||||
|
|
35
leaky-ships/lib/backend/components/decodeToken.ts
Normal file
35
leaky-ships/lib/backend/components/decodeToken.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { rejectionErrorFns } from "../errors"
|
||||||
|
import jwtVerifyCatch from "../jwtVerifyCatch"
|
||||||
|
import logging from "../logging"
|
||||||
|
import type { IdToken, RawToken } from "./createTokenDB"
|
||||||
|
import sendError, { API } from "./sendError"
|
||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
|
||||||
|
async function decodeToken<T>(
|
||||||
|
context: API<T>,
|
||||||
|
rawToken: RawToken,
|
||||||
|
next: (token: IdToken) => void
|
||||||
|
) {
|
||||||
|
const { value, type } = rawToken
|
||||||
|
|
||||||
|
// Verify the token and get the payload
|
||||||
|
let data: string | jwt.JwtPayload
|
||||||
|
try {
|
||||||
|
data = jwt.verify(value, process.env.TOKEN_SECRET as string)
|
||||||
|
} catch (err: any) {
|
||||||
|
// Deal with the problem in more detail
|
||||||
|
logging(jwtVerifyCatch(type, err).message, ["error"])
|
||||||
|
const fallbackData = jwt.decode(value)
|
||||||
|
// Making sure the token data is not a string (because it should be an object)
|
||||||
|
if (typeof fallbackData === "string")
|
||||||
|
return sendError(context, rejectionErrorFns.tokenWasString(type, value))
|
||||||
|
return next({ id: fallbackData?.id, type })
|
||||||
|
}
|
||||||
|
// Making sure the token data is not a string (because it should be an object)
|
||||||
|
if (typeof data === "string")
|
||||||
|
return sendError(context, rejectionErrorFns.tokenWasString(type, value))
|
||||||
|
|
||||||
|
return next({ id: data.id, type })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default decodeToken
|
|
@ -7,7 +7,8 @@ import type { Token } from "@prisma/client"
|
||||||
async function getTokenDB<T>(
|
async function getTokenDB<T>(
|
||||||
context: API<T>,
|
context: API<T>,
|
||||||
token: IdToken,
|
token: IdToken,
|
||||||
next: (tokenDB: Token) => void
|
next: (tokenDB: Token) => void,
|
||||||
|
ignoreChecks?: boolean
|
||||||
) {
|
) {
|
||||||
const { id, type } = token
|
const { id, type } = token
|
||||||
// Find refresh token in DB
|
// Find refresh token in DB
|
||||||
|
@ -18,14 +19,15 @@ async function getTokenDB<T>(
|
||||||
})
|
})
|
||||||
if (!tokenDB) return sendError(context, rejectionErrorFns.tokenNotFound(type))
|
if (!tokenDB) return sendError(context, rejectionErrorFns.tokenNotFound(type))
|
||||||
|
|
||||||
if (tokenDB.used) return sendError(context, rejectionErrors.tokenUsed)
|
if (tokenDB.used && !ignoreChecks)
|
||||||
|
return sendError(context, rejectionErrors.tokenUsed)
|
||||||
|
|
||||||
await prisma.token.update({
|
await prisma.token.update({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
used: true,
|
used: type === "ACCESS",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,32 @@
|
||||||
import createPlayerDB from "./createPlayerDB"
|
import createPlayerDB from "./createPlayerDB"
|
||||||
import createTokenDB, { RawToken } from "./createTokenDB"
|
import createTokenDB, { RawToken } from "./createTokenDB"
|
||||||
import type { API } from "./sendError"
|
import type { API } from "./sendError"
|
||||||
|
import { setCookie } from "cookies-next"
|
||||||
|
|
||||||
async function getTokenFromCookie<T>(
|
async function getTokenFromCookie<T>(
|
||||||
context: API<T>,
|
context: API<T>,
|
||||||
next: (refreshToken: RawToken) => void
|
next: (refreshToken: RawToken) => void
|
||||||
) {
|
) {
|
||||||
const type = "REFRESH"
|
const type = "REFRESH"
|
||||||
const value = context.req.cookies.token
|
const { req, res } = context
|
||||||
|
const value = req.cookies.token
|
||||||
|
|
||||||
// Checking for cookie presens, because it is necessary
|
// Checking for cookie presens, because it is necessary
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return createPlayerDB((player) =>
|
return createPlayerDB((player) =>
|
||||||
createTokenDB(player, type, (newToken) => next(newToken))
|
createTokenDB(player, type, (newToken) => {
|
||||||
|
// Set login cookie
|
||||||
|
setCookie("token", newToken.value, {
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
maxAge: 172800,
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: true,
|
||||||
|
secure: true,
|
||||||
|
path: "/api",
|
||||||
|
})
|
||||||
|
return next(newToken)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return next({ value, type })
|
return next({ value, type })
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
import BorderTiles from "../../components/Gamefield/BorderTiles"
|
||||||
|
import EventBar from "../../components/Gamefield/EventBar"
|
||||||
import type { PointerProps } from "../../components/Gamefield/GamefieldPointer"
|
import type { PointerProps } from "../../components/Gamefield/GamefieldPointer"
|
||||||
|
import HitElems from "../../components/Gamefield/HitElems"
|
||||||
|
import Targets from "../../components/Gamefield/Targets"
|
||||||
import {
|
import {
|
||||||
Hit,
|
Hit,
|
||||||
Mode,
|
Mode,
|
||||||
|
@ -6,6 +10,7 @@ import {
|
||||||
Target,
|
Target,
|
||||||
Position,
|
Position,
|
||||||
} from "../../interfaces/frontend"
|
} from "../../interfaces/frontend"
|
||||||
|
import { gameContext } from "../../pages/_app"
|
||||||
import {
|
import {
|
||||||
hitReducer,
|
hitReducer,
|
||||||
initlialLastLeftTile,
|
initlialLastLeftTile,
|
||||||
|
@ -13,7 +18,7 @@ import {
|
||||||
initlialTargetPreview,
|
initlialTargetPreview,
|
||||||
initlialMouseCursor,
|
initlialMouseCursor,
|
||||||
} from "../utils/helpers"
|
} from "../utils/helpers"
|
||||||
import { useCallback, useEffect, useReducer, useState } from "react"
|
import { useCallback, useContext, useEffect, useReducer, useState } from "react"
|
||||||
|
|
||||||
const modes: Mode[] = [
|
const modes: Mode[] = [
|
||||||
{
|
{
|
||||||
|
@ -28,10 +33,14 @@ const modes: Mode[] = [
|
||||||
pointerGrid: Array.from(Array(1), () => Array.from(Array(3))),
|
pointerGrid: Array.from(Array(1), () => Array.from(Array(3))),
|
||||||
type: "vtorpedo",
|
type: "vtorpedo",
|
||||||
},
|
},
|
||||||
{ pointerGrid: [[{ x: 0, y: 0 }]], type: "missile" },
|
{
|
||||||
|
pointerGrid: Array.from(Array(1), () => Array.from(Array(1))),
|
||||||
|
type: "missile",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function useGameEvent(count: number) {
|
function useGameEvent(count: number) {
|
||||||
|
const [gameProps, setGameProps] = useContext(gameContext)
|
||||||
const [lastLeftTile, setLastLeftTile] =
|
const [lastLeftTile, setLastLeftTile] =
|
||||||
useState<Position>(initlialLastLeftTile)
|
useState<Position>(initlialLastLeftTile)
|
||||||
const [target, setTarget] = useState<Target>(initlialTarget)
|
const [target, setTarget] = useState<Target>(initlialTarget)
|
||||||
|
@ -208,10 +217,16 @@ function useGameEvent(count: number) {
|
||||||
}, [targetPreview.show])
|
}, [targetPreview.show])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tilesProps: { count, settingTarget, setMouseCursor, setLastLeftTile },
|
BorderTiles: () => (
|
||||||
pointersProps: { composeTargetTiles, target, targetPreview },
|
<BorderTiles
|
||||||
targetsProps: { setMode, setTarget },
|
props={{ count, settingTarget, setMouseCursor, setLastLeftTile }}
|
||||||
hits,
|
/>
|
||||||
|
),
|
||||||
|
HitElems: () => <HitElems hits={hits} />,
|
||||||
|
Targets: () => (
|
||||||
|
<Targets props={{ composeTargetTiles, target, targetPreview }} />
|
||||||
|
),
|
||||||
|
EventBar: () => <EventBar props={{ setMode, setTarget }} />,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,8 @@
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"socket.io": "^4.6.1",
|
"socket.io": "^4.6.1",
|
||||||
"socket.io-client": "^4.6.1",
|
"socket.io-client": "^4.6.1",
|
||||||
"typescript": "4.9.4"
|
"typescript": "4.9.4",
|
||||||
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@total-typescript/ts-reset": "^0.3.7",
|
"@total-typescript/ts-reset": "^0.3.7",
|
||||||
|
|
|
@ -3,7 +3,28 @@ import "../styles/globals.css"
|
||||||
import "../styles/grid2.scss"
|
import "../styles/grid2.scss"
|
||||||
import "../styles/grid.scss"
|
import "../styles/grid.scss"
|
||||||
import type { AppProps } from "next/app"
|
import type { AppProps } from "next/app"
|
||||||
|
import { Dispatch, SetStateAction, createContext, useState } from "react"
|
||||||
|
|
||||||
|
interface gameContext {
|
||||||
|
pin?: string
|
||||||
|
game?: {
|
||||||
|
id: ""
|
||||||
|
}
|
||||||
|
enemy?: {
|
||||||
|
id: string
|
||||||
|
username?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const gameContext = createContext<
|
||||||
|
[gameContext, Dispatch<SetStateAction<gameContext>>]
|
||||||
|
>([{}, () => {}])
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
return <Component {...pageProps} />
|
const gameProps = useState<gameContext>({})
|
||||||
|
return (
|
||||||
|
<gameContext.Provider value={gameProps}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</gameContext.Provider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,18 @@ import sendError from "@backend/components/sendError"
|
||||||
import sendResponse from "@backend/components/sendResponse"
|
import sendResponse from "@backend/components/sendResponse"
|
||||||
import getPlayer from "@lib/backend/components/getPlayer"
|
import getPlayer from "@lib/backend/components/getPlayer"
|
||||||
import prisma from "@lib/prisma"
|
import prisma from "@lib/prisma"
|
||||||
import type { Game } from "@prisma/client"
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
interface Data {
|
const returnSchema = z.object({
|
||||||
game: Game
|
game: z.object({
|
||||||
}
|
id: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
running: z.boolean(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
type Data = z.infer<typeof returnSchema>
|
||||||
|
|
||||||
export default async function create(
|
export default async function create(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
|
|
@ -18,19 +18,19 @@ export default async function auth(
|
||||||
const context = { req, res }
|
const context = { req, res }
|
||||||
const type = "ACCESS"
|
const type = "ACCESS"
|
||||||
|
|
||||||
getTokenFromCookie(context, (refreshToken) => {
|
return getTokenFromCookie(context, (refreshToken) =>
|
||||||
checkTokenIsValid(context, refreshToken, (token) => {
|
checkTokenIsValid(context, refreshToken, (token) =>
|
||||||
getTokenDB(context, token, (tokenDB) => {
|
getTokenDB(context, token, (tokenDB) =>
|
||||||
getPlayerByIdDB(context, tokenDB, (player) => {
|
getPlayerByIdDB(context, tokenDB, (player) =>
|
||||||
createTokenDB(player, type, (newToken, newTokenDB) => {
|
createTokenDB(player, type, (newToken, newTokenDB) =>
|
||||||
sendResponse(context, {
|
sendResponse(context, {
|
||||||
message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
|
message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
|
||||||
body: { token: newToken.value },
|
body: { token: newToken.value },
|
||||||
type: ["debug", "infoCyan"],
|
type: ["debug", "infoCyan"],
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
})
|
)
|
||||||
})
|
)
|
||||||
})
|
)
|
||||||
}).catch((err) => sendError(context, err))
|
).catch((err) => sendError(context, err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import checkTokenIsValid from "@backend/components/checkTokenIsValid"
|
|
||||||
import getTokenDB from "@backend/components/getTokenDB"
|
import getTokenDB from "@backend/components/getTokenDB"
|
||||||
import getTokenFromCookie from "@backend/components/getTokenFromCookie"
|
import getTokenFromCookie from "@backend/components/getTokenFromCookie"
|
||||||
import sendError from "@backend/components/sendError"
|
import sendError from "@backend/components/sendError"
|
||||||
import sendResponse from "@backend/components/sendResponse"
|
import sendResponse from "@backend/components/sendResponse"
|
||||||
|
import decodeToken from "@lib/backend/components/decodeToken"
|
||||||
import { deleteCookie } from "cookies-next"
|
import { deleteCookie } from "cookies-next"
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
|
@ -16,18 +16,23 @@ export default async function logout(
|
||||||
) {
|
) {
|
||||||
const context = { req, res }
|
const context = { req, res }
|
||||||
|
|
||||||
return getTokenFromCookie(context, (refreshToken) => {
|
return getTokenFromCookie(context, (refreshToken) =>
|
||||||
checkTokenIsValid(context, refreshToken, (token) => {
|
decodeToken(context, refreshToken, (token) =>
|
||||||
getTokenDB(context, token, (tokenDB) => {
|
getTokenDB(
|
||||||
// Set login cookie
|
context,
|
||||||
deleteCookie("token", { req, res })
|
token,
|
||||||
|
(tokenDB) => {
|
||||||
|
// Set login cookie
|
||||||
|
deleteCookie("token", { req, res })
|
||||||
|
|
||||||
sendResponse(context, {
|
return sendResponse(context, {
|
||||||
message: "User of Token " + tokenDB.id + " logged out.",
|
message: "User of Token " + tokenDB.id + " logged out.",
|
||||||
body: { loggedOut: true },
|
body: { loggedOut: true },
|
||||||
type: ["debug", "infoCyan"],
|
type: ["debug", "infoCyan"],
|
||||||
})
|
})
|
||||||
})
|
},
|
||||||
})
|
true
|
||||||
}).catch((err) => sendError(context, err))
|
)
|
||||||
|
)
|
||||||
|
).catch((err) => sendError(context, err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,23 @@ import { faPlus, faUserPlus } from "@fortawesome/pro-solid-svg-icons"
|
||||||
import { faCirclePlay } from "@fortawesome/pro-thin-svg-icons"
|
import { faCirclePlay } from "@fortawesome/pro-thin-svg-icons"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import getAccessToken from "@lib/frontend/getAccessToken"
|
import getAccessToken from "@lib/frontend/getAccessToken"
|
||||||
|
import { GetServerSideProps } from "next"
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
export default function Home() {
|
interface Props {
|
||||||
|
start: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Home({ start }: Props) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [heWantsToPlay, setHeWantsToPlay] = useState<boolean | null>(false)
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-theme">
|
<div className="h-full bg-theme">
|
||||||
<div className="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly">
|
<div className="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly">
|
||||||
<Logo />
|
<Logo />
|
||||||
<BurgerMenu />
|
<BurgerMenu />
|
||||||
{!heWantsToPlay ? (
|
{!start ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-36 w-64 items-center justify-center rounded-xl border-4 border-black bg-[#2227] sm:h-48 sm:w-96 md:h-72 md:w-[32rem] md:border-[6px] xl:h-[26rem] xl:w-[48rem]">
|
<div className="flex h-36 w-64 items-center justify-center rounded-xl border-4 border-black bg-[#2227] sm:h-48 sm:w-96 md:h-72 md:w-[32rem] md:border-[6px] xl:h-[26rem] xl:w-[48rem]">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
|
@ -27,7 +32,15 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="font-farro rounded-lg border-b-4 border-orange-400 bg-warn px-12 pb-4 pt-5 text-2xl font-bold duration-100 active:border-b-0 active:border-t-4 sm:rounded-xl sm:border-b-[6px] sm:px-14 sm:pb-5 sm:pt-6 sm:text-3xl sm:active:border-t-[6px] md:rounded-2xl md:border-b-8 md:px-20 md:pb-6 md:pt-7 md:text-4xl md:active:border-t-8 xl:px-24 xl:pb-8 xl:pt-10 xl:text-5xl"
|
className="font-farro rounded-lg border-b-4 border-orange-400 bg-warn px-12 pb-4 pt-5 text-2xl font-bold duration-100 active:border-b-0 active:border-t-4 sm:rounded-xl sm:border-b-[6px] sm:px-14 sm:pb-5 sm:pt-6 sm:text-3xl sm:active:border-t-[6px] md:rounded-2xl md:border-b-8 md:px-20 md:pb-6 md:pt-7 md:text-4xl md:active:border-t-8 xl:px-24 xl:pb-8 xl:pt-10 xl:text-5xl"
|
||||||
onClick={() => setTimeout(() => setHeWantsToPlay(true), 200)}
|
onClick={() =>
|
||||||
|
setTimeout(() => {
|
||||||
|
// Navigate to the same page with the `start` query parameter set to `true`
|
||||||
|
router.push({
|
||||||
|
pathname: router.pathname,
|
||||||
|
query: { start: true },
|
||||||
|
})
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
START
|
START
|
||||||
</button>
|
</button>
|
||||||
|
@ -36,7 +49,14 @@ export default function Home() {
|
||||||
<div className="flex flex-col items-center rounded-xl border-4 border-black bg-grayish px-4 py-6 shadow-lg sm:mx-8 sm:p-12 md:w-full">
|
<div className="flex flex-col items-center rounded-xl border-4 border-black bg-grayish px-4 py-6 shadow-lg sm:mx-8 sm:p-12 md:w-full">
|
||||||
<button
|
<button
|
||||||
className="-mt-2 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
|
className="-mt-2 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
|
||||||
onClick={() => setTimeout(() => setHeWantsToPlay(false), 200)}
|
onClick={() =>
|
||||||
|
setTimeout(() => {
|
||||||
|
// Navigate to the same page with the `start` query parameter set to `false`
|
||||||
|
router.push({
|
||||||
|
pathname: router.pathname,
|
||||||
|
})
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faLeftLong} />
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -48,6 +68,25 @@ export default function Home() {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ token }),
|
body: JSON.stringify({ token }),
|
||||||
})
|
})
|
||||||
|
const gameSchema = z.object({
|
||||||
|
game: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
running: z.boolean(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const check = gameSchema.safeParse(game)
|
||||||
|
|
||||||
|
if (check.success) {
|
||||||
|
const gameData = check.data
|
||||||
|
console.log(gameData)
|
||||||
|
} else {
|
||||||
|
console.error(check.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// const warst = result.game
|
||||||
|
|
||||||
router.push("/dev/lobby")
|
router.push("/dev/lobby")
|
||||||
}}
|
}}
|
||||||
|
@ -64,3 +103,14 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
const { start } = context.query
|
||||||
|
|
||||||
|
// Convert the `start` query parameter to a boolean
|
||||||
|
const isStart = start === "true"
|
||||||
|
|
||||||
|
return { props: { start: isStart } }
|
||||||
|
}
|
||||||
|
|
7
leaky-ships/pnpm-lock.yaml
generated
7
leaky-ships/pnpm-lock.yaml
generated
|
@ -73,6 +73,9 @@ dependencies:
|
||||||
typescript:
|
typescript:
|
||||||
specifier: 4.9.4
|
specifier: 4.9.4
|
||||||
version: 4.9.4
|
version: 4.9.4
|
||||||
|
zod:
|
||||||
|
specifier: ^3.21.4
|
||||||
|
version: 3.21.4
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@total-typescript/ts-reset':
|
'@total-typescript/ts-reset':
|
||||||
|
@ -3397,3 +3400,7 @@ packages:
|
||||||
/yocto-queue@0.1.0:
|
/yocto-queue@0.1.0:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
/zod@3.21.4:
|
||||||
|
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||||
|
dev: false
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue