react query ftw
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// chatgpt
|
||||
function rewriteCookieDomain(rawCookie: string): string {
|
||||
return rawCookie
|
||||
.replace(/;?\s*Domain=[^;]+/i, '')
|
||||
.replace(/;?\s*Domain=[^;]+/i, "")
|
||||
.concat(`; Domain=localhost:3000`);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import Image from "next/image";
|
||||
import { QuickTopUI, QuickTopUILogoPart } from "@/components/site/QuickTopUI";
|
||||
import { ReactQueryProvider } from "@/components/providers/ReactQueryProvider";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -31,24 +32,26 @@ export default function RootLayout({
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased overflow-x-hidden`}
|
||||
>
|
||||
<TooltipProvider>
|
||||
<main>
|
||||
<Image
|
||||
/* window.localStorage.BgImageUrl */
|
||||
src={"/bg.png"}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="w-screen h-screen bg-blend-hard-light fixed top-0 left-0 blur-lg opacity-25"
|
||||
alt=""
|
||||
/>
|
||||
<div className="z-10 isolate overflow-scroll no-scrollbar w-screen max-h-screen h-screen antialiased overflow-x-hidden">
|
||||
<QuickTopUI />
|
||||
<QuickTopUILogoPart />
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
<Toaster />
|
||||
</TooltipProvider>
|
||||
<ReactQueryProvider>
|
||||
<TooltipProvider>
|
||||
<main>
|
||||
<Image
|
||||
/* window.localStorage.BgImageUrl */
|
||||
src={"/bg.png"}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="w-screen h-screen bg-blend-hard-light fixed top-0 left-0 blur-lg opacity-25"
|
||||
alt=""
|
||||
/>
|
||||
<div className="z-10 isolate overflow-scroll no-scrollbar w-screen max-h-screen h-screen antialiased overflow-x-hidden">
|
||||
<QuickTopUI />
|
||||
<QuickTopUILogoPart />
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
<Toaster />
|
||||
</TooltipProvider>
|
||||
</ReactQueryProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
17
app/page.tsx
17
app/page.tsx
@@ -13,17 +13,17 @@ import {
|
||||
OmniRecommendation
|
||||
} from "@/lib/omniRecommendation";
|
||||
import { loadThumbnails } from "@/lib/thumbnailLoader";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { AlertTriangleIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Home() {
|
||||
const SORTS_ALLOWED_IDS = [100000003, 100000001];
|
||||
const [rec, setRec] = useState<OmniRecommendation | null>(null);
|
||||
useEffect(() => {
|
||||
setTimeout(async () => {
|
||||
|
||||
const { data: rec } = useQuery({
|
||||
queryKey: ["omni-recommendations"],
|
||||
queryFn: async () => {
|
||||
const r = await getOmniRecommendationsHome();
|
||||
if (r) {
|
||||
setRec(r);
|
||||
loadThumbnails(
|
||||
Object.entries(r.contentMetadata.Game).map((a) => ({
|
||||
type: "GameThumbnail",
|
||||
@@ -33,8 +33,11 @@ export default function Home() {
|
||||
}))
|
||||
).catch((a) => {});
|
||||
}
|
||||
}, 1000);
|
||||
}, []);
|
||||
return r;
|
||||
},
|
||||
staleTime: 300000, // 5 minutes
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
|
||||
export default function Page() {
|
||||
return <>
|
||||
hi
|
||||
</>
|
||||
return <>hi</>;
|
||||
}
|
||||
|
||||
10
bun.lock
10
bun.lock
@@ -35,6 +35,8 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tanstack/react-query": "^5.85.3",
|
||||
"@tanstack/react-query-devtools": "^5.85.3",
|
||||
"@types/bun": "^1.2.19",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -285,6 +287,14 @@
|
||||
|
||||
"@tailwindcss/line-clamp": ["@tailwindcss/line-clamp@0.4.4", "", { "peerDependencies": { "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" } }, "sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.85.3", "", {}, "sha512-9Ne4USX83nHmRuEYs78LW+3lFEEO2hBDHu7mrdIgAFx5Zcrs7ker3n/i8p4kf6OgKExmaDN5oR0efRD7i2J0DQ=="],
|
||||
|
||||
"@tanstack/query-devtools": ["@tanstack/query-devtools@5.84.0", "", {}, "sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.85.3", "", { "dependencies": { "@tanstack/query-core": "5.85.3" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-AqU8TvNh5GVIE8I+TUU0noryBRy7gOY0XhSayVXmOPll4UkZeLWKDwi0rtWOZbwLRCbyxorfJ5DIjDqE7GXpcQ=="],
|
||||
|
||||
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.85.3", "", { "dependencies": { "@tanstack/query-devtools": "5.84.0" }, "peerDependencies": { "@tanstack/react-query": "^5.85.3", "react": "^18 || ^19" } }, "sha512-WSVweCE1Kh1BVvPDHAmLgGT+GGTJQ9+a7bVqzD+zUiUTht+salJjYm5nikpMNaHFPJV102TCYdvgHgBXtURRNg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
|
||||
|
||||
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
||||
|
||||
22
components/providers/ReactQueryProvider.tsx
Normal file
22
components/providers/ReactQueryProvider.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { useState } from "react";
|
||||
|
||||
export function ReactQueryProvider({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [queryClient] = useState(() => new QueryClient());
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
{process.env.NODE_ENV === "development" && (
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
)}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount";
|
||||
import { useFriendsHome } from "@/hooks/roblox/useFriends";
|
||||
import { useFriendsPresence } from "@/hooks/roblox/usePresence";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import LazyLoadedImage from "../util/LazyLoadedImage";
|
||||
import { StupidHoverThing } from "../util/MiscStuff";
|
||||
import { VerifiedIcon } from "./RobloxIcons";
|
||||
@@ -29,78 +28,32 @@ export function FriendCarousel({
|
||||
}) {
|
||||
const acct = useCurrentAccount();
|
||||
const presence = useFriendsPresence(
|
||||
(!!friendsUnsorted ? friendsUnsorted : []).map((f) => f.id)
|
||||
(friendsUnsorted || []).map((f) => f.id)
|
||||
);
|
||||
|
||||
const [friendsLabel, setFriendsLabel] = useState<string>("");
|
||||
const friends = useMemo(() => {
|
||||
if (!friendsUnsorted) return [];
|
||||
|
||||
const [friends, setFriends] = useState<
|
||||
{
|
||||
hasVerifiedBadge: boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
>([]);
|
||||
return [...friendsUnsorted].sort((a, b) => {
|
||||
if (dontSortByActivity) return -10;
|
||||
|
||||
useEffect(() => {
|
||||
let numStudio = 0;
|
||||
let numGame = 0;
|
||||
let numOnline = 0;
|
||||
for (const friend of friendsUnsorted || []) {
|
||||
const st = presence.find((c) => c.userId === friend.id);
|
||||
switch (st?.userPresenceType || 0) {
|
||||
case 1:
|
||||
numOnline += 1;
|
||||
break;
|
||||
case 2:
|
||||
numGame += 1;
|
||||
break;
|
||||
case 3:
|
||||
numStudio += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setFriendsLabel(
|
||||
[
|
||||
// `${friends.length}`,
|
||||
(numOnline+numGame+numStudio === 0 || numOnline === 0) ? null : `${numOnline+numGame+numStudio} online`,
|
||||
numGame === 0 ? null : `${numGame} in-game`,
|
||||
const userStatusA = presence.find((c) => c.userId === a.id);
|
||||
const userStatusB = presence.find((c) => c.userId === b.id);
|
||||
|
||||
]
|
||||
.filter((a) => !!a)
|
||||
.join(" | ")
|
||||
);
|
||||
|
||||
if (!friendsUnsorted) {
|
||||
setFriends([]);
|
||||
return;
|
||||
}
|
||||
setFriends(
|
||||
friendsUnsorted.sort((a, b) => {
|
||||
if (!!dontSortByActivity) return -10;
|
||||
const userStatusA = presence.find((c) => c.userId === a.id);
|
||||
const userStatusB = presence.find((c) => c.userId === b.id);
|
||||
|
||||
return (
|
||||
(userStatusB?.userPresenceType || 0) -
|
||||
(userStatusA?.userPresenceType || 0)
|
||||
);
|
||||
})
|
||||
);
|
||||
return (
|
||||
(userStatusB?.userPresenceType || 0) -
|
||||
(userStatusA?.userPresenceType || 0)
|
||||
);
|
||||
});
|
||||
}, [friendsUnsorted, presence, dontSortByActivity]);
|
||||
|
||||
if (!friends || friends.length === 0) {
|
||||
return <></>;
|
||||
if (friends.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
{/* <button onClick={()=>console.log(acct,presence,friends)}>debug</button> */}
|
||||
<h1 className="text-2xl pt-4 pl-4 -mb-4">
|
||||
{title}{" "}
|
||||
<span className="text-overlay1 text-sm pl-2">{friendsLabel}</span>
|
||||
</h1>
|
||||
<h1 className="text-2xl pt-4 pl-4 -mb-4">{title}</h1>
|
||||
<div className="rounded-xl flex flex-col gap-2 px-4 no-scrollbar">
|
||||
<div
|
||||
className="flex p-8 items-center gap-4 overflow-x-auto overflow-y-visible no-scrollbar pb-2 -mx-4 w-screen scrollbar-thin scrollbar-thumb-surface2 scrollbar-track-surface0"
|
||||
@@ -110,7 +63,6 @@ export function FriendCarousel({
|
||||
scrollbarWidth: "none"
|
||||
}}
|
||||
>
|
||||
{/* <div className="w-8" /> */}
|
||||
{friends.map((a) => {
|
||||
const userStatus = presence.find(
|
||||
(b) => b.userId === a.id
|
||||
@@ -161,15 +113,16 @@ export function FriendCarousel({
|
||||
className={`w-4 h-4 shrink-0`}
|
||||
/>
|
||||
) : null}
|
||||
{ userPresence >= 2 ? <p>{userStatus?.lastLocation}</p> : <></>}
|
||||
{userPresence >= 2 ? (
|
||||
<p>
|
||||
{userStatus?.lastLocation}
|
||||
</p>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
key={a.id}
|
||||
className="flex flex-col min-w-[6.5rem]"
|
||||
>
|
||||
<div className="flex flex-col min-w-[6.5rem]">
|
||||
<LazyLoadedImage
|
||||
imgId={`AvatarHeadShot_${a.id}`}
|
||||
alt={a.name}
|
||||
@@ -191,7 +144,6 @@ export function FriendCarousel({
|
||||
</StupidHoverThing>
|
||||
);
|
||||
})}
|
||||
{/* <div className="w-8" /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,5 +22,12 @@ export function BestFriendsHomeSect(
|
||||
) {
|
||||
const friends = useBestFriends();
|
||||
|
||||
return <FriendCarousel {...props} title="Best Friends" dontSortByActivity friends={friends} />;
|
||||
return (
|
||||
<FriendCarousel
|
||||
{...props}
|
||||
title="Best Friends"
|
||||
dontSortByActivity
|
||||
friends={friends}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { PremiumIconSmall, VerifiedIcon } from "./RobloxIcons";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger
|
||||
} from "../ui/tooltip";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||
|
||||
export function RobloxPremiumSmall(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
|
||||
@@ -17,9 +17,7 @@ import { toast } from "sonner";
|
||||
|
||||
// chatgpt + human
|
||||
function randomGreeting(name: string): string {
|
||||
const greetings = [
|
||||
`Howdy, ${name}`
|
||||
];
|
||||
const greetings = [`Howdy, ${name}`];
|
||||
|
||||
const index = Math.floor(Math.random() * greetings.length);
|
||||
return greetings[index];
|
||||
@@ -52,12 +50,12 @@ export function HomeLoggedInHeader() {
|
||||
userPresence === 1
|
||||
? "border-blue/25 bg-blue/25"
|
||||
: userPresence === 2
|
||||
? "border-green/25 bg-green/25"
|
||||
: userPresence === 3
|
||||
? "border-yellow/25 bg-yellow/25"
|
||||
: userPresence === 0
|
||||
? "border-surface2/25 bg-surface2/25"
|
||||
: "border-red/25 bg-red/25";
|
||||
? "border-green/25 bg-green/25"
|
||||
: userPresence === 3
|
||||
? "border-yellow/25 bg-yellow/25"
|
||||
: userPresence === 0
|
||||
? "border-surface2/25 bg-surface2/25"
|
||||
: "border-red/25 bg-red/25";
|
||||
|
||||
const isLoaded = !!profile && !!accountSettings;
|
||||
|
||||
@@ -92,7 +90,13 @@ export function HomeLoggedInHeader() {
|
||||
)}
|
||||
<div className="flex flex-col justify-center">
|
||||
<span className="text-3xl font-bold text-text flex items-center gap-2">
|
||||
{isLoaded ? randomGreeting(window.localStorage.UserPreferredName || profile.displayName || "Robloxian!") : (
|
||||
{isLoaded ? (
|
||||
randomGreeting(
|
||||
window.localStorage.UserPreferredName ||
|
||||
profile.displayName ||
|
||||
"Robloxian!"
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<Skeleton className="w-96 h-8 rounded-lg" />
|
||||
</>
|
||||
|
||||
@@ -10,10 +10,16 @@ import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount";
|
||||
|
||||
type OutfitSelectorProps = {
|
||||
setVisible: (visible: boolean) => void;
|
||||
updateOutfit: (outfit: { id: number }, acc: {id: number}) => Promise<void>;
|
||||
updateOutfit: (
|
||||
outfit: { id: number },
|
||||
acc: { id: number }
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
export function OutfitSelector({ setVisible, updateOutfit }: OutfitSelectorProps) {
|
||||
export function OutfitSelector({
|
||||
setVisible,
|
||||
updateOutfit
|
||||
}: OutfitSelectorProps) {
|
||||
const outfits = useAvatarOutfits();
|
||||
const acc = useCurrentAccount();
|
||||
|
||||
@@ -33,7 +39,7 @@ export function OutfitSelector({ setVisible, updateOutfit }: OutfitSelectorProps
|
||||
key={outfit.id}
|
||||
className="hover:bg-base/50 rounded-lg"
|
||||
onClick={async () => {
|
||||
updateOutfit(outfit,acc);
|
||||
updateOutfit(outfit, acc);
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -79,7 +79,6 @@ async function updateOutfit(outfit: { id: number }, acc: { id: number }) {
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
loadThumbnails([
|
||||
{
|
||||
type: "AvatarHeadShot",
|
||||
@@ -98,7 +97,7 @@ export const QuickTopUI = React.memo(function () {
|
||||
const bf = useBestFriends();
|
||||
useCurrentAccount();
|
||||
|
||||
useFriendsPresence([...(f ? f : []), ...(bf ? bf : [])].map(a=>a.id))
|
||||
useFriendsPresence([...(f ? f : []), ...(bf ? bf : [])].map((a) => a.id));
|
||||
|
||||
const robux = useRobuxBalance();
|
||||
const [isOutfitSelectorVisible, setIsOutfitSelectorVisible] =
|
||||
@@ -154,9 +153,12 @@ export const QuickTopUILogoPart = React.memo(function () {
|
||||
<Link href="/" className="-m-1 w-8 h-8">
|
||||
<img src="/icon-512.webp" className="w-8 h-8" alt="" />
|
||||
</Link>
|
||||
<Link href="/test" className="mt-2 gap-2 flex items-center">
|
||||
<Link href="/" className="mt-2 gap-2 flex items-center">
|
||||
<p>{"ocbwoy3-chan's roblox"}</p>
|
||||
<p className="text-surface2 line-clamp-1">{process.env.NODE_ENV} {process.env.NEXT_PUBLIC_CWD} {process.env.NEXT_PUBLIC_ARGV0}</p>
|
||||
{/* <p className="text-surface2 line-clamp-1">
|
||||
{process.env.NODE_ENV} {process.env.NEXT_PUBLIC_CWD}{" "}
|
||||
{process.env.NEXT_PUBLIC_ARGV0}
|
||||
</p> */}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,15 +2,18 @@ import { TooltipProps } from "@radix-ui/react-tooltip";
|
||||
import { VerifiedIcon } from "../roblox/RobloxIcons";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||
|
||||
export function StupidHoverThing({ children, text, ...props }: React.PropsWithChildren & TooltipProps & { text: string | React.ReactNode }) {
|
||||
export function StupidHoverThing({
|
||||
children,
|
||||
text,
|
||||
...props
|
||||
}: React.PropsWithChildren &
|
||||
TooltipProps & { text: string | React.ReactNode }) {
|
||||
return (
|
||||
<Tooltip {...props}>
|
||||
<TooltipTrigger asChild>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent className="bg-surface0 text-text m-2">
|
||||
<span className="text-sm flex items-center">{text}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,84 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useCurrentAccount } from "./useCurrentAccount";
|
||||
import { proxyFetch } from "@/lib/utils";
|
||||
import { useEffect } from "react";
|
||||
|
||||
type AccountSettings = {
|
||||
ChangeUsernameEnabled: boolean
|
||||
ChangeUsernameEnabled: boolean;
|
||||
|
||||
/* determines if the account owner is a roblox admin */
|
||||
IsAdmin: boolean,
|
||||
IsAdmin: boolean;
|
||||
|
||||
PreviousUserNames: string,
|
||||
PreviousUserNames: string;
|
||||
|
||||
/* censored out email */
|
||||
UserEmail: string,
|
||||
UserEmail: string;
|
||||
|
||||
UserAbove13: boolean,
|
||||
UserAbove13: boolean;
|
||||
|
||||
/* does the user have roblox premium */
|
||||
IsPremium: boolean,
|
||||
IsPremium: boolean;
|
||||
|
||||
/* ingame chat */
|
||||
IsGameChatSettingEnabled: boolean
|
||||
}
|
||||
IsGameChatSettingEnabled: boolean;
|
||||
};
|
||||
|
||||
export function useAccountSettings() {
|
||||
const acct = useCurrentAccount();
|
||||
const [accountSettings, setAccountSettings] = useState<AccountSettings | false | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
if (!acct) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const fetchSetttings = async () => {
|
||||
if (!acct || cancelled) return;
|
||||
const { data: accountSettings } = useQuery<AccountSettings | false | null>({
|
||||
queryKey: ["account-settings", acct ? acct.id : "acctId"],
|
||||
queryFn: async () => {
|
||||
if (!acct) return null;
|
||||
try {
|
||||
const res = await proxyFetch(
|
||||
`https://www.roblox.com/my/settings/json`
|
||||
);
|
||||
if (!res.ok) {
|
||||
console.error(
|
||||
`[useAccountSettings] API Error ${res.status} ${res.statusText}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!cancelled) setAccountSettings(data);
|
||||
} catch {
|
||||
if (!cancelled) setAccountSettings(false);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[useAccountSettings] Failed to fetch settings",
|
||||
error
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
fetchSetttings();
|
||||
},
|
||||
enabled: !!acct,
|
||||
staleTime: Infinity,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleTransaction = () => {
|
||||
fetchSetttings();
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["account-settings", acct ? acct.id : "acctId"]
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("settingTransactionCompletedEvent", handleTransaction);
|
||||
window.addEventListener(
|
||||
"settingTransactionCompletedEvent",
|
||||
handleTransaction
|
||||
);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.removeEventListener(
|
||||
"settingTransactionCompletedEvent",
|
||||
handleTransaction
|
||||
);
|
||||
};
|
||||
}, [acct]);
|
||||
}, [acct ? acct.id : "acctId", queryClient]);
|
||||
|
||||
return accountSettings;
|
||||
}
|
||||
|
||||
@@ -1,63 +1,64 @@
|
||||
// https://avatar.roblox.com/v2/avatar/users/1083030325/outfits?isEditable=true&itemsPerPage=50&outfitType=Avatar
|
||||
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import { useCurrentAccount } from "./useCurrentAccount";
|
||||
import { proxyFetch } from "@/lib/utils";
|
||||
import { loadThumbnails } from "@/lib/thumbnailLoader";
|
||||
|
||||
type Outfit = {
|
||||
name: string,
|
||||
id: number
|
||||
}
|
||||
name: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export function useAvatarOutfits() {
|
||||
export function useAvatarOutfits(): Outfit[] | false | null {
|
||||
const acct = useCurrentAccount();
|
||||
const [outfits, setOutfits] = useState<Outfit[] | false | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = useQuery<Outfit[] | false>({
|
||||
queryKey: ["avatarOutfits", acct ? acct.id : "acctId"],
|
||||
enabled: !!acct,
|
||||
queryFn: async () => {
|
||||
if (!acct) return false;
|
||||
|
||||
const res = await proxyFetch(
|
||||
`https://avatar.roblox.com/v2/avatar/users/${acct.id}/outfits?page=1&itemsPerPage=25&isEditable=true`
|
||||
);
|
||||
const data = (await res.json()) as { data: Outfit[] };
|
||||
|
||||
loadThumbnails(
|
||||
data.data.map((a) => ({
|
||||
type: "Outfit",
|
||||
targetId: a.id,
|
||||
format: "webp",
|
||||
size: "420x420"
|
||||
}))
|
||||
).catch(() => {});
|
||||
|
||||
return data.data;
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!acct) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const fetchSetttings = async () => {
|
||||
if (!acct || cancelled) return;
|
||||
try {
|
||||
const res = await proxyFetch(
|
||||
`https://avatar.roblox.com/v2/avatar/users/${acct.id}/outfits?page=1&itemsPerPage=25&isEditable=true`
|
||||
);
|
||||
const data = await res.json() as {data: Outfit[]};
|
||||
if (!cancelled) {
|
||||
setOutfits(data.data);
|
||||
loadThumbnails(data.data.map(a=>({
|
||||
type: "Outfit",
|
||||
targetId: a.id,
|
||||
format: "webp",
|
||||
size: "420x420"
|
||||
}))).catch(a=>{})
|
||||
}
|
||||
} catch {
|
||||
if (!cancelled) setOutfits(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSetttings();
|
||||
|
||||
const handleTransaction = () => {
|
||||
fetchSetttings();
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["avatarOutfits", acct ? acct.id : "acctId"]
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("avatarTransactionCompletedEvent", handleTransaction);
|
||||
|
||||
window.addEventListener(
|
||||
"avatarTransactionCompletedEvent",
|
||||
handleTransaction
|
||||
);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.removeEventListener(
|
||||
"avatarTransactionCompletedEvent",
|
||||
handleTransaction
|
||||
);
|
||||
};
|
||||
}, [acct]);
|
||||
}, [acct ? acct.id : "acctId", queryClient]);
|
||||
|
||||
return outfits;
|
||||
return query.data ?? null;
|
||||
}
|
||||
|
||||
@@ -1,46 +1,41 @@
|
||||
"use client";
|
||||
|
||||
// https://friends.roblox.com/v1/users/1083030325/friends/find?userSort=1
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCurrentAccount } from "./useCurrentAccount";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { proxyFetch } from "@/lib/utils";
|
||||
import { loadThumbnails } from "@/lib/thumbnailLoader";
|
||||
import { useCurrentAccount } from "./useCurrentAccount";
|
||||
|
||||
let isFetching = false;
|
||||
let cachedData: any = null;
|
||||
|
||||
export function useBestFriends() {
|
||||
export function useBestFriends():
|
||||
| {
|
||||
hasVerifiedBadge: boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
| null
|
||||
| false {
|
||||
const acct = useCurrentAccount();
|
||||
const [friends, setFriends] = useState<
|
||||
|
||||
const query = useQuery<
|
||||
| {
|
||||
hasVerifiedBadge: boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
| null
|
||||
| false
|
||||
>(cachedData);
|
||||
>({
|
||||
queryKey: ["bestFriends", acct ? acct.id : "acctId"],
|
||||
enabled: !!acct,
|
||||
queryFn: async () => {
|
||||
if (!acct) return false;
|
||||
|
||||
const BestFriendIDs = JSON.parse(
|
||||
window.localStorage.getItem("BestFriendsStore") || "[]"
|
||||
) as number[];
|
||||
|
||||
if (BestFriendIDs.length === 0) return [];
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (!acct) return;
|
||||
if (isFetching) {
|
||||
const IN = setInterval(() => {
|
||||
if (cachedData !== null) {
|
||||
if (!cancelled) setFriends(cachedData);
|
||||
clearInterval(IN);
|
||||
}
|
||||
}, 50);
|
||||
return () => {
|
||||
clearInterval(IN);
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
isFetching = true;
|
||||
(async () => {
|
||||
const BestFriendIDs = JSON.parse(window.localStorage.getItem("BestFriendsStore") || "[]") as number[]
|
||||
const friendsAPICall2 = await proxyFetch(
|
||||
`https://users.roblox.com/v1/users`,
|
||||
{
|
||||
@@ -51,6 +46,7 @@ export function useBestFriends() {
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const J2 = (await friendsAPICall2.json()) as {
|
||||
data: {
|
||||
hasVerifiedBadge: boolean;
|
||||
@@ -59,6 +55,7 @@ export function useBestFriends() {
|
||||
displayName: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
loadThumbnails(
|
||||
J2.data.map((a) => ({
|
||||
type: "AvatarHeadShot",
|
||||
@@ -67,7 +64,8 @@ export function useBestFriends() {
|
||||
format: "webp"
|
||||
}))
|
||||
).catch(() => {});
|
||||
const friendsList = BestFriendIDs.map((a) => {
|
||||
|
||||
return BestFriendIDs.map((a) => {
|
||||
const x = J2.data.find((b) => b.id === a);
|
||||
return {
|
||||
id: a,
|
||||
@@ -76,14 +74,10 @@ export function useBestFriends() {
|
||||
displayName: x?.displayName || "?"
|
||||
};
|
||||
});
|
||||
if (!cancelled) setFriends(friendsList);
|
||||
cachedData = friendsList;
|
||||
isFetching = false;
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [acct]);
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
return friends;
|
||||
return query.data ?? null;
|
||||
}
|
||||
|
||||
@@ -1,61 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getLoggedInUser,
|
||||
getUserByUserId,
|
||||
UserProfileDetails
|
||||
} from "@/lib/profile";
|
||||
import { loadThumbnails } from "@/lib/thumbnailLoader";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
let isFetching = false;
|
||||
let cachedData: UserProfileDetails | null | false = null;
|
||||
|
||||
export function useCurrentAccount() {
|
||||
const [profileDetails, setProfileDetails] = useState<
|
||||
UserProfileDetails | null | false
|
||||
>(cachedData);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (profileDetails !== null && profileDetails !== undefined) return;
|
||||
if (isFetching) {
|
||||
const IN = setInterval(() => {
|
||||
if (cachedData !== null) {
|
||||
if (!cancelled) setProfileDetails(cachedData);
|
||||
clearInterval(IN);
|
||||
}
|
||||
}, 50);
|
||||
return () => {
|
||||
clearInterval(IN);
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
isFetching = true;
|
||||
(async () => {
|
||||
export function useCurrentAccount(): UserProfileDetails | null | false {
|
||||
const query = useQuery<UserProfileDetails | false>({
|
||||
queryKey: ["currentAccount"],
|
||||
queryFn: async () => {
|
||||
const authed = await getLoggedInUser();
|
||||
if (authed) {
|
||||
const user = await getUserByUserId(authed.id.toString());
|
||||
if (!cancelled) setProfileDetails(user);
|
||||
cachedData = user;
|
||||
loadThumbnails([
|
||||
{
|
||||
type: "AvatarHeadShot",
|
||||
targetId: authed.id,
|
||||
format: "webp",
|
||||
size: "720x720"
|
||||
}
|
||||
]).catch(() => {});
|
||||
} else {
|
||||
if (!cancelled) setProfileDetails(false);
|
||||
cachedData = false;
|
||||
}
|
||||
isFetching = false;
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [profileDetails]);
|
||||
if (!authed) return false;
|
||||
|
||||
return profileDetails;
|
||||
const user = await getUserByUserId(authed.id.toString());
|
||||
|
||||
loadThumbnails([
|
||||
{
|
||||
type: "AvatarHeadShot",
|
||||
targetId: authed.id,
|
||||
format: "webp",
|
||||
size: "720x720"
|
||||
}
|
||||
]).catch(() => {});
|
||||
|
||||
return user;
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
return query.data ?? null;
|
||||
}
|
||||
|
||||
@@ -1,64 +1,34 @@
|
||||
"use client";
|
||||
|
||||
// https://friends.roblox.com/v1/users/1083030325/friends/find?userSort=1
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useCurrentAccount } from "./useCurrentAccount";
|
||||
import { proxyFetch } from "@/lib/utils";
|
||||
import { loadThumbnails } from "@/lib/thumbnailLoader";
|
||||
|
||||
let isFetching = false;
|
||||
let cachedData: any = null;
|
||||
import { UserProfileDetails } from "@/lib/profile";
|
||||
|
||||
export function useFriendsHome() {
|
||||
const acct = useCurrentAccount();
|
||||
const [friends, setFriends] = useState<
|
||||
| {
|
||||
hasVerifiedBadge: boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
| null
|
||||
| false
|
||||
>(cachedData);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (!acct) return;
|
||||
if (isFetching) {
|
||||
const IN = setInterval(() => {
|
||||
if (cachedData !== null) {
|
||||
if (!cancelled) setFriends(cachedData);
|
||||
clearInterval(IN);
|
||||
}
|
||||
}, 50);
|
||||
return () => {
|
||||
clearInterval(IN);
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
isFetching = true;
|
||||
(async () => {
|
||||
const { data: friends } = useQuery({
|
||||
queryKey: ["friends", acct ? acct.id : "acctId"],
|
||||
queryFn: async () => {
|
||||
if (!acct) return null;
|
||||
const friendsAPICall = await proxyFetch(
|
||||
`https://friends.roblox.com/v1/users/${acct.id}/friends` // /find?userSort=1
|
||||
`https://friends.roblox.com/v1/users/${acct.id}/friends`
|
||||
);
|
||||
const J = (await friendsAPICall.json()) as {
|
||||
const j = (await friendsAPICall.json()) as {
|
||||
data: { id: number }[];
|
||||
// PageItems: { id: number }[]; // /find
|
||||
};
|
||||
const friendsAPICall2 = await proxyFetch(
|
||||
`https://users.roblox.com/v1/users`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
userIds: J.data.map((a) => a.id),
|
||||
// userIds: J.PageItems.map((a) => a.id),
|
||||
userIds: j.data.map((a) => a.id),
|
||||
excludeBannedUsers: false
|
||||
})
|
||||
}
|
||||
);
|
||||
const J2 = (await friendsAPICall2.json()) as {
|
||||
const j2 = (await friendsAPICall2.json()) as {
|
||||
data: {
|
||||
hasVerifiedBadge: boolean;
|
||||
id: number;
|
||||
@@ -67,15 +37,15 @@ export function useFriendsHome() {
|
||||
}[];
|
||||
};
|
||||
loadThumbnails(
|
||||
J2.data.map((a) => ({
|
||||
j2.data.map((a) => ({
|
||||
type: "AvatarHeadShot",
|
||||
size: "420x420",
|
||||
targetId: a.id,
|
||||
format: "webp"
|
||||
}))
|
||||
).catch(() => {});
|
||||
const friendsList = J.data.map((a) => { // J.PageItems /find
|
||||
const x = J2.data.find((b) => b.id === a.id);
|
||||
const friendsList = j.data.map((a) => {
|
||||
const x = j2.data.find((b) => b.id === a.id);
|
||||
return {
|
||||
id: a.id,
|
||||
hasVerifiedBadge: x?.hasVerifiedBadge || false,
|
||||
@@ -83,14 +53,14 @@ export function useFriendsHome() {
|
||||
displayName: x?.displayName || "?"
|
||||
};
|
||||
});
|
||||
if (!cancelled) setFriends(friendsList);
|
||||
cachedData = friendsList;
|
||||
isFetching = false;
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [acct]);
|
||||
return friendsList;
|
||||
},
|
||||
enabled: !!acct,
|
||||
staleTime: 300000, // 5 minutes
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false
|
||||
});
|
||||
|
||||
return friends;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"use client";
|
||||
|
||||
// smartass method by google gemini
|
||||
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useCurrentAccount } from "./useCurrentAccount";
|
||||
import { proxyFetch } from "@/lib/utils";
|
||||
|
||||
@@ -16,114 +14,53 @@ type PresenceData = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
// --- Internal Shared State ---
|
||||
|
||||
/**
|
||||
* A Map to track subscribers.
|
||||
* Key: The component's update callback function.
|
||||
* Value: The array of user IDs that component is interested in.
|
||||
* This allows multiple components to subscribe with their own lists of IDs.
|
||||
*/
|
||||
let subscribers = new Map<(data: PresenceData[]) => void, number[]>();
|
||||
|
||||
let interval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
let latestData: PresenceData[] = [];
|
||||
|
||||
/**
|
||||
* Fetches presence for all unique user IDs requested by all subscribed components.
|
||||
* @param acctId - The ID of the currently logged-in user.
|
||||
*/
|
||||
async function fetchPresence(acctId: number) {
|
||||
const allIdArrays = [...subscribers.values()];
|
||||
const uniqueUserIds = [...new Set(allIdArrays.flat())];
|
||||
|
||||
if (!acctId || uniqueUserIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await proxyFetch(
|
||||
"https://presence.roblox.com/v1/presence/users",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
userIds: [...new Set([acctId, ...uniqueUserIds])]
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
console.log(`[usePresence] API Error ${res.status} ${res.statusText}`)
|
||||
return
|
||||
// throw new Error(`API request failed with status ${res.status}`);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
latestData = json.userPresences || [];
|
||||
|
||||
subscribers.forEach((_requestedIds, callback) => callback(latestData));
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch presence:", error);
|
||||
latestData = [];
|
||||
subscribers.forEach((_requestedIds, callback) => callback([]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A React hook to get the real-time presence of a list of Roblox users.
|
||||
* This hook can be used by multiple components simultaneously without conflict.
|
||||
* This hook uses @tanstack/react-query to handle caching and periodic refetching.
|
||||
*
|
||||
* @param userIds - An array of user IDs to track.
|
||||
* @returns An array of PresenceData objects for the requested user IDs.
|
||||
*/
|
||||
export function useFriendsPresence(userIds: number[]) {
|
||||
const acct = useCurrentAccount();
|
||||
const [data, setData] = useState<PresenceData[]>([]);
|
||||
|
||||
const userIdsKey = useMemo(
|
||||
() => JSON.stringify([...userIds].sort()),
|
||||
[userIds]
|
||||
);
|
||||
// Sort userIds to ensure the query key is stable, regardless of the order of IDs.
|
||||
const sortedUserIds = [...(userIds || [])].sort();
|
||||
|
||||
useEffect(() => {
|
||||
if (!acct || !userIds || userIds.length === 0) {
|
||||
setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const updateCallback = (globalData: PresenceData[]) => {
|
||||
const filteredData = globalData.filter((presence) =>
|
||||
userIds.includes(presence.userId)
|
||||
);
|
||||
setData(filteredData);
|
||||
};
|
||||
|
||||
updateCallback(latestData);
|
||||
|
||||
subscribers.set(updateCallback, userIds);
|
||||
|
||||
if (!interval) {
|
||||
fetchPresence(acct.id);
|
||||
interval = setInterval(() => fetchPresence(acct.id), 5000);
|
||||
} else {
|
||||
fetchPresence(acct.id);
|
||||
}
|
||||
|
||||
// The cleanup function runs when the component unmounts.
|
||||
return () => {
|
||||
subscribers.delete(updateCallback);
|
||||
|
||||
if (subscribers.size === 0 && interval) {
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
latestData = [];
|
||||
const { data: presences = [] } = useQuery({
|
||||
queryKey: ["presence", ...sortedUserIds],
|
||||
queryFn: async () => {
|
||||
if (!acct || sortedUserIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}, [acct, userIdsKey]);
|
||||
|
||||
return data;
|
||||
const res = await proxyFetch(
|
||||
"https://presence.roblox.com/v1/presence/users",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
userIds: sortedUserIds
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(
|
||||
`[usePresence] API Error ${res.status} ${res.statusText}`
|
||||
);
|
||||
throw new Error(`API request failed with status ${res.status}`);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return (json.userPresences || []) as PresenceData[];
|
||||
},
|
||||
enabled: !!acct && sortedUserIds.length > 0,
|
||||
refetchInterval: 5000,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
return presences;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useCurrentAccount } from "./useCurrentAccount";
|
||||
import { proxyFetch } from "@/lib/utils";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function useRobuxBalance() {
|
||||
const acct = useCurrentAccount();
|
||||
const [robux, setRobux] = useState<number | false | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
if (!acct) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const fetchBalance = async () => {
|
||||
if (!acct || cancelled) return;
|
||||
const { data: robux } = useQuery<number | false | null>({
|
||||
queryKey: ["robux-balance", acct ? acct.id : "acctId"],
|
||||
queryFn: async () => {
|
||||
if (!acct) return null;
|
||||
try {
|
||||
const res = await proxyFetch(
|
||||
`https://economy.roblox.com/v1/users/${acct.id}/currency`
|
||||
);
|
||||
const data = await res.json();
|
||||
if (!cancelled) setRobux(data.robux);
|
||||
return data.robux;
|
||||
} catch {
|
||||
if (!cancelled) setRobux(false);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
fetchBalance();
|
||||
const interval = setInterval(fetchBalance, 10000);
|
||||
},
|
||||
enabled: !!acct,
|
||||
refetchInterval: 10000,
|
||||
staleTime: 10000
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleTransaction = () => {
|
||||
fetchBalance();
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["robux-balance", acct ? acct.id : "acctId"]
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("transactionCompletedEvent", handleTransaction);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(interval);
|
||||
window.removeEventListener(
|
||||
"transactionCompletedEvent",
|
||||
handleTransaction
|
||||
);
|
||||
};
|
||||
}, [acct]);
|
||||
}, [acct ? acct.id : "acctId", queryClient]);
|
||||
|
||||
return robux;
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ export function findClosestBrickColor(hex: string): {
|
||||
col: [number, number, number];
|
||||
} {
|
||||
const target = hexToRgb(hex);
|
||||
console.log(hex,target)
|
||||
console.log(hex, target);
|
||||
if (!target) throw new Error("Invalid hex");
|
||||
|
||||
let bestDist = Infinity;
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function proxyFetchRaw(
|
||||
...init,
|
||||
method: init?.method || "GET",
|
||||
headers,
|
||||
body: init?.body,
|
||||
body: init?.body
|
||||
};
|
||||
|
||||
return window.fetch(proxyUrl, fetchInit);
|
||||
@@ -57,7 +57,7 @@ export async function proxyFetch(
|
||||
|
||||
response = await proxyFetchRaw(input, {
|
||||
...init,
|
||||
headers: newHeaders,
|
||||
headers: newHeaders
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
if (!process.isBun) {
|
||||
console.error(`You are running this with node. Rerun the process: bun --bun run dev`)
|
||||
process.exit(1)
|
||||
console.error(
|
||||
`You are running this with node. Rerun the process: bun --bun run dev`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.env.NEXT_PUBLIC_CWD = __dirname || "~"
|
||||
process.env.NEXT_PUBLIC_ARGV0 = process.argv0 || "node"
|
||||
|
||||
process.env.NEXT_PUBLIC_CWD = __dirname || "~";
|
||||
process.env.NEXT_PUBLIC_ARGV0 = process.argv0 || "node";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tanstack/react-query": "^5.85.3",
|
||||
"@tanstack/react-query-devtools": "^5.85.3",
|
||||
"@types/bun": "^1.2.19",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
Reference in New Issue
Block a user