mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 20:38:17 +03:00
241 lines
6.0 KiB
TypeScript
241 lines
6.0 KiB
TypeScript
import { APIFetchV1 } from "util/api";
|
|
import { FormatDate } from "util/time";
|
|
import Navbar from "components/nav/Navbar";
|
|
import NavItem from "components/nav/NavItem";
|
|
import Divider from "components/util/Divider";
|
|
import ExternalLink from "components/util/ExternalLink";
|
|
import Icon from "components/util/Icon";
|
|
import Muted from "components/util/Muted";
|
|
import { UserContext } from "context/UserContext";
|
|
import { ClientConfig } from "lib/config";
|
|
import React, { useContext, useState } from "react";
|
|
import { Button, Form, Modal } from "react-bootstrap";
|
|
import { PublicUserDocument } from "tachi-common";
|
|
import { SetState } from "types/react";
|
|
import ProfileBadges from "./ProfileBadges";
|
|
import ProfilePicture from "./ProfilePicture";
|
|
|
|
export function UserHeaderBody({ reqUser }: { reqUser: PublicUserDocument }) {
|
|
function ConditionalSocialMediaRender({
|
|
mode,
|
|
href,
|
|
}: {
|
|
mode: "discord" | "twitter" | "github" | "steam" | "youtube" | "twitch";
|
|
href?: string;
|
|
}) {
|
|
if (!reqUser.socialMedia[mode]) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<li>
|
|
<Icon brand type={mode} />{" "}
|
|
<ExternalLink
|
|
className="gentle-link"
|
|
href={href ? href + reqUser.socialMedia[mode] : undefined}
|
|
>
|
|
{reqUser.socialMedia[mode]}
|
|
</ExternalLink>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="col-12 col-lg-3">
|
|
<div className="d-flex justify-content-center mb-3">
|
|
<ProfilePicture user={reqUser} />
|
|
</div>
|
|
<div className="d-flex align-items-center" style={{ flexDirection: "column" }}>
|
|
<ProfileBadges badges={reqUser.badges} />
|
|
</div>
|
|
<div className="d-block d-lg-none">
|
|
<Divider className="mt-4 mb-4" />
|
|
</div>
|
|
</div>
|
|
<div className="col-12 col-lg-6 d-flex justify-content-center">
|
|
<StatusComponent reqUser={reqUser} />
|
|
</div>
|
|
<div className="col-12 col-lg-3 d-flex justify-content-center">
|
|
<ul>
|
|
<ConditionalSocialMediaRender mode="discord" />
|
|
<ConditionalSocialMediaRender href="https://github.com/" mode="github" />
|
|
<ConditionalSocialMediaRender
|
|
href="https://steamcommunity.com/id/"
|
|
mode="steam"
|
|
/>
|
|
<ConditionalSocialMediaRender href="https://twitch.tv/" mode="twitch" />
|
|
<ConditionalSocialMediaRender href="https://twitter.com/" mode="twitter" />
|
|
<ConditionalSocialMediaRender
|
|
href="https://youtube.com/channel/"
|
|
mode="youtube"
|
|
/>
|
|
<li>
|
|
<Muted>UserID: {reqUser.id}</Muted>
|
|
</li>
|
|
<li>
|
|
<Muted>Joined: {FormatDate(reqUser.joinDate)}</Muted>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function UserBottomNav({
|
|
baseUrl,
|
|
reqUser,
|
|
}: {
|
|
baseUrl: string;
|
|
reqUser: PublicUserDocument;
|
|
}) {
|
|
const { user } = useContext(UserContext);
|
|
|
|
const isRequestedUser = !!(user && user.id === reqUser.id);
|
|
|
|
const navItems = [
|
|
<NavItem key="about" to={`${baseUrl}/`}>
|
|
Overview
|
|
</NavItem>,
|
|
<NavItem key="games" to={`${baseUrl}/games`}>
|
|
Games
|
|
</NavItem>,
|
|
];
|
|
|
|
if (isRequestedUser) {
|
|
navItems.push(
|
|
<NavItem key="settings" to={`${baseUrl}/settings`}>
|
|
Profile Settings
|
|
</NavItem>
|
|
);
|
|
navItems.push(
|
|
<NavItem key="integrations" to={`${baseUrl}/integrations`}>
|
|
Service Integrations
|
|
</NavItem>
|
|
);
|
|
navItems.push(
|
|
<NavItem key="imports" to={`${baseUrl}/imports`}>
|
|
Imports
|
|
</NavItem>
|
|
);
|
|
|
|
// If mandates login, assume that we also use invite codes.
|
|
// I'm sure we could set up an elaborate way of doing this by
|
|
// querying the server, but I just don't care.
|
|
if (ClientConfig.MANDATE_LOGIN) {
|
|
navItems.push(
|
|
<NavItem key="invites" to={`${baseUrl}/invites`}>
|
|
Invites
|
|
</NavItem>
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="row align-items-center mb-0">
|
|
<Navbar>{navItems}</Navbar>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function StatusComponent({ reqUser }: { reqUser: PublicUserDocument }) {
|
|
const { user } = useContext(UserContext);
|
|
|
|
const isRequestedUser = user?.id === reqUser.id;
|
|
|
|
const [modalShow, setModalShow] = useState(false);
|
|
|
|
return (
|
|
<div className="row text-center">
|
|
<div className="col-12">
|
|
{reqUser.status ? (
|
|
<span>{reqUser.status}</span>
|
|
) : (
|
|
<Muted>
|
|
{isRequestedUser ? "You have" : `${reqUser.username} has`} no status...
|
|
</Muted>
|
|
)}
|
|
</div>
|
|
<div className="col-12">
|
|
{isRequestedUser && (
|
|
<a href="#" onClick={() => setModalShow(true)}>
|
|
Change Status
|
|
</a>
|
|
)}
|
|
</div>
|
|
{/* <div className="col-12">
|
|
<Muted>Last Seen: {MillisToSince(reqUser.lastSeen)}</Muted>
|
|
</div> */}
|
|
<ChangeStatusModal
|
|
reqUser={reqUser}
|
|
modalShow={modalShow}
|
|
setModalShow={setModalShow}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ChangeStatusModal({
|
|
modalShow,
|
|
setModalShow,
|
|
reqUser,
|
|
}: {
|
|
modalShow: boolean;
|
|
setModalShow: SetState<boolean>;
|
|
reqUser: PublicUserDocument;
|
|
}) {
|
|
const [status, setStatus] = useState(reqUser.status);
|
|
const [innerStatus, setInnerStatus] = useState(reqUser.status ?? "");
|
|
|
|
return (
|
|
<Modal show={modalShow} onHide={() => setModalShow(false)}>
|
|
<Modal.Header closeButton>
|
|
<Modal.Title>Change Status</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
<Form
|
|
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
|
|
APIFetchV1(
|
|
"/users/me",
|
|
{
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
status: innerStatus || null,
|
|
}),
|
|
},
|
|
true,
|
|
true
|
|
).then((r) => {
|
|
if (r.success) {
|
|
setStatus(innerStatus);
|
|
reqUser.status = innerStatus;
|
|
setModalShow(false);
|
|
}
|
|
});
|
|
}}
|
|
>
|
|
<Form.Group>
|
|
<div className="input-group">
|
|
<input
|
|
className="form-control form-control-lg"
|
|
type="text"
|
|
placeholder={status ?? "This score was great!"}
|
|
value={innerStatus}
|
|
onChange={(e) => setInnerStatus(e.target.value)}
|
|
/>
|
|
<div className="input-group-append">
|
|
<Button variant="primary" type="submit">
|
|
Submit
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Form.Group>
|
|
</Form>
|
|
</Modal.Body>
|
|
</Modal>
|
|
);
|
|
}
|