Implemented simple full-stack logic (unfinished)
This commit is contained in:
parent
a32b40395e
commit
2862f94f1c
19 changed files with 481 additions and 189 deletions
|
@ -14,7 +14,7 @@ function EventBar({
|
||||||
{ icon: "burger-menu", text: "Menu" },
|
{ icon: "burger-menu", text: "Menu" },
|
||||||
{ icon: "radar", text: "Radar scan", mode: 0, amount: 1 },
|
{ icon: "radar", text: "Radar scan", mode: 0, amount: 1 },
|
||||||
{ icon: "torpedo", text: "Fire torpedo", mode: 1, amount: 1 },
|
{ icon: "torpedo", text: "Fire torpedo", mode: 1, amount: 1 },
|
||||||
{ icon: "scope", text: "Fire missile", mode: 2 },
|
{ icon: "scope", text: "Fire missile", mode: 3 },
|
||||||
{ icon: "gear", text: "Settings" },
|
{ icon: "gear", text: "Settings" },
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Fragment, useContext, useEffect, useState } from "react"
|
||||||
|
|
||||||
function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
||||||
const [gameProps, setGameProps] = useContext(gameContext)
|
const [gameProps, setGameProps] = useContext(gameContext)
|
||||||
const [enemy, setEnemy] = useState(false)
|
const { enemy } = gameProps
|
||||||
const [dots, setDots] = useState(1)
|
const [dots, setDots] = useState(1)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -28,18 +28,13 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
||||||
<div className="flex items-center justify-around">
|
<div className="flex items-center justify-around">
|
||||||
<Player
|
<Player
|
||||||
src="player_blue.png"
|
src="player_blue.png"
|
||||||
text="Spieler 1 (Du)"
|
text={gameProps.player?.username ?? "Spieler 1 (Du)"}
|
||||||
primary={true}
|
primary={true}
|
||||||
edit={true}
|
edit={true}
|
||||||
/>
|
/>
|
||||||
<p
|
<p className="font-farro m-4 text-6xl font-semibold">VS</p>
|
||||||
className="font-farro m-4 text-6xl font-semibold"
|
|
||||||
onClick={() => setEnemy((e) => !e)}
|
|
||||||
>
|
|
||||||
VS
|
|
||||||
</p>
|
|
||||||
{enemy ? (
|
{enemy ? (
|
||||||
<Player src="player_red.png" text="Spieler 2" />
|
<Player src="player_red.png" text={enemy.username ?? "Spieler 2"} />
|
||||||
) : (
|
) : (
|
||||||
<p className="font-farro w-96 text-center text-5xl font-medium">
|
<p className="font-farro w-96 text-center text-5xl font-medium">
|
||||||
Warte auf Spieler 2 {Array.from(Array(dots), () => ".").join("")}
|
Warte auf Spieler 2 {Array.from(Array(dots), () => ".").join("")}
|
||||||
|
|
|
@ -11,7 +11,7 @@ export interface IdToken {
|
||||||
type: TokenType
|
type: TokenType
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenLifetime = {
|
export const tokenLifetime = {
|
||||||
REFRESH: 172800,
|
REFRESH: 172800,
|
||||||
ACCESS: 15,
|
ACCESS: 15,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { rejectionErrors } from "../errors"
|
|
||||||
import sendError, { API } from "./sendError"
|
import sendError, { API } from "./sendError"
|
||||||
|
|
||||||
async function getPinFromBody<T>(context: API<T>, next: (pin: string) => void) {
|
async function getPinFromBody<T>(context: API<T>, next: (pin: string) => void) {
|
||||||
|
@ -9,7 +8,13 @@ async function getPinFromBody<T>(context: API<T>, next: (pin: string) => void) {
|
||||||
!("pin" in body) ||
|
!("pin" in body) ||
|
||||||
typeof body.pin !== "string"
|
typeof body.pin !== "string"
|
||||||
)
|
)
|
||||||
return sendError(context, rejectionErrors.noUsername)
|
return sendError(context, {
|
||||||
|
rejected: true,
|
||||||
|
message: "No pin in request body!",
|
||||||
|
statusCode: 401,
|
||||||
|
solved: true,
|
||||||
|
type: "warn",
|
||||||
|
})
|
||||||
const { pin } = body
|
const { pin } = body
|
||||||
|
|
||||||
return next(pin)
|
return next(pin)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { rejectionErrorFns, rejectionErrors } from "../errors"
|
||||||
import type { IdToken } from "./createTokenDB"
|
import type { IdToken } from "./createTokenDB"
|
||||||
import sendError, { API } from "./sendError"
|
import sendError, { API } from "./sendError"
|
||||||
import type { Token } from "@prisma/client"
|
import type { Token } from "@prisma/client"
|
||||||
|
import { deleteCookie } from "cookies-next"
|
||||||
|
|
||||||
async function getTokenDB<T>(
|
async function getTokenDB<T>(
|
||||||
context: API<T>,
|
context: API<T>,
|
||||||
|
@ -17,7 +18,10 @@ async function getTokenDB<T>(
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!tokenDB) return sendError(context, rejectionErrorFns.tokenNotFound(type))
|
if (!tokenDB) {
|
||||||
|
deleteCookie("token", { ...context, path: "/api" })
|
||||||
|
return sendError(context, rejectionErrorFns.tokenNotFound(type))
|
||||||
|
}
|
||||||
|
|
||||||
if (tokenDB.used && !ignoreChecks)
|
if (tokenDB.used && !ignoreChecks)
|
||||||
return sendError(context, rejectionErrors.tokenUsed)
|
return sendError(context, rejectionErrors.tokenUsed)
|
||||||
|
|
|
@ -18,6 +18,7 @@ async function getTokenFromBody<T>(
|
||||||
const value = body.token
|
const value = body.token
|
||||||
return next({ value, type })
|
return next({ value, type })
|
||||||
}
|
}
|
||||||
|
console.log(body)
|
||||||
|
|
||||||
return sendError(context, rejectionErrorFns.noToken(type))
|
return sendError(context, rejectionErrorFns.noToken(type))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,24 @@
|
||||||
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"
|
import { Player, Token } from "@prisma/client"
|
||||||
|
|
||||||
async function getTokenFromCookie<T>(
|
async function getTokenFromCookie<T>(
|
||||||
context: API<T>,
|
context: API<T>,
|
||||||
next: (refreshToken: RawToken) => void
|
next: (
|
||||||
|
refreshToken: RawToken,
|
||||||
|
newPlayer?: { player: Player; newToken: RawToken; newTokenDB: Token }
|
||||||
|
) => void
|
||||||
) {
|
) {
|
||||||
const type = "REFRESH"
|
const type = "REFRESH"
|
||||||
const { req, res } = context
|
const value = context.req.cookies.token
|
||||||
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) => {
|
createTokenDB(player, type, (newToken, newTokenDB) =>
|
||||||
// Set login cookie
|
next(newToken, { player, newToken, newTokenDB })
|
||||||
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,15 +1,39 @@
|
||||||
import logging, { Logging } from "../logging"
|
import logging, { Logging } from "../logging"
|
||||||
|
import { RawToken, tokenLifetime } from "./createTokenDB"
|
||||||
import type { API } from "./sendError"
|
import type { API } from "./sendError"
|
||||||
|
import { deleteCookie, setCookie } from "cookies-next"
|
||||||
|
|
||||||
export interface Result<T> {
|
export interface Result<T> {
|
||||||
message: string
|
message: string
|
||||||
statusCode?: number
|
statusCode?: number
|
||||||
body?: T
|
body?: T
|
||||||
type?: Logging[]
|
type?: Logging[]
|
||||||
|
cookie?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function sendResponse<T>(context: API<T>, result: Result<T>) {
|
export default function sendResponse<T>(context: API<T>, result: Result<T>) {
|
||||||
const { req, res } = context
|
const { req, res } = context
|
||||||
|
if (typeof result.cookie === "string") {
|
||||||
|
if (result.cookie) {
|
||||||
|
console.log(1)
|
||||||
|
setCookie("token", result.cookie, {
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
maxAge: tokenLifetime.REFRESH,
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: true,
|
||||||
|
secure: true,
|
||||||
|
path: "/api",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log(2)
|
||||||
|
deleteCookie("token", {
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
path: "/api",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
res.status(result.statusCode ?? 200)
|
res.status(result.statusCode ?? 200)
|
||||||
result.body ? res.json(result.body) : res.end()
|
result.body ? res.json(result.body) : res.end()
|
||||||
logging(result.message, result.type ?? ["debug"], req)
|
logging(result.message, result.type ?? ["debug"], req)
|
||||||
|
|
|
@ -69,6 +69,12 @@ export const rejectionErrors: rejectionErrors = {
|
||||||
solved: true,
|
solved: true,
|
||||||
type: "warn",
|
type: "warn",
|
||||||
},
|
},
|
||||||
|
gameNotFound: {
|
||||||
|
rejected: true,
|
||||||
|
message: "Game not found!",
|
||||||
|
statusCode: 403,
|
||||||
|
solved: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rejectionErrorFns: rejectionErrorFns = {
|
export const rejectionErrorFns: rejectionErrorFns = {
|
||||||
|
|
|
@ -1,18 +1,42 @@
|
||||||
|
import status from "http-status"
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
import { ZodError, z } from "zod"
|
||||||
|
|
||||||
|
const tokenSchema = z.object({
|
||||||
|
token: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function successfulResponse(res: Response) {
|
||||||
|
if (status[`${res.status}_CLASS`] === status.classes.SUCCESSFUL)
|
||||||
|
return res.json()
|
||||||
|
|
||||||
|
const resStatus = status[`${res.status}_CLASS`]
|
||||||
|
if (typeof resStatus !== "string") return
|
||||||
|
|
||||||
|
toast(status.classes[resStatus] + ": " + status[res.status], {
|
||||||
|
position: "top-center",
|
||||||
|
type: "error",
|
||||||
|
theme: "colored",
|
||||||
|
})
|
||||||
|
return Promise.reject()
|
||||||
|
}
|
||||||
|
|
||||||
async function getAccessToken() {
|
async function getAccessToken() {
|
||||||
const response = await fetch("/api/user/auth", {
|
return fetch("/api/user/auth", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
})
|
})
|
||||||
const res = await response.json()
|
.then(successfulResponse)
|
||||||
|
.then((response) => {
|
||||||
|
try {
|
||||||
|
const token = tokenSchema.parse(response)
|
||||||
|
|
||||||
if (
|
return token
|
||||||
typeof res === "object" &&
|
} catch (err: any) {
|
||||||
res &&
|
const error = err as ZodError
|
||||||
"token" in res &&
|
toast(JSON.stringify(error))
|
||||||
typeof res.token === "string"
|
return Promise.reject()
|
||||||
)
|
}
|
||||||
return res.token
|
})
|
||||||
|
|
||||||
throw new Error("Access token not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getAccessToken
|
export default getAccessToken
|
||||||
|
|
|
@ -25,11 +25,14 @@
|
||||||
"cookies-next": "^2.1.1",
|
"cookies-next": "^2.1.1",
|
||||||
"eslint": "8.31.0",
|
"eslint": "8.31.0",
|
||||||
"eslint-config-next": "13.1.1",
|
"eslint-config-next": "13.1.1",
|
||||||
|
"http-status": "^1.6.2",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"next": "13.1.1",
|
"next": "13.1.1",
|
||||||
"prisma": "^4.12.0",
|
"prisma": "^4.12.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-otp-input": "^3.0.0",
|
||||||
|
"react-toastify": "^9.1.2",
|
||||||
"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",
|
||||||
|
|
|
@ -4,11 +4,18 @@ 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"
|
import { Dispatch, SetStateAction, createContext, useState } from "react"
|
||||||
|
import { ToastContainer } from "react-toastify"
|
||||||
|
import "react-toastify/dist/ReactToastify.css"
|
||||||
|
|
||||||
interface gameContext {
|
interface gameContext {
|
||||||
pin?: string
|
pin?: string
|
||||||
game?: {
|
game?: {
|
||||||
id: ""
|
id: string
|
||||||
|
}
|
||||||
|
player?: {
|
||||||
|
id: string
|
||||||
|
username?: string
|
||||||
|
isOwner?: boolean
|
||||||
}
|
}
|
||||||
enemy?: {
|
enemy?: {
|
||||||
id: string
|
id: string
|
||||||
|
@ -25,6 +32,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<gameContext.Provider value={gameProps}>
|
<gameContext.Provider value={gameProps}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
<ToastContainer />
|
||||||
</gameContext.Provider>
|
</gameContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,33 @@ 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 { Game, Gamepin, Player_Game } from "@prisma/client"
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
const returnSchema = z.object({
|
export const createSchema = z.object({
|
||||||
game: z.object({
|
game: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
running: z.boolean(),
|
running: z.boolean(),
|
||||||
}),
|
}),
|
||||||
|
pin: z.string().optional(),
|
||||||
|
player: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
isOwner: z.boolean().optional(),
|
||||||
|
}),
|
||||||
|
enemy: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
isOwner: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
type Data = z.infer<typeof returnSchema>
|
|
||||||
|
type Data = z.infer<typeof createSchema>
|
||||||
|
|
||||||
export default async function create(
|
export default async function create(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
@ -26,16 +41,20 @@ export default async function create(
|
||||||
const pin = Math.floor(Math.random() * 10000)
|
const pin = Math.floor(Math.random() * 10000)
|
||||||
.toString()
|
.toString()
|
||||||
.padStart(4, "0")
|
.padStart(4, "0")
|
||||||
const game = await prisma.game.create({
|
|
||||||
data: {
|
let created = false
|
||||||
pin: {
|
let game:
|
||||||
create: {
|
| (Game & {
|
||||||
pin,
|
pin: Gamepin | null
|
||||||
},
|
players: Player_Game[]
|
||||||
},
|
})
|
||||||
|
| null
|
||||||
|
|
||||||
|
game = await prisma.game.findFirst({
|
||||||
|
where: {
|
||||||
|
running: true,
|
||||||
players: {
|
players: {
|
||||||
create: {
|
some: {
|
||||||
isOwner: true,
|
|
||||||
playerId: player.id,
|
playerId: player.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -45,10 +64,41 @@ export default async function create(
|
||||||
players: true,
|
players: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
if (!game) {
|
||||||
|
created = true
|
||||||
|
game = await prisma.game.create({
|
||||||
|
data: {
|
||||||
|
pin: {
|
||||||
|
create: {
|
||||||
|
pin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
players: {
|
||||||
|
create: {
|
||||||
|
isOwner: true,
|
||||||
|
playerId: player.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
pin: true,
|
||||||
|
players: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return sendResponse(context, {
|
return sendResponse(context, {
|
||||||
message: `Player: ${player.id} created game: ${game.id}`,
|
message: `Player: ${player.id} created game: ${game.id}`,
|
||||||
body: { game },
|
statusCode: created ? 201 : 200,
|
||||||
|
body: {
|
||||||
|
game,
|
||||||
|
pin: game.pin?.pin,
|
||||||
|
player: {
|
||||||
|
id: player.id,
|
||||||
|
username: player.username ?? undefined,
|
||||||
|
isOwner: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
type: ["debug", "infoCyan"],
|
type: ["debug", "infoCyan"],
|
||||||
})
|
})
|
||||||
}).catch((err) => sendError(context, err))
|
}).catch((err) => sendError(context, err))
|
||||||
|
|
|
@ -2,6 +2,7 @@ import sendError from "@backend/components/sendError"
|
||||||
import sendResponse from "@backend/components/sendResponse"
|
import sendResponse from "@backend/components/sendResponse"
|
||||||
import getPinFromBody from "@lib/backend/components/getPinFromBody"
|
import getPinFromBody from "@lib/backend/components/getPinFromBody"
|
||||||
import getPlayer from "@lib/backend/components/getPlayer"
|
import getPlayer from "@lib/backend/components/getPlayer"
|
||||||
|
import { rejectionErrors } from "@lib/backend/errors"
|
||||||
import prisma from "@lib/prisma"
|
import prisma from "@lib/prisma"
|
||||||
import type { Game } from "@prisma/client"
|
import type { Game } from "@prisma/client"
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
@ -18,30 +19,61 @@ export default async function join(
|
||||||
|
|
||||||
return getPinFromBody(context, (pin) =>
|
return getPinFromBody(context, (pin) =>
|
||||||
getPlayer(context, async (player) => {
|
getPlayer(context, async (player) => {
|
||||||
const { game } = await prisma.gamepin.update({
|
try {
|
||||||
where: {
|
const pinDB = await prisma.gamepin.update({
|
||||||
pin,
|
where: {
|
||||||
},
|
pin,
|
||||||
data: {
|
},
|
||||||
game: {
|
data: {
|
||||||
update: {
|
game: {
|
||||||
players: {
|
update: {
|
||||||
create: {
|
players: {
|
||||||
isOwner: false,
|
create: {
|
||||||
playerId: player.id,
|
isOwner: false,
|
||||||
|
playerId: player.id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
include: {
|
||||||
include: { game: true },
|
game: {
|
||||||
})
|
include: {
|
||||||
|
players: {
|
||||||
|
include: {
|
||||||
|
player: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
sendResponse(context, {
|
const enemy = pinDB.game.players.find(
|
||||||
message: `Player: ${player.id} joined game: ${game.id}`,
|
(enemy) => enemy.player.id !== player.id
|
||||||
body: { game },
|
)
|
||||||
type: ["debug", "infoCyan"],
|
return sendResponse(context, {
|
||||||
})
|
message: `Player: ${player.id} joined game: ${pinDB.game.id}`,
|
||||||
|
body: {
|
||||||
|
game: pinDB.game,
|
||||||
|
pin: pinDB.pin,
|
||||||
|
player: {
|
||||||
|
id: player.id,
|
||||||
|
username: player.username ?? undefined,
|
||||||
|
isOwner: true,
|
||||||
|
},
|
||||||
|
enemy: {
|
||||||
|
id: enemy?.player.id,
|
||||||
|
username: enemy?.player.username ?? undefined,
|
||||||
|
isOwner: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: ["debug", "infoCyan"],
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log("HERE".red, err.code, err.meta, err.message)
|
||||||
|
return sendError(context, rejectionErrors.gameNotFound)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
).catch((err) => sendError(context, err))
|
).catch((err) => sendError(context, err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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 { Player, Token } from "@prisma/client"
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
|
@ -18,19 +19,24 @@ export default async function auth(
|
||||||
const context = { req, res }
|
const context = { req, res }
|
||||||
const type = "ACCESS"
|
const type = "ACCESS"
|
||||||
|
|
||||||
return getTokenFromCookie(context, (refreshToken) =>
|
return getTokenFromCookie(context, (refreshToken, newPlayer) => {
|
||||||
checkTokenIsValid(context, refreshToken, (token) =>
|
const next = (player: Player, tokenDB: Token, cookie?: string) =>
|
||||||
getTokenDB(context, token, (tokenDB) =>
|
createTokenDB(player, type, (newToken, newTokenDB) =>
|
||||||
getPlayerByIdDB(context, tokenDB, (player) =>
|
sendResponse(context, {
|
||||||
createTokenDB(player, type, (newToken, newTokenDB) =>
|
message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
|
||||||
sendResponse(context, {
|
body: { token: newToken.value },
|
||||||
message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
|
type: ["debug", "infoCyan"],
|
||||||
body: { token: newToken.value },
|
cookie,
|
||||||
type: ["debug", "infoCyan"],
|
})
|
||||||
})
|
)
|
||||||
)
|
if (!newPlayer) {
|
||||||
|
return checkTokenIsValid(context, refreshToken, (token) =>
|
||||||
|
getTokenDB(context, token, (tokenDB) =>
|
||||||
|
getPlayerByIdDB(context, tokenDB, (player) => next(player, tokenDB))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
).catch((err) => sendError(context, err))
|
const { player, newToken, newTokenDB } = newPlayer
|
||||||
|
return next(player, newTokenDB, newToken.value)
|
||||||
|
}).catch((err) => sendError(context, err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,17 +26,6 @@ export default async function login(
|
||||||
getPlayerByNameDB(context, username, (player) => {
|
getPlayerByNameDB(context, username, (player) => {
|
||||||
checkPasswordIsValid(context, player, password, () => {
|
checkPasswordIsValid(context, player, password, () => {
|
||||||
createTokenDB(player, "REFRESH", (newToken, newTokenDB) => {
|
createTokenDB(player, "REFRESH", (newToken, newTokenDB) => {
|
||||||
// Set login cookie
|
|
||||||
setCookie("token", newToken.value, {
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
maxAge: 172800,
|
|
||||||
httpOnly: true,
|
|
||||||
sameSite: true,
|
|
||||||
secure: true,
|
|
||||||
path: "/api",
|
|
||||||
})
|
|
||||||
|
|
||||||
sendResponse(context, {
|
sendResponse(context, {
|
||||||
message:
|
message:
|
||||||
"User " +
|
"User " +
|
||||||
|
@ -45,6 +34,7 @@ export default async function login(
|
||||||
newTokenDB.id,
|
newTokenDB.id,
|
||||||
body: { loggedIn: true },
|
body: { loggedIn: true },
|
||||||
type: ["debug", "infoCyan"],
|
type: ["debug", "infoCyan"],
|
||||||
|
cookie: newToken.value,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,6 @@ 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 decodeToken from "@lib/backend/components/decodeToken"
|
||||||
import { deleteCookie } from "cookies-next"
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
|
@ -12,7 +11,7 @@ interface Data {
|
||||||
|
|
||||||
export default async function logout(
|
export default async function logout(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<any>
|
res: NextApiResponse<Data>
|
||||||
) {
|
) {
|
||||||
const context = { req, res }
|
const context = { req, res }
|
||||||
|
|
||||||
|
@ -22,13 +21,11 @@ export default async function logout(
|
||||||
context,
|
context,
|
||||||
token,
|
token,
|
||||||
(tokenDB) => {
|
(tokenDB) => {
|
||||||
// Set login cookie
|
|
||||||
deleteCookie("token", { req, res })
|
|
||||||
|
|
||||||
return 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"],
|
||||||
|
cookie: "",
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,104 +1,222 @@
|
||||||
import BurgerMenu from "../../components/BurgerMenu"
|
import BurgerMenu from "../../components/BurgerMenu"
|
||||||
import Logo from "../../components/Logo"
|
import Logo from "../../components/Logo"
|
||||||
import OptionButton from "../../components/OptionButton"
|
import OptionButton from "../../components/OptionButton"
|
||||||
|
import { gameContext } from "../_app"
|
||||||
import { faEye, faLeftLong } from "@fortawesome/pro-regular-svg-icons"
|
import { faEye, faLeftLong } from "@fortawesome/pro-regular-svg-icons"
|
||||||
import { faPlus, faUserPlus } from "@fortawesome/pro-solid-svg-icons"
|
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, {
|
||||||
|
successfulResponse,
|
||||||
|
} from "@lib/frontend/getAccessToken"
|
||||||
import { GetServerSideProps } from "next"
|
import { GetServerSideProps } from "next"
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router"
|
||||||
import { useState } from "react"
|
import { useCallback, useContext, useEffect, useState } from "react"
|
||||||
|
import OtpInput from "react-otp-input"
|
||||||
|
import { toast } from "react-toastify"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
start: boolean
|
q: string | string[] | undefined
|
||||||
|
}
|
||||||
|
function isInputOnlyNumbers(input: string) {
|
||||||
|
return /^\d+$/.test(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Home({ start }: Props) {
|
export default function Home({ q }: Props) {
|
||||||
|
const [otp, setOtp] = useState("")
|
||||||
|
const [gameProps, setGameProps] = useContext(gameContext)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const gameFetch = useCallback(
|
||||||
|
async (pin?: string) => {
|
||||||
|
const createSchema = z.object({
|
||||||
|
game: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
}),
|
||||||
|
pin: z.string().optional(),
|
||||||
|
player: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
isOwner: z.boolean().optional(),
|
||||||
|
}),
|
||||||
|
enemy: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
isOwner: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
const gamePromise = getAccessToken().then((token) => {
|
||||||
|
console.log(otp, { ...token, pin })
|
||||||
|
return fetch("/api/game/" + (!pin ? "create" : "join"), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ ...token, pin: pin }),
|
||||||
|
})
|
||||||
|
.then(successfulResponse)
|
||||||
|
.then((game) => createSchema.parse(game))
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await toast.promise(gamePromise, {
|
||||||
|
pending: {
|
||||||
|
render: "Raum wird " + (!pin ? "erstellt" : "angefragt"),
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
render: "Raum " + (!pin ? "erstellt" : "angefragt") + " 👌",
|
||||||
|
type: "info",
|
||||||
|
theme: "colored",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
render: "Es ist ein Fehler aufgetreten 🤯",
|
||||||
|
type: "error",
|
||||||
|
theme: "colored",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
setGameProps(res)
|
||||||
|
|
||||||
|
await toast.promise(router.push("/dev/lobby"), {
|
||||||
|
pending: {
|
||||||
|
render: "Raum wird beigetreten",
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
render: "Raum begetreten 👌",
|
||||||
|
type: "info",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
render: "Es ist ein Fehler aufgetreten 🤯",
|
||||||
|
type: "error",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[router, setGameProps]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (otp.length !== 4) return
|
||||||
|
if (!isInputOnlyNumbers(otp)) {
|
||||||
|
toast("Der Code darf nur Zahlen beinhalten!", {
|
||||||
|
type: "warning",
|
||||||
|
theme: "dark",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gameFetch(otp)
|
||||||
|
}, [otp])
|
||||||
|
|
||||||
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 />
|
||||||
{!start ? (
|
{(() => {
|
||||||
<>
|
switch (q) {
|
||||||
<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]">
|
case "join":
|
||||||
<FontAwesomeIcon
|
return (
|
||||||
className="text-6xl sm:text-7xl md:text-8xl"
|
<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">
|
||||||
icon={faCirclePlay}
|
<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"
|
||||||
</div>
|
onClick={() =>
|
||||||
<button
|
setTimeout(() => {
|
||||||
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"
|
// Navigate to the same page with the `start` query parameter set to `false`
|
||||||
onClick={() =>
|
router.push({
|
||||||
setTimeout(() => {
|
pathname: router.pathname,
|
||||||
// Navigate to the same page with the `start` query parameter set to `true`
|
query: null,
|
||||||
router.push({
|
})
|
||||||
pathname: router.pathname,
|
}, 200)
|
||||||
query: { start: true },
|
}
|
||||||
})
|
>
|
||||||
}, 200)
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
}
|
</button>
|
||||||
>
|
<div className="flex flex-col items-center gap-6 sm:gap-12">
|
||||||
START
|
<OptionButton icon={faPlus}>Raum erstellen</OptionButton>
|
||||||
</button>
|
<OptionButton
|
||||||
</>
|
action={() => {
|
||||||
) : (
|
router.push({
|
||||||
<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">
|
pathname: router.pathname,
|
||||||
<button
|
query: { q: "join" },
|
||||||
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(() => {
|
icon={faUserPlus}
|
||||||
// Navigate to the same page with the `start` query parameter set to `false`
|
>
|
||||||
router.push({
|
<OtpInput
|
||||||
pathname: router.pathname,
|
containerStyle={{ color: "initial" }}
|
||||||
})
|
value={otp}
|
||||||
}, 200)
|
onChange={setOtp}
|
||||||
}
|
numInputs={4}
|
||||||
>
|
placeholder="0000"
|
||||||
<FontAwesomeIcon icon={faLeftLong} />
|
renderSeparator={<span>-</span>}
|
||||||
</button>
|
renderInput={(props) => <input {...props} />}
|
||||||
<div className="flex flex-col items-center gap-6 sm:gap-12">
|
/>
|
||||||
<OptionButton
|
</OptionButton>
|
||||||
action={async () => {
|
<OptionButton icon={faEye}>Zuschauen</OptionButton>
|
||||||
const token = await getAccessToken()
|
</div>
|
||||||
const game = await fetch("/api/game/create", {
|
</div>
|
||||||
method: "POST",
|
)
|
||||||
body: JSON.stringify({ token }),
|
case "start":
|
||||||
})
|
return (
|
||||||
const gameSchema = z.object({
|
<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">
|
||||||
game: z.object({
|
<button
|
||||||
id: z.string(),
|
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"
|
||||||
createdAt: z.date(),
|
onClick={() =>
|
||||||
updatedAt: z.date(),
|
setTimeout(() => {
|
||||||
running: z.boolean(),
|
// Navigate to the same page with the `start` query parameter set to `false`
|
||||||
}),
|
router.push({
|
||||||
})
|
pathname: router.pathname,
|
||||||
|
query: null,
|
||||||
const check = gameSchema.safeParse(game)
|
})
|
||||||
|
}, 200)
|
||||||
if (check.success) {
|
}
|
||||||
const gameData = check.data
|
>
|
||||||
console.log(gameData)
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
} else {
|
</button>
|
||||||
console.error(check.error)
|
<div className="flex flex-col items-center gap-6 sm:gap-12">
|
||||||
}
|
<OptionButton action={() => gameFetch()} icon={faPlus}>
|
||||||
|
Raum erstellen
|
||||||
// const warst = result.game
|
</OptionButton>
|
||||||
|
<OptionButton
|
||||||
router.push("/dev/lobby")
|
action={() => {
|
||||||
}}
|
router.push({
|
||||||
icon={faPlus}
|
pathname: router.pathname,
|
||||||
>
|
query: { q: "join" },
|
||||||
Raum erstellen
|
})
|
||||||
</OptionButton>
|
}}
|
||||||
<OptionButton icon={faUserPlus}>Raum beitreten</OptionButton>
|
icon={faUserPlus}
|
||||||
<OptionButton icon={faEye}>Zuschauen</OptionButton>
|
>
|
||||||
</div>
|
Raum beitreten
|
||||||
</div>
|
</OptionButton>
|
||||||
)}
|
<OptionButton icon={faEye}>Zuschauen</OptionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<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
|
||||||
|
className="text-6xl sm:text-7xl md:text-8xl"
|
||||||
|
icon={faCirclePlay}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<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"
|
||||||
|
onClick={() =>
|
||||||
|
setTimeout(() => {
|
||||||
|
// Navigate to the same page with the `start` query parameter set to `true`
|
||||||
|
router.push({
|
||||||
|
pathname: router.pathname,
|
||||||
|
query: { q: "start" },
|
||||||
|
})
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
START
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -107,10 +225,7 @@ export default function Home({ start }: Props) {
|
||||||
export const getServerSideProps: GetServerSideProps<Props> = async (
|
export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
context
|
context
|
||||||
) => {
|
) => {
|
||||||
const { start } = context.query
|
const { q } = context.query
|
||||||
|
|
||||||
// Convert the `start` query parameter to a boolean
|
return { props: { q: q ? q : "" } }
|
||||||
const isStart = start === "true"
|
|
||||||
|
|
||||||
return { props: { start: isStart } }
|
|
||||||
}
|
}
|
||||||
|
|
40
leaky-ships/pnpm-lock.yaml
generated
40
leaky-ships/pnpm-lock.yaml
generated
|
@ -49,6 +49,9 @@ dependencies:
|
||||||
eslint-config-next:
|
eslint-config-next:
|
||||||
specifier: 13.1.1
|
specifier: 13.1.1
|
||||||
version: 13.1.1(eslint@8.31.0)(typescript@4.9.4)
|
version: 13.1.1(eslint@8.31.0)(typescript@4.9.4)
|
||||||
|
http-status:
|
||||||
|
specifier: ^1.6.2
|
||||||
|
version: 1.6.2
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.0.0
|
version: 9.0.0
|
||||||
|
@ -64,6 +67,12 @@ dependencies:
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
react-otp-input:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
react-toastify:
|
||||||
|
specifier: ^9.1.2
|
||||||
|
version: 9.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||||
socket.io:
|
socket.io:
|
||||||
specifier: ^4.6.1
|
specifier: ^4.6.1
|
||||||
version: 4.6.1
|
version: 4.6.1
|
||||||
|
@ -1010,6 +1019,11 @@ packages:
|
||||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/clsx@1.2.1:
|
||||||
|
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color-convert@1.9.3:
|
/color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1920,6 +1934,11 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.1
|
function-bind: 1.1.1
|
||||||
|
|
||||||
|
/http-status@1.6.2:
|
||||||
|
resolution: {integrity: sha512-oUExvfNckrpTpDazph7kNG8sQi5au3BeTo0idaZFXEhTaJKu7GNJCLHI0rYY2wljm548MSTM+Ljj/c6anqu2zQ==}
|
||||||
|
engines: {node: '>= 0.4.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/https-proxy-agent@5.0.1:
|
/https-proxy-agent@5.0.1:
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
@ -2785,6 +2804,27 @@ packages:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-otp-input@3.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-TgTE3PbHhu4VxsAA9JUACzhtxwuZ8OSle9kiwK0Ne7fCv7wOXTtIRWQWPoNlfOZ/sxGsXjMexqwrQdB9yy0qEQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.6 || ^17.0.0 || ^18.0.0'
|
||||||
|
react-dom: '>=16.8.6 || ^17.0.0 || ^18.0.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/react-toastify@9.1.2(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-PBfzXO5jMGEtdYR5jxrORlNZZe/EuOkwvwKijMatsZZm8IZwLj01YvobeJYNjFcA6uy6CVrx2fzL9GWbhWPTDA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16'
|
||||||
|
react-dom: '>=16'
|
||||||
|
dependencies:
|
||||||
|
clsx: 1.2.1
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react@18.2.0:
|
/react@18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue