From 9e2e0556c403902dc67d0799306acde7270dabe0 Mon Sep 17 00:00:00 2001 From: Grant Date: Tue, 11 Jun 2024 13:53:24 -0600 Subject: [PATCH] add basic profile modal --- packages/client/src/components/App.tsx | 3 ++ .../src/components/Profile/ProfileModal.tsx | 50 +++++++++++++++++++ .../src/components/Profile/UserCard.tsx | 14 ++++-- packages/client/src/contexts/AppContext.tsx | 11 +++- packages/client/src/lib/utils.ts | 9 ++++ packages/server/src/api/client.ts | 19 +++++++ 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 packages/client/src/components/Profile/ProfileModal.tsx diff --git a/packages/client/src/components/App.tsx b/packages/client/src/components/App.tsx index 340031d..26a2551 100644 --- a/packages/client/src/components/App.tsx +++ b/packages/client/src/components/App.tsx @@ -14,6 +14,7 @@ import { AuthErrors } from "./AuthErrors"; import "../lib/keybinds"; import { PixelWhoisSidebar } from "./PixelWhoisSidebar"; import { KeybindModal } from "./KeybindModal"; +import { ProfileModal } from "./Profile/ProfileModal"; const Chat = lazy(() => import("./Chat/Chat")); @@ -142,6 +143,8 @@ const AppInner = () => { + + ); diff --git a/packages/client/src/components/Profile/ProfileModal.tsx b/packages/client/src/components/Profile/ProfileModal.tsx new file mode 100644 index 0000000..c2b17e6 --- /dev/null +++ b/packages/client/src/components/Profile/ProfileModal.tsx @@ -0,0 +1,50 @@ +import { + Button, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, +} from "@nextui-org/react"; +import { useAppContext } from "../../contexts/AppContext"; +import { useEffect, useState } from "react"; +import { IUser, UserCard } from "./UserCard"; +import { api, handleError } from "../../lib/utils"; + +export const ProfileModal = () => { + const { profile, setProfile } = useAppContext(); + const [user, setUser] = useState(); + + useEffect(() => { + if (!profile) { + setUser(undefined); + return; + } + + api<{ user: IUser }>("/api/user/" + profile).then(({ status, data }) => { + if (status === 200 && data.success) { + setUser(data.user); + } else { + handleError({ status, data }); + } + }); + }, [profile]); + + return ( + setProfile()} placement="center"> + + {(onClose) => ( + <> + Profile + + {user ? : <>Loading...} + + + + + + )} + + + ); +}; diff --git a/packages/client/src/components/Profile/UserCard.tsx b/packages/client/src/components/Profile/UserCard.tsx index d580f9e..a26cb99 100644 --- a/packages/client/src/components/Profile/UserCard.tsx +++ b/packages/client/src/components/Profile/UserCard.tsx @@ -1,4 +1,4 @@ -import { faMessage, faWarning, faX } from "@fortawesome/free-solid-svg-icons"; +import { faMessage, faWarning } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, Link, Spinner } from "@nextui-org/react"; import { ClientConfig } from "@sc07-canvas/lib/src/net"; @@ -6,7 +6,7 @@ import { MouseEvent, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useAppContext } from "../../contexts/AppContext"; -interface IUser { +export interface IUser { sub: string; display_name?: string; picture_url?: string; @@ -25,7 +25,7 @@ const getMatrixLink = (user: IUser, config: ClientConfig) => { * @returns */ export const UserCard = ({ user }: { user: IUser }) => { - const { config } = useAppContext(); + const { config, setProfile } = useAppContext(); const [messageStatus, setMessageStatus] = useState< "loading" | "no_account" | "has_account" | "error" >("loading"); @@ -68,6 +68,10 @@ export const UserCard = ({ user }: { user: IUser }) => { } }; + const openProfile = () => { + setProfile(user.sub); + }; + return (
@@ -101,7 +105,9 @@ export const UserCard = ({ user }: { user: IUser }) => { )}
- + ); }; diff --git a/packages/client/src/contexts/AppContext.tsx b/packages/client/src/contexts/AppContext.tsx index a87a465..5fd3a3f 100644 --- a/packages/client/src/contexts/AppContext.tsx +++ b/packages/client/src/contexts/AppContext.tsx @@ -13,15 +13,17 @@ import { api } from "../lib/utils"; interface IAppContext { config?: ClientConfig; user?: AuthSession; + connected: boolean; + canvasPosition?: ICanvasPosition; setCanvasPosition: (v: ICanvasPosition) => void; cursorPosition?: IPosition; setCursorPosition: (v?: IPosition) => void; pixels: { available: number }; undo?: { available: true; expireAt: number }; + loadChat: boolean; setLoadChat: (v: boolean) => void; - connected: boolean; settingsSidebar: boolean; setSettingsSidebar: (v: boolean) => void; @@ -35,6 +37,9 @@ interface IAppContext { heatmapOverlay: IMapOverlay; setHeatmapOverlay: React.Dispatch>; + profile?: string; // sub + setProfile: (v?: string) => void; + hasAdmin: boolean; } @@ -93,6 +98,8 @@ export const AppContext = ({ children }: PropsWithChildren) => { loading: false, }); + const [profile, setProfile] = useState(); + const [hasAdmin, setHasAdmin] = useState(false); useEffect(() => { @@ -195,6 +202,8 @@ export const AppContext = ({ children }: PropsWithChildren) => { setBlankOverlay, heatmapOverlay, setHeatmapOverlay, + profile, + setProfile, }} > {!config && ( diff --git a/packages/client/src/lib/utils.ts b/packages/client/src/lib/utils.ts index 1d89258..d3e72da 100644 --- a/packages/client/src/lib/utils.ts +++ b/packages/client/src/lib/utils.ts @@ -1,3 +1,5 @@ +import { toast } from "react-toastify"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const api = async ( endpoint: string, @@ -36,3 +38,10 @@ export const api = async ( export type EnforceObjectType = ( v: V ) => { [k in keyof V]: T }; + +export const handleError = (api_response: Awaited>) => { + toast.error( + `Error: [${api_response.status}] ` + + ("error" in api_response.data ? api_response.data.error : "Unknown Error") + ); +}; diff --git a/packages/server/src/api/client.ts b/packages/server/src/api/client.ts index 02acfd4..1495c1f 100644 --- a/packages/server/src/api/client.ts +++ b/packages/server/src/api/client.ts @@ -244,4 +244,23 @@ app.get("/heatmap", async (req, res) => { res.json({ success: true, heatmap }); }); +app.get("/user/:sub", async (req, res) => { + const user = await prisma.user.findFirst({ where: { sub: req.params.sub } }); + if (!user) { + return res.status(404).json({ success: false, error: "unknown_user" }); + } + + res.json({ + success: true, + user: { + sub: user.sub, + display_name: user.display_name, + picture_url: user.picture_url, + profile_url: user.profile_url, + isAdmin: user.isAdmin, + isModerator: user.isModerator, + }, + }); +}); + export default app;