useBestFriends();

This commit is contained in:
2025-07-24 19:50:54 +03:00
parent 980b27bf84
commit c7d3cd85be
24 changed files with 925 additions and 249 deletions

View File

@@ -0,0 +1,109 @@
"use client";
import React, { useEffect } from "react";
import LazyLoadedImage from "../util/LazyLoadedImage";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
import { OctagonXIcon } from "lucide-react";
import {
RobloxPremiumSmall,
RobloxVerifiedSmall
} from "@/components/roblox/RobloxTooltips";
import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount";
import { Skeleton } from "../ui/skeleton";
import { useFriendsPresence } from "@/hooks/roblox/usePresence";
import { useAccountSettings } from "@/hooks/roblox/useAccountSettings";
export function HomeLoggedInHeader() {
const profile = useCurrentAccount();
const accountSettings = useAccountSettings();
if (profile === false) {
return (
<div className="justify-center w-screen px-8 py-6">
<Alert variant="destructive" className="bg-base/50 space-x-2">
<OctagonXIcon />
<AlertTitle>Failed to fetch account info</AlertTitle>
<AlertDescription>
Please make sure <code>.ROBLOSECURITY</code> is set! Is
Roblox having an outage?
</AlertDescription>
</Alert>
</div>
);
}
const presence = useFriendsPresence(profile ? [profile.id] : []);
const userActivity = presence.find((b) => b.userId === profile?.id);
const userPresence = userActivity?.userPresenceType;
const borderColor =
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";
const isLoaded = !!profile && !!accountSettings;
return (
<>
{/* <button onClick={()=>console.log(userPresence)}>debug this</button> */}
<div className="flex items-center gap-6 rounded-xl px-8 py-6 w-fit mt-8 ml-0">
{!isLoaded ? (
<Skeleton className="w-28 h-28 rounded-full" />
) : (
<LazyLoadedImage
imgId={`AvatarHeadShot_${profile.id}`}
alt=""
className={`w-28 h-28 rounded-full shadow-crust border-2 ${borderColor}`}
/>
)}
<div className="flex flex-col justify-center">
<span className="text-3xl font-bold text-text flex items-center gap-2">
{isLoaded ? (
<>
{!!accountSettings &&
accountSettings.IsPremium === true
? `Howdy, ${profile.displayName}`
: `${profile.displayName}`}
</>
) : (
<>
<Skeleton className="w-96 h-8 rounded-lg" />
</>
)}
{!!accountSettings &&
accountSettings.IsPremium === true ? (
<RobloxPremiumSmall className="w-6 h-6 fill-transparent" />
) : (
<></>
)}
{isLoaded ? (
<RobloxVerifiedSmall className="w-6 h-6 fill-blue text-base" />
) : (
<></>
)}
</span>
<span className="text-base font-mono text-subtext0 mt-1">
{isLoaded ? (
<>
@{profile.name}
{!!userActivity && userPresence === 2 ? (
<> - {userActivity.lastLocation}</>
) : (
<></>
)}
</>
) : (
<Skeleton className="w-64 h-6 rounded-lg" />
)}
</span>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,52 @@
"use client";
import { useAvatarOutfits } from "@/hooks/roblox/useAvatarOutfits";
import { Button } from "@/components/ui/button";
import { cn, proxyFetch } from "@/lib/utils";
import LazyLoadedImage from "../util/LazyLoadedImage";
import { StupidHoverThing } from "../util/MiscStuff";
import { loadThumbnails } from "@/lib/thumbnailLoader";
import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount";
type OutfitSelectorProps = {
setVisible: (visible: boolean) => void;
updateOutfit: (outfit: { id: number }, acc: {id: number}) => Promise<void>;
};
export function OutfitSelector({ setVisible, updateOutfit }: OutfitSelectorProps) {
const outfits = useAvatarOutfits();
const acc = useCurrentAccount();
if (!outfits || !acc) return null;
return (
<div className="z-30 isolate absolute inset-0 flex items-center justify-center bg-crust/50">
<button
className="z-10 absolute w-screen h-screen cursor-default"
onClick={() => {
setVisible(false);
}}
/>
<div className="z-20 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 p-8 bg-crust/90 rounded-xl">
{(outfits || []).map((outfit: { id: number; name: string }) => (
<button
key={outfit.id}
className="hover:bg-base/50 rounded-lg"
onClick={async () => {
updateOutfit(outfit,acc);
setVisible(false);
}}
>
<StupidHoverThing delayDuration={0} text={outfit.name}>
<LazyLoadedImage
imgId={`Outfit_${outfit.id}`}
alt={outfit.name}
className="w-32 h-32 rounded-md"
/>
</StupidHoverThing>
</button>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,99 @@
"use client";
import { useRobuxBalance } from "@/hooks/roblox/useRobuxBalance";
import { RobuxIcon } from "../roblox/RobloxIcons";
import React, { useState } from "react";
import { Separator } from "../ui/separator";
import { Bell, SettingsIcon, ShirtIcon } from "lucide-react";
import { StupidHoverThing } from "../util/MiscStuff";
import { toast } from "sonner";
import { OutfitSelector } from "./OutfitQuickChooser";
import { proxyFetch } from "@/lib/utils";
import { loadThumbnails } from "@/lib/thumbnailLoader";
/**
requires csrf token cuz u cant use noblox.js on the web
either go to https://roblox.com/my/avataar or the app to change ur fit
*/
async function updateOutfit(outfit: { id: number }, acc: {id: number}) {
try {
const J = (await (
await proxyFetch(
`https://avatar.roblox.com/v3/outfits/${outfit.id}/details`
)
).json()) as {
id: number;
name: string;
assets: any[];
};
await proxyFetch(
`https://avatar.roblox.com/v1/avatar/set-wearing-assets`,
{
method: "POST",
body: JSON.stringify({
assetIds: J.assets.map(a=>a.id).filter(a=>!!a)
})
}
);
loadThumbnails([
{
type: "AvatarHeadShot",
targetId: acc.id,
format: "webp",
size: "720x720"
}
]).catch((a) => {});
} catch {}
}
export const QuickTopUI = React.memo(function () {
const robux = useRobuxBalance();
const [isOutfitSelectorVisible, setIsOutfitSelectorVisible] =
useState<boolean>(false);
return (
<>
{/* {isOutfitSelectorVisible ? (
<OutfitSelector setVisible={setIsOutfitSelectorVisible} updateOutfit={updateOutfit} />
) : (
<></>
)} */}
<div className="z-50 absolute top-4 right-4 p-4 flex gap-2 items-center text-blue/75">
{/* <StupidHoverThing text="Change Outfit">
<button
className="rounded-full bg-crust/50 flex items-center p-2"
onClick={() => {
setIsOutfitSelectorVisible((a) => !a);
}}
>
<ShirtIcon />
</button>
</StupidHoverThing> */}
<StupidHoverThing
text={!robux ? "Loading..." : `You have ${robux} Robux`}
>
<div className="rounded-full bg-crust/50 flex items-center p-2">
<RobuxIcon className="w-6 h-6" />
{robux ? (
<p className="pl-1">{robux || "???"}</p>
) : (
<></>
)}
</div>
</StupidHoverThing>
</div>
</>
);
});
export const QuickTopUILogoPart = React.memo(function () {
return (
<div className="z-[15] relative top-4 left-4 p-4 flex gap-4 items-center text-blue">
<img src="/icon-512.webp" className="-m-1 w-8 h-8" alt="" />
<p className="mt-2">{"not roblox lol"}</p>
</div>
);
});