add basic profile modal
This commit is contained in:
parent
169c19b8e2
commit
9e2e0556c4
|
@ -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 = () => {
|
|||
<KeybindModal />
|
||||
<AuthErrors />
|
||||
|
||||
<ProfileModal />
|
||||
|
||||
<ToastContainer position="top-left" />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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<IUser>();
|
||||
|
||||
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 (
|
||||
<Modal isOpen={!!profile} onClose={() => setProfile()} placement="center">
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<ModalHeader className="flex flex-col gap-1">Profile</ModalHeader>
|
||||
<ModalBody>
|
||||
{user ? <UserCard user={user} /> : <>Loading...</>}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onPress={onClose}>Close</Button>
|
||||
</ModalFooter>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -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 (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-row gap-2">
|
||||
|
@ -101,7 +105,9 @@ export const UserCard = ({ user }: { user: IUser }) => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button size="sm">View Profile</Button>
|
||||
<Button size="sm" onPress={openProfile}>
|
||||
View Profile
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<React.SetStateAction<IMapOverlay>>;
|
||||
|
||||
profile?: string; // sub
|
||||
setProfile: (v?: string) => void;
|
||||
|
||||
hasAdmin: boolean;
|
||||
}
|
||||
|
||||
|
@ -93,6 +98,8 @@ export const AppContext = ({ children }: PropsWithChildren) => {
|
|||
loading: false,
|
||||
});
|
||||
|
||||
const [profile, setProfile] = useState<string>();
|
||||
|
||||
const [hasAdmin, setHasAdmin] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -195,6 +202,8 @@ export const AppContext = ({ children }: PropsWithChildren) => {
|
|||
setBlankOverlay,
|
||||
heatmapOverlay,
|
||||
setHeatmapOverlay,
|
||||
profile,
|
||||
setProfile,
|
||||
}}
|
||||
>
|
||||
{!config && (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { toast } from "react-toastify";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const api = async <T = unknown, Error = string>(
|
||||
endpoint: string,
|
||||
|
@ -36,3 +38,10 @@ export const api = async <T = unknown, Error = string>(
|
|||
export type EnforceObjectType<T> = <V extends { [k: string]: T }>(
|
||||
v: V
|
||||
) => { [k in keyof V]: T };
|
||||
|
||||
export const handleError = (api_response: Awaited<ReturnType<typeof api>>) => {
|
||||
toast.error(
|
||||
`Error: [${api_response.status}] ` +
|
||||
("error" in api_response.data ? api_response.data.error : "Unknown Error")
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue