Invite Code Page

This commit is contained in:
zkldi
2021-11-24 14:47:23 +00:00
parent 4564387373
commit d2aaacf2fc
3 changed files with 158 additions and 0 deletions
@@ -0,0 +1,140 @@
import useSetSubheader from "components/layout/header/useSetSubheader";
import Card from "components/layout/page/Card";
import ApiError from "components/util/ApiError";
import Divider from "components/util/Divider";
import Loading from "components/util/Loading";
import Muted from "components/util/Muted";
import useApiQuery from "components/util/query/useApiQuery";
import React from "react";
import { Button, Col, Row } from "react-bootstrap";
import { Link } from "react-router-dom";
import { integer, InviteCodeDocument, PublicUserDocument } from "tachi-common";
import { APIFetchV1 } from "util/api";
import { DelayedPageReload } from "util/misc";
import { FormatTime } from "util/time";
export default function UserInvitesPage({ reqUser }: { reqUser: PublicUserDocument }) {
useSetSubheader(
["Users", reqUser.username, "Invites"],
[reqUser],
`${reqUser.username}'s Invites`
);
const { data, isLoading, error } = useApiQuery<{ invites: integer; limit: integer }>(
`/users/${reqUser.id}/invites/limit`
);
if (error) {
return <ApiError error={error} />;
}
if (!data || isLoading) {
return <Loading />;
}
const newInvites = data.limit - data.invites;
return (
<Card header="Invites" className="col-12 offset-lg-2 col-lg-8">
<Row>
<Col xs={12} className="text-center">
<h4 className="display-4">
You can make <b>{newInvites}</b> invite code{newInvites !== 1 ? "s" : ""}.
</h4>
<h4>You get new invite codes every month.</h4>
<span className="text-danger">
By inviting a user, you take partial responsibility for their actions. If
they cause trouble, you may also be in trouble.
<br />
Only invite people you trust!
</span>
<Divider />
</Col>
{newInvites !== 0 && (
<Col xs={12}>
<div className="d-flex justify-content-center">
<Button
onClick={async () => {
const r = await APIFetchV1(
`/users/${reqUser.id}/invites/create`,
{
method: "POST",
},
true,
true
);
if (r.success) {
DelayedPageReload();
}
}}
className="btn btn-primary"
>
Create new Invite
</Button>
</div>
<Divider />
</Col>
)}
<Col xs={12}>
<h4 className="text-center mb-4">Your Invites</h4>
<InviteList reqUser={reqUser} />
</Col>
</Row>
</Card>
);
}
function InviteList({ reqUser }: { reqUser: PublicUserDocument }) {
const { data, isLoading, error } = useApiQuery<{
invites: InviteCodeDocument[];
consumers: PublicUserDocument[];
}>(`/users/${reqUser.id}/invites`);
if (error) {
return <ApiError error={error} />;
}
if (isLoading || !data) {
return <Loading />;
}
const unconsumed = data.invites.filter(e => !e.consumed);
const consumed = data.invites.filter(e => e.consumed);
return (
<div style={{ fontSize: "1.2rem" }}>
<h3>Unused</h3>
<Muted>
These codes have not been used by anyone yet! You can send them to anyone to let
them onto the site.
</Muted>
{unconsumed.length ? (
unconsumed.map(e => (
<div key={e.code}>
<code>{e.code}</code>
</div>
))
) : (
<div>No Unconsumed Invites!</div>
)}
<h3 className="mt-4">Users you have Invited</h3>
<div style={{ fontSize: "1.1rem" }}>
{consumed.length ? (
consumed.map(e => (
<div key={e.code}>
Invited{" "}
<Link to={`/dashboard/users/${e.consumedBy}`}>
{data.consumers.find(u => u.id === e.consumedBy)?.username ??
"Unknown"}
</Link>{" "}
at {FormatTime(e.consumedAt || 0)}
</div>
))
) : (
<div>No-one, yet!</div>
)}
</div>
</div>
);
}
+6
View File
@@ -7,6 +7,7 @@ import SessionsPage from "app/pages/dashboard/users/games/_game/_playtype/Sessio
import UGPTSettingsPage from "app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage";
import UserGamesPage from "app/pages/dashboard/users/UserGamesPage";
import UserIntegrationsPage from "app/pages/dashboard/users/UserIntegrationsPage";
import UserInvitesPage from "app/pages/dashboard/users/UserInvitesPage";
import UserSettingsPage from "app/pages/dashboard/users/UserSettingsPage";
import { ErrorPage } from "app/pages/ErrorPage";
import RequireAuthAsUserParam from "components/auth/RequireAuthAsUserParam";
@@ -109,6 +110,11 @@ function UserProfileRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
<UserIntegrationsPage reqUser={reqUser} />
</RequireAuthAsUserParam>
</Route>
<Route exact path="/dashboard/users/:userID/invites">
<RequireAuthAsUserParam>
<UserInvitesPage reqUser={reqUser} />
</RequireAuthAsUserParam>
</Route>
</>
);
}
+12
View File
@@ -5,6 +5,7 @@ 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, TachiConfig } from "lib/config";
import React, { useContext, useState } from "react";
import { Button, Form, Modal } from "react-bootstrap";
import { PublicUserDocument } from "tachi-common";
@@ -112,6 +113,17 @@ export function UserBottomNav({
Service Integrations
</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 || true) {
navItems.push(
<NavItem key="invites" to={`${baseUrl}/invites`}>
Invites
</NavItem>
);
}
}
return (