mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-28 01:48:11 +03:00
fix: remove almost all window.reloads()
This commit is contained in:
@@ -14,9 +14,10 @@ import SelectLinkButton from "#components/util/SelectLinkButton";
|
||||
import { TachiConfig } from "#lib/config";
|
||||
import { type SetState } from "#types/react";
|
||||
import { APIFetchV1 } from "#util/api";
|
||||
import { allPermissions, DelayedPageReload } from "#util/misc";
|
||||
import { allPermissions } from "#util/misc";
|
||||
import React, { useEffect, useReducer, useState } from "react";
|
||||
import { Alert, Button, Col, Form, Modal, Row } from "react-bootstrap";
|
||||
import { useQueryClient } from "react-query";
|
||||
import { Link, Route, Switch } from "react-router-dom";
|
||||
import {
|
||||
type APIPermissions,
|
||||
@@ -167,7 +168,7 @@ function CreateNewOAuthClient({ setClients }: { setClients: SetState<TachiAPICli
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const res = await APIFetchV1(
|
||||
const res = await APIFetchV1<TachiAPIClientDocument>(
|
||||
"/clients/create",
|
||||
{
|
||||
method: "POST",
|
||||
@@ -188,7 +189,8 @@ function CreateNewOAuthClient({ setClients }: { setClients: SetState<TachiAPICli
|
||||
);
|
||||
|
||||
if (res.success) {
|
||||
DelayedPageReload();
|
||||
setClients((prev) => [...prev, res.body]);
|
||||
setShow(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -696,9 +698,9 @@ function KAIIntegrationStatus({
|
||||
kaiType: "eag" | "flo" | "min";
|
||||
userID: integer;
|
||||
}) {
|
||||
const { data, error } = useApiQuery<{ authStatus: boolean }>(
|
||||
`/users/${userID}/integrations/kai/${kaiType}`,
|
||||
);
|
||||
const queryClient = useQueryClient();
|
||||
const kaiQueryKey = `/users/${userID}/integrations/kai/${kaiType}`;
|
||||
const { data, error } = useApiQuery<{ authStatus: boolean }>(kaiQueryKey);
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
@@ -730,7 +732,7 @@ function KAIIntegrationStatus({
|
||||
);
|
||||
|
||||
if (res.success) {
|
||||
window.location.reload();
|
||||
await queryClient.invalidateQueries([kaiQueryKey]);
|
||||
}
|
||||
}}
|
||||
variant="danger"
|
||||
|
||||
@@ -7,10 +7,11 @@ import Loading from "#components/util/Loading";
|
||||
import Muted from "#components/util/Muted";
|
||||
import useApiQuery from "#components/util/query/useApiQuery";
|
||||
import { APIFetchV1 } from "#util/api";
|
||||
import { CopyToClipboard, DelayedPageReload } from "#util/misc";
|
||||
import { CopyToClipboard } from "#util/misc";
|
||||
import { FormatTime } from "#util/time";
|
||||
import React from "react";
|
||||
import { Button, Col, Row } from "react-bootstrap";
|
||||
import { useQueryClient } from "react-query";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
type integer,
|
||||
@@ -26,6 +27,8 @@ export default function UserInvitesPage({ reqUser }: { reqUser: UserDocument })
|
||||
`${reqUser.username}'s Invites`,
|
||||
);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, error } = useApiQuery<{ invites: integer; limit: integer }>(
|
||||
`/users/${reqUser.id}/invites/limit`,
|
||||
);
|
||||
@@ -72,7 +75,14 @@ export default function UserInvitesPage({ reqUser }: { reqUser: UserDocument })
|
||||
);
|
||||
|
||||
if (r.success) {
|
||||
DelayedPageReload();
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries([
|
||||
`/users/${reqUser.id}/invites`,
|
||||
]),
|
||||
queryClient.invalidateQueries([
|
||||
`/users/${reqUser.id}/invites/limit`,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -6,10 +6,12 @@ import Icon from "#components/util/Icon";
|
||||
import Muted from "#components/util/Muted";
|
||||
import useApiQuery from "#components/util/query/useApiQuery";
|
||||
import SelectButton from "#components/util/SelectButton";
|
||||
import { BackgroundContext } from "#context/BackgroundContext";
|
||||
import { UserContext } from "#context/UserContext";
|
||||
import { UserSettingsContext } from "#context/UserSettingsContext";
|
||||
import { type SetState } from "#types/react";
|
||||
import { APIFetchV1, ToAPIURL } from "#util/api";
|
||||
import { DelayedPageReload, FetchJSONBody, UppercaseFirst } from "#util/misc";
|
||||
import { FetchJSONBody, UppercaseFirst } from "#util/misc";
|
||||
import { getStoredTheme, mediaQueryPrefers, setTheme, type Themes } from "#util/themeUtils";
|
||||
import { useFormik } from "formik";
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
@@ -19,6 +21,7 @@ import Form from "react-bootstrap/Form";
|
||||
import InputGroup from "react-bootstrap/InputGroup";
|
||||
import Stack from "react-bootstrap/Stack";
|
||||
import toast from "react-hot-toast";
|
||||
import { useQueryClient } from "react-query";
|
||||
import { type integer, type UserDocument, type UserSettingsDocument } from "tachi-common";
|
||||
|
||||
interface Props {
|
||||
@@ -537,6 +540,13 @@ function ImageForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
const pfpInput = useRef<HTMLInputElement>(null);
|
||||
const [banner, setBanner] = useState<File | undefined>();
|
||||
const bannerInput = useRef<HTMLInputElement>(null);
|
||||
const [mediaNonce, setMediaNonce] = useState(0);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { setBackground } = useContext(BackgroundContext);
|
||||
const { user: ctxUser, setUser } = useContext(UserContext);
|
||||
|
||||
const mediaQs = mediaNonce > 0 ? `?v=${mediaNonce}` : "";
|
||||
|
||||
const handleReset = (ref: React.MutableRefObject<HTMLInputElement | null>) => {
|
||||
if (ref.current) {
|
||||
@@ -544,6 +554,23 @@ function ImageForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
}
|
||||
};
|
||||
|
||||
const afterMediaMutation = async (uploadType: "banner" | "pfp") => {
|
||||
setMediaNonce((n) => n + 1);
|
||||
if (uploadType === "banner") {
|
||||
setBackground(ToAPIURL(`/users/${reqUser.id}/banner?v=${Date.now()}`));
|
||||
}
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries([`/users/${reqUser.username}`]),
|
||||
queryClient.invalidateQueries([`/users/${reqUser.id}`]),
|
||||
]);
|
||||
if (ctxUser?.id === reqUser.id) {
|
||||
const r = await APIFetchV1<UserDocument>("/users/me", {}, true, true);
|
||||
if (r.success) {
|
||||
setUser(r.body);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={4}>
|
||||
<Alert variant="danger">
|
||||
@@ -563,12 +590,17 @@ function ImageForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
/>
|
||||
<div className="d-flex justify-content-center my-4">
|
||||
<ProfilePicture
|
||||
src={pfp ? URL.createObjectURL(pfp) : ToAPIURL(`/users/${reqUser.id}/pfp`)}
|
||||
src={
|
||||
pfp
|
||||
? URL.createObjectURL(pfp)
|
||||
: ToAPIURL(`/users/${reqUser.id}/pfp`) + mediaQs
|
||||
}
|
||||
user={reqUser}
|
||||
/>
|
||||
</div>
|
||||
<FileUploadController
|
||||
file={pfp}
|
||||
onMutationSuccess={afterMediaMutation}
|
||||
reqUser={reqUser}
|
||||
reset={() => handleReset(pfpInput)}
|
||||
setFile={setPfp}
|
||||
@@ -592,11 +624,12 @@ function ImageForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
src={
|
||||
banner
|
||||
? URL.createObjectURL(banner)
|
||||
: ToAPIURL(`/users/${reqUser.id}/banner`)
|
||||
: ToAPIURL(`/users/${reqUser.id}/banner`) + mediaQs
|
||||
}
|
||||
/>
|
||||
<FileUploadController
|
||||
file={banner}
|
||||
onMutationSuccess={afterMediaMutation}
|
||||
reqUser={reqUser}
|
||||
reset={() => handleReset(bannerInput)}
|
||||
setFile={setBanner}
|
||||
@@ -608,6 +641,7 @@ function ImageForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
}
|
||||
|
||||
function SocialMediaForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
const queryClient = useQueryClient();
|
||||
const placeholders = {
|
||||
discord: "Discord Username",
|
||||
twitter: "Twitter Handle",
|
||||
@@ -648,7 +682,10 @@ function SocialMediaForm({ reqUser }: { reqUser: UserDocument }) {
|
||||
);
|
||||
|
||||
if (rj.success) {
|
||||
DelayedPageReload();
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries([`/users/${reqUser.username}`]),
|
||||
queryClient.invalidateQueries([`/users/${reqUser.id}`]),
|
||||
]);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -710,8 +747,10 @@ function FileUploadController({
|
||||
reqUser,
|
||||
setFile,
|
||||
reset,
|
||||
onMutationSuccess,
|
||||
}: {
|
||||
file?: File;
|
||||
onMutationSuccess: (uploadType: "banner" | "pfp") => Promise<void> | void;
|
||||
reqUser: UserDocument;
|
||||
reset: () => void;
|
||||
setFile: SetState<File | undefined>;
|
||||
@@ -750,7 +789,7 @@ function FileUploadController({
|
||||
);
|
||||
|
||||
if (res.success) {
|
||||
DelayedPageReload();
|
||||
await onMutationSuccess(type);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -782,7 +821,9 @@ function FileUploadController({
|
||||
);
|
||||
|
||||
if (res.success) {
|
||||
window.location.reload();
|
||||
setFile(undefined);
|
||||
reset();
|
||||
await onMutationSuccess(type);
|
||||
}
|
||||
}}
|
||||
variant="success"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import Icon from "#components/util/Icon";
|
||||
import { useInvalidateUseApiQueryCache } from "#components/util/query/useApiQuery";
|
||||
import { APIFetchV1 } from "#util/api";
|
||||
import { DelayedPageReload } from "#util/misc";
|
||||
import React, { useMemo, useReducer } from "react";
|
||||
import { type ScoreDocument } from "tachi-common";
|
||||
|
||||
export default function DeleteScoreBtn({ score }: { score: ScoreDocument }) {
|
||||
const [warn, upgWarn] = useReducer((r) => r + 1, 0);
|
||||
const invalidateApiQueries = useInvalidateUseApiQueryCache();
|
||||
const message = useMemo(() => {
|
||||
if (warn === 0) {
|
||||
return <Icon noPad type="trash" />;
|
||||
@@ -36,7 +37,7 @@ export default function DeleteScoreBtn({ score }: { score: ScoreDocument }) {
|
||||
},
|
||||
true,
|
||||
true,
|
||||
).then(() => DelayedPageReload());
|
||||
).then(() => invalidateApiQueries());
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useInvalidateUseApiQueryCache } from "#components/util/query/useApiQuery";
|
||||
import { APIFetchV1 } from "#util/api";
|
||||
import { DelayedPageReload } from "#util/misc";
|
||||
import React, { useMemo, useReducer } from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { type ImportDocument } from "tachi-common";
|
||||
|
||||
export default function ManageImport({ importDoc }: { importDoc: ImportDocument }) {
|
||||
const [warn, upgWarn] = useReducer((r) => r + 1, 0);
|
||||
const invalidateApiQueries = useInvalidateUseApiQueryCache();
|
||||
const message = useMemo(() => {
|
||||
if (warn === 0) {
|
||||
return "Undo Import (Requires Further Confirmation)";
|
||||
@@ -39,7 +40,7 @@ export default function ManageImport({ importDoc }: { importDoc: ImportDocument
|
||||
},
|
||||
true,
|
||||
true,
|
||||
).then(() => DelayedPageReload());
|
||||
).then(() => invalidateApiQueries());
|
||||
}
|
||||
}}
|
||||
variant="danger"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { APIFetchV1 } from "#util/api";
|
||||
import { FormatDate } from "#util/time";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Button, Form, InputGroup, Modal } from "react-bootstrap";
|
||||
import { useQueryClient } from "react-query";
|
||||
import { UserAuthLevels, type UserDocument } from "tachi-common";
|
||||
|
||||
import ProfileBadges from "./ProfileBadges";
|
||||
@@ -42,12 +43,20 @@ export function UserHeaderBody({ reqUser }: { reqUser: UserDocument }) {
|
||||
}
|
||||
|
||||
const { user: loggedInUser } = useContext(UserContext);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
async function invalidateProfileQueries() {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries([`/users/${reqUser.username}`]),
|
||||
queryClient.invalidateQueries([`/users/${reqUser.id}`]),
|
||||
]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="col-12 col-lg-3">
|
||||
<div className="d-flex justify-content-center mb-3">
|
||||
<ProfilePicture user={reqUser} />
|
||||
<ProfilePicture key={reqUser.customPfpLocation ?? "default"} user={reqUser} />
|
||||
</div>
|
||||
<div className="d-flex align-items-center" style={{ flexDirection: "column" }}>
|
||||
<ProfileBadges user={reqUser} />
|
||||
@@ -69,7 +78,11 @@ export function UserHeaderBody({ reqUser }: { reqUser: UserDocument }) {
|
||||
{ method: "DELETE" },
|
||||
true,
|
||||
true,
|
||||
).then(() => window.location.reload())
|
||||
).then(async (res) => {
|
||||
if (res.success) {
|
||||
await invalidateProfileQueries();
|
||||
}
|
||||
})
|
||||
}
|
||||
variant="danger"
|
||||
>
|
||||
@@ -83,7 +96,11 @@ export function UserHeaderBody({ reqUser }: { reqUser: UserDocument }) {
|
||||
{ method: "POST" },
|
||||
true,
|
||||
true,
|
||||
).then(() => window.location.reload())
|
||||
).then(async (res) => {
|
||||
if (res.success) {
|
||||
await invalidateProfileQueries();
|
||||
}
|
||||
})
|
||||
}
|
||||
variant="primary"
|
||||
>
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import { APIFetchV1, type UnsuccessfulAPIFetchResponse } from "#util/api";
|
||||
import { type QueryKey, useQuery } from "react-query";
|
||||
import { type QueryClient, type QueryKey, useQuery, useQueryClient } from "react-query";
|
||||
import { type SuccessfulAPIResponse } from "tachi-common";
|
||||
|
||||
export function UnfuckURLsV3GameToV2Format(url: string) {
|
||||
return url;
|
||||
}
|
||||
|
||||
/** Invalidate data fetched via useApiQuery (keys are URL path strings) without a full page reload. */
|
||||
export function invalidateUseApiQueryCache(queryClient: QueryClient) {
|
||||
return queryClient.invalidateQueries({
|
||||
predicate: (q) =>
|
||||
Array.isArray(q.queryKey) &&
|
||||
q.queryKey.length > 0 &&
|
||||
q.queryKey.every((part) => typeof part === "string" && part.startsWith("/")),
|
||||
});
|
||||
}
|
||||
|
||||
export function useInvalidateUseApiQueryCache() {
|
||||
const queryClient = useQueryClient();
|
||||
return () => invalidateUseApiQueryCache(queryClient);
|
||||
}
|
||||
|
||||
export default function useApiQuery<T>(
|
||||
url: string | string[],
|
||||
options?: RequestInit,
|
||||
|
||||
@@ -146,10 +146,6 @@ export function FormatMillions(v: number) {
|
||||
return v.toLocaleString();
|
||||
}
|
||||
|
||||
export function DelayedPageReload(delay = 300) {
|
||||
setTimeout(() => window.location.reload(), delay);
|
||||
}
|
||||
|
||||
export function ShortDelayify(fn: () => void, delay = 300) {
|
||||
setTimeout(fn, delay);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user