From d2aaacf2fca651e0865424b81083f0e427c3d1c2 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 24 Nov 2021 14:47:23 +0000 Subject: [PATCH] Invite Code Page --- .../pages/dashboard/users/UserInvitesPage.tsx | 140 ++++++++++++++++++ client/src/app/routes/UserRoutes.tsx | 6 + client/src/components/user/UserHeader.tsx | 12 ++ 3 files changed, 158 insertions(+) create mode 100644 client/src/app/pages/dashboard/users/UserInvitesPage.tsx diff --git a/client/src/app/pages/dashboard/users/UserInvitesPage.tsx b/client/src/app/pages/dashboard/users/UserInvitesPage.tsx new file mode 100644 index 000000000..92bddde77 --- /dev/null +++ b/client/src/app/pages/dashboard/users/UserInvitesPage.tsx @@ -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 ; + } + + if (!data || isLoading) { + return ; + } + + const newInvites = data.limit - data.invites; + + return ( + + + +

+ You can make {newInvites} invite code{newInvites !== 1 ? "s" : ""}. +

+

You get new invite codes every month.

+ + By inviting a user, you take partial responsibility for their actions. If + they cause trouble, you may also be in trouble. +
+ Only invite people you trust! +
+ + + {newInvites !== 0 && ( + +
+ +
+ + + )} + +

Your Invites

+ + +
+
+ ); +} + +function InviteList({ reqUser }: { reqUser: PublicUserDocument }) { + const { data, isLoading, error } = useApiQuery<{ + invites: InviteCodeDocument[]; + consumers: PublicUserDocument[]; + }>(`/users/${reqUser.id}/invites`); + + if (error) { + return ; + } + + if (isLoading || !data) { + return ; + } + + const unconsumed = data.invites.filter(e => !e.consumed); + const consumed = data.invites.filter(e => e.consumed); + + return ( +
+

Unused

+ + These codes have not been used by anyone yet! You can send them to anyone to let + them onto the site. + + {unconsumed.length ? ( + unconsumed.map(e => ( +
+ {e.code} +
+ )) + ) : ( +
No Unconsumed Invites!
+ )} +

Users you have Invited

+
+ {consumed.length ? ( + consumed.map(e => ( +
+ Invited{" "} + + {data.consumers.find(u => u.id === e.consumedBy)?.username ?? + "Unknown"} + {" "} + at {FormatTime(e.consumedAt || 0)} +
+ )) + ) : ( +
No-one, yet!
+ )} +
+
+ ); +} diff --git a/client/src/app/routes/UserRoutes.tsx b/client/src/app/routes/UserRoutes.tsx index 8c45380f9..14a6ef43b 100644 --- a/client/src/app/routes/UserRoutes.tsx +++ b/client/src/app/routes/UserRoutes.tsx @@ -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 }) { + + + + + ); } diff --git a/client/src/components/user/UserHeader.tsx b/client/src/components/user/UserHeader.tsx index 8887e8034..1456d008e 100644 --- a/client/src/components/user/UserHeader.tsx +++ b/client/src/components/user/UserHeader.tsx @@ -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 ); + + // 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( + + Invites + + ); + } } return (