From 43b4eaaf8f79d9942b8ad7f7c94e65ec53d5ea1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6ssner?= Date: Fri, 25 Oct 2024 18:08:06 +1100 Subject: [PATCH] fix: improved error handling on sign in on /plaground page --- app/playground/page.tsx | 505 +++++++++++++++++++++------------------ components/ui/dialog.tsx | 122 ++++++++++ convex/users.ts | 8 +- package.json | 1 + 4 files changed, 395 insertions(+), 241 deletions(-) create mode 100644 components/ui/dialog.tsx diff --git a/app/playground/page.tsx b/app/playground/page.tsx index 483c1ec..aa1f15c 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -9,6 +9,7 @@ import { ChevronUp, UploadIcon, } from "lucide-react"; +import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { Map } from "@/components/Map"; import { MapStatus } from "@/components/MapStatus"; @@ -16,9 +17,17 @@ import { ModelSelector } from "@/components/ModelSelector"; import { Visualizer } from "@/components/Visualizer"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { useToast } from "@/components/ui/use-toast"; import { api } from "@/convex/_generated/api"; import { Id } from "@/convex/_generated/dataModel"; +import { SIGN_IN_ERROR_MESSAGE } from "@/convex/users"; import { errorMessage } from "@/lib/utils"; import { ZombieSurvival } from "@/simulators/zombie-survival"; @@ -53,6 +62,7 @@ export default function PlaygroundPage() { const [userPlaying, setUserPlaying] = useState(false); const [userSolution, setUserSolution] = useState([]); const [visualizingUserSolution, setVisualizingUserSolution] = useState(false); + const [openSignInModal, setOpenSignInModal] = useState(false); async function handlePublish() { if (!ZombieSurvival.mapHasZombies(map)) { @@ -73,10 +83,14 @@ export default function PlaygroundPage() { description: "Map submitted successfully!", }); } catch (error) { - toast({ - description: errorMessage(error), - variant: "destructive", - }); + if (errorMessage(error).includes(SIGN_IN_ERROR_MESSAGE)) { + setOpenSignInModal(true); + } else { + toast({ + description: errorMessage(error), + variant: "destructive", + }); + } } finally { setPublishing(false); } @@ -137,18 +151,6 @@ export default function PlaygroundPage() { window.localStorage.setItem(STORAGE_MAP_KEY, JSON.stringify(cleanedMap)); } - function handleReset() { - handleChangeMap([]); - - setSolution(null); - setReasoning(null); - setPublishing(false); - setSimulating(false); - setUserPlaying(false); - setUserSolution([]); - setVisualizingUserSolution(false); - } - function handleUserPlay() { if (!ZombieSurvival.mapHasZombies(map)) { alert("Add player to the map first"); @@ -238,248 +240,275 @@ export default function PlaygroundPage() { const visualizing = solution !== null || visualizingUserSolution; return ( -
-

Playground

- -
- {/* Left side: Grid */} -
- - {!visualizing && !userPlaying && ( -

- Click on the board to place or remove units. Use the buttons - below to switch between unit types. -

- )} - {!visualizing && userPlaying && ( -

- Place a player (P) and blocks (B) on the board to create your - escape route. Click to toggle between empty, player, and block. -

- )} -
- {visualizing && ( - + <> + setOpenSignInModal(open)} + > + + + For this action you need to be signed in! + + In order to submit a map you need to be signed in. + +
+ + + +
+
+
+
+
+

Playground

+ +
+ {/* Left side: Grid */} +
+ + {!visualizing && !userPlaying && ( +

+ Click on the board to place or remove units. Use the buttons + below to switch between unit types. +

)} - {!visualizing && ( -
- -
- {(userPlaying ? userSolution : map).map((row, y) => - row.map((cell, x) => ( -
{ - const newMap = userPlaying - ? [...userSolution] - : [...map]; - if (userPlaying) { - // Count existing players and blocks - const playerCount = newMap - .flat() - .filter((c) => c === "P").length; - const blockCount = newMap - .flat() - .filter((c) => c === "B").length; - - // Toggle logic for play mode - if (cell === " ") { - if (playerCount === 0) { - newMap[y][x] = "P"; - } else if (blockCount < 2) { - newMap[y][x] = "B"; + {!visualizing && userPlaying && ( +

+ Place a player (P) and blocks (B) on the board to create your + escape route. Click to toggle between empty, player, and + block. +

+ )} +
+ {visualizing && ( + + )} + {!visualizing && ( +
+ +
+ {(userPlaying ? userSolution : map).map((row, y) => + row.map((cell, x) => ( +
{ + const newMap = userPlaying + ? [...userSolution] + : [...map]; + if (userPlaying) { + // Count existing players and blocks + const playerCount = newMap + .flat() + .filter((c) => c === "P").length; + const blockCount = newMap + .flat() + .filter((c) => c === "B").length; + + // Toggle logic for play mode + if (cell === " ") { + if (playerCount === 0) { + newMap[y][x] = "P"; + } else if (blockCount < 2) { + newMap[y][x] = "B"; + } + } else if (cell === "P") { + newMap[y][x] = " "; + } else if (cell === "B") { + newMap[y][x] = " "; } - } else if (cell === "P") { - newMap[y][x] = " "; - } else if (cell === "B") { - newMap[y][x] = " "; + userPlaying + ? setUserSolution(newMap) + : handleChangeMap(newMap); + } else { + // Toggle between empty, zombie, and rock for edit mode + if (cell === " ") newMap[y][x] = "Z"; + else if (cell === "Z") newMap[y][x] = "R"; + else newMap[y][x] = " "; } userPlaying ? setUserSolution(newMap) : handleChangeMap(newMap); - } else { - // Toggle between empty, zombie, and rock for edit mode - if (cell === " ") newMap[y][x] = "Z"; - else if (cell === "Z") newMap[y][x] = "R"; - else newMap[y][x] = " "; - } - userPlaying - ? setUserSolution(newMap) - : handleChangeMap(newMap); - }} - /> - )), + }} + /> + )), + )} +
+ + {/* Grid expansion controls */} + {!userPlaying && !visualizing && ( + <> +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ )}
+ )} +
+ +
- {/* Grid expansion controls */} - {!userPlaying && !visualizing && ( - <> -
- - -
-
- - -
-
- - -
-
- - -
- + {/* Right side: Action Panel */} + +
+ {userPlaying || solution !== null ? ( + <> + + {userPlaying && ( + )} -
- )} -
- -
- - {/* Right side: Action Panel */} - -
- {userPlaying || solution !== null ? ( - <> - - {userPlaying && ( + + ) : ( + <> - )} - - ) : ( - <> - - - - )} - - {error !== null &&

{error}

} - {visualizingUserSolution && } - {reasoning !== null && ( -
- -

{reasoning}

-
- )} - - {isAdmin && !userPlaying && solution === null && ( - <> -
-
-

Model (~$0.002)

- + + )} + + {error !== null && ( +

{error}

+ )} + {visualizingUserSolution && } + {reasoning !== null && ( +
+ +

{reasoning}

- - )} -
- + )} + + {isAdmin && !userPlaying && solution === null && ( + <> +
+
+

Model (~$0.002)

+ + +
+ + )} +
+
+
-
+ ); } diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..95b0d38 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { Cross2Icon } from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/convex/users.ts b/convex/users.ts index 30dbeb7..396a284 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -4,8 +4,8 @@ import { customMutation, customQuery, } from "convex-helpers/server/customFunctions"; -import { mutation, query } from "./_generated/server"; import { ConvexError } from "convex/values"; +import { mutation, query } from "./_generated/server"; export const viewer = query({ args: {}, @@ -101,6 +101,8 @@ export const adminMutationBuilder = customMutation( }), ); +export const SIGN_IN_ERROR_MESSAGE = + "You must be signed in to perform this action"; export const authenticatedMutation = customMutation( mutation, @@ -108,11 +110,11 @@ export const authenticatedMutation = customMutation( const userId = await getAuthUserId(ctx); if (userId === null) { - throw new ConvexError("You must be signed in to perform this action"); + throw new ConvexError(SIGN_IN_ERROR_MESSAGE); } return { userId, }; }), -); +); diff --git a/package.json b/package.json index 50b2a2a..670db83 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@google/generative-ai": "^0.21.0", "@icons-pack/react-simple-icons": "^10.1.0", "@mistralai/mistralai": "^1.1.0", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.1.2",