useBestFriends();
This commit is contained in:
@@ -1,119 +0,0 @@
|
||||
import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount";
|
||||
import { useFriendsHome } from "@/hooks/roblox/useFriendsHome";
|
||||
import LazyLoadedImage from "./lazyLoadedImage";
|
||||
import React from "react";
|
||||
import { VerifiedIcon } from "./RobloxIcons";
|
||||
import { useFriendsPresence } from "@/hooks/roblox/usePresence";
|
||||
import { StupidHoverThing } from "./MiscStuff";
|
||||
|
||||
export function FriendsHomeSect(
|
||||
props: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>
|
||||
) {
|
||||
const friends = useFriendsHome();
|
||||
const acct = useCurrentAccount();
|
||||
const presence = useFriendsPresence(
|
||||
(!!friends ? friends : []).map((f) => f.id)
|
||||
);
|
||||
|
||||
if (!friends) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
{/* <button onClick={()=>console.log(acct,presence,friends)}>debug</button> */}
|
||||
<h1 className="text-2xl pb-2 pl-4">Friends</h1>
|
||||
<div className="bg-base p-4 rounded-xl flex flex-col gap-2 px-4 no-scrollbar">
|
||||
<div
|
||||
className="flex 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"
|
||||
style={{
|
||||
scrollSnapType: "x mandatory",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
scrollbarWidth: "none"
|
||||
}}
|
||||
>
|
||||
<div className="w-8" />
|
||||
{friends.map((a) => {
|
||||
const userStatus = presence.find(
|
||||
(b) => b.userId === a.id
|
||||
);
|
||||
const userPresence = userStatus?.userPresenceType || 0;
|
||||
const borderColor =
|
||||
userPresence === 1
|
||||
? "border-blue bg-blue/50"
|
||||
: userPresence === 2
|
||||
? "border-green bg-green/50"
|
||||
: userPresence === 3
|
||||
? "border-yellow bg-yellow/50"
|
||||
: userPresence === 0
|
||||
? "border-surface2 bg-surface2/50"
|
||||
: "border-red bg-red/50";
|
||||
const textColor =
|
||||
userPresence === 1
|
||||
? "text-blue"
|
||||
: userPresence === 2
|
||||
? "text-green"
|
||||
: userPresence === 3
|
||||
? "text-yellow"
|
||||
: userPresence === 0
|
||||
? "text-surface2"
|
||||
: "text-red";
|
||||
const fillColor =
|
||||
userPresence === 1
|
||||
? "fill-blue"
|
||||
: userPresence === 2
|
||||
? "fill-green"
|
||||
: userPresence === 3
|
||||
? "fill-yellow"
|
||||
: userPresence === 0
|
||||
? "fill-surface2"
|
||||
: "fill-red";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={a.id}
|
||||
className="flex flex-col min-w-[6.5rem]"
|
||||
>
|
||||
<LazyLoadedImage
|
||||
imgId={`AvatarHeadShot_${a.id}`}
|
||||
alt={a.name}
|
||||
className={`w-24 h-24 rounded-full border-2 ${borderColor} object-cover shadow-xl`}
|
||||
/>
|
||||
<span
|
||||
className={`text-xs ${textColor} mt-1 text-center flex items-center justify-center gap-1 max-w-[6.5rem] overflow-hidden line-clamp-2`}
|
||||
>
|
||||
<StupidHoverThing
|
||||
text={
|
||||
<span className="space-x-1 flex items-center">
|
||||
<p>{a.displayName || a.name}</p>
|
||||
{!a.hasVerifiedBadge ? (
|
||||
<VerifiedIcon
|
||||
useDefault
|
||||
className={`w-4 h-4 shrink-0`}
|
||||
/>
|
||||
) : null}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span className="line-clamp-1 overflow-hidden text-ellipsis">
|
||||
{a.displayName || a.name}
|
||||
</span>
|
||||
</StupidHoverThing>
|
||||
{!a.hasVerifiedBadge ? (
|
||||
<VerifiedIcon
|
||||
className={`text-base ${fillColor} w-3 h-3 shrink-0`}
|
||||
/>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="w-8" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { VerifiedIcon } from "./RobloxIcons";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||
|
||||
export function StupidHoverThing({ children, text }: React.PropsWithChildren & { text: string | React.ReactNode }) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-surface0 text-text m-2">
|
||||
<p className="text-sm flex items-center">{text}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useRobuxBalance } from "@/hooks/roblox/useRobuxBalance";
|
||||
import { RobuxIcon } from "./RobloxIcons";
|
||||
import React from "react";
|
||||
|
||||
export const QuickTopUI = React.memo(function () {
|
||||
const robux = useRobuxBalance();
|
||||
return (
|
||||
<div className="z-50 absolute top-4 right-4 p-4 flex gap-4 items-center">
|
||||
<div className="rounded-full bg-crust/50 flex items-center p-2">
|
||||
<div className="px-2 font-sans text-blue text-xl flex items-center">
|
||||
<RobuxIcon className="w-6 h-6" />
|
||||
<p className="pl-1">{robux || "???"}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const QuickTopUILogoPart = React.memo(function () {
|
||||
return (
|
||||
<div className="z-50 relative top-4 left-4 p-4 flex gap-4 items-center text-blue">
|
||||
<img src="/icon-128.webp" className="-m-1 w-8 h-8" alt="" />
|
||||
<p className="mt-2">{"ocbwoy3-chan-blox"}</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
198
components/roblox/FriendCarousel.tsx
Normal file
198
components/roblox/FriendCarousel.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
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 LazyLoadedImage from "../util/LazyLoadedImage";
|
||||
import { StupidHoverThing } from "../util/MiscStuff";
|
||||
import { VerifiedIcon } from "./RobloxIcons";
|
||||
|
||||
export function FriendCarousel({
|
||||
friends: friendsUnsorted,
|
||||
title,
|
||||
dontSortByActivity,
|
||||
...props
|
||||
}: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
title: string;
|
||||
dontSortByActivity?: boolean;
|
||||
friends:
|
||||
| {
|
||||
hasVerifiedBadge: boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
| null
|
||||
| false;
|
||||
}) {
|
||||
const acct = useCurrentAccount();
|
||||
const presence = useFriendsPresence(
|
||||
(!!friendsUnsorted ? friendsUnsorted : []).map((f) => f.id)
|
||||
);
|
||||
|
||||
const [friendsLabel, setFriendsLabel] = useState<string>("");
|
||||
|
||||
const [friends, setFriends] = useState<
|
||||
{
|
||||
hasVerifiedBadge: boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
>([]);
|
||||
|
||||
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}`,
|
||||
numGame === 0 ? null : `${numGame} in-game`,
|
||||
numStudio === 0 ? null : `${numStudio} studio`
|
||||
]
|
||||
.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)
|
||||
);
|
||||
})
|
||||
);
|
||||
}, [friendsUnsorted, presence, dontSortByActivity]);
|
||||
|
||||
if (!friends || friends.length === 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
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>
|
||||
<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"
|
||||
style={{
|
||||
scrollSnapType: "x mandatory",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
scrollbarWidth: "none"
|
||||
}}
|
||||
>
|
||||
{/* <div className="w-8" /> */}
|
||||
{friends.map((a) => {
|
||||
const userStatus = presence.find(
|
||||
(b) => b.userId === a.id
|
||||
);
|
||||
const userPresence = userStatus?.userPresenceType || 0;
|
||||
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 textColor =
|
||||
userPresence === 1
|
||||
? "text-blue"
|
||||
: userPresence === 2
|
||||
? "text-green"
|
||||
: userPresence === 3
|
||||
? "text-yellow"
|
||||
: userPresence === 0
|
||||
? "text-surface2"
|
||||
: "text-red";
|
||||
const fillColor =
|
||||
userPresence === 1
|
||||
? "fill-blue"
|
||||
: userPresence === 2
|
||||
? "fill-green"
|
||||
: userPresence === 3
|
||||
? "fill-yellow"
|
||||
: userPresence === 0
|
||||
? "fill-surface2"
|
||||
: "fill-red";
|
||||
|
||||
return (
|
||||
<StupidHoverThing
|
||||
key={a.id}
|
||||
delayDuration={0}
|
||||
text={
|
||||
<div className="text-center items-center justify-center content-center">
|
||||
<span className="space-x-1 flex items-center">
|
||||
<p>{a.displayName || a.name}</p>
|
||||
{!a.hasVerifiedBadge ? (
|
||||
<VerifiedIcon
|
||||
useDefault
|
||||
className={`w-4 h-4 shrink-0`}
|
||||
/>
|
||||
) : null}
|
||||
{ userPresence >= 2 ? <p>{userStatus?.lastLocation}</p> : <></>}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
key={a.id}
|
||||
className="flex flex-col min-w-[6.5rem]"
|
||||
>
|
||||
<LazyLoadedImage
|
||||
imgId={`AvatarHeadShot_${a.id}`}
|
||||
alt={a.name}
|
||||
className={`w-24 h-24 rounded-full border-2 ${borderColor} object-cover shadow-xl`}
|
||||
/>
|
||||
<span
|
||||
className={`text-xs ${textColor} mt-1 text-center flex items-center justify-center gap-1 max-w-[6.5rem] overflow-hidden line-clamp-2`}
|
||||
>
|
||||
<span className="line-clamp-1 overflow-hidden text-ellipsis">
|
||||
{a.displayName || a.name}
|
||||
</span>
|
||||
{!a.hasVerifiedBadge ? (
|
||||
<VerifiedIcon
|
||||
className={`text-base ${fillColor} w-3 h-3 shrink-0`}
|
||||
/>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
</StupidHoverThing>
|
||||
);
|
||||
})}
|
||||
{/* <div className="w-8" /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
components/roblox/FriendsOnline.tsx
Normal file
26
components/roblox/FriendsOnline.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useFriendsHome } from "@/hooks/roblox/useFriends";
|
||||
import React from "react";
|
||||
import { FriendCarousel } from "./FriendCarousel";
|
||||
import { useBestFriends } from "@/hooks/roblox/useBestFriends";
|
||||
|
||||
export function FriendsHomeSect(
|
||||
props: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>
|
||||
) {
|
||||
const friends = useFriendsHome();
|
||||
|
||||
return <FriendCarousel {...props} title="Friends" friends={friends} />;
|
||||
}
|
||||
|
||||
export function BestFriendsHomeSect(
|
||||
props: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>
|
||||
) {
|
||||
const friends = useBestFriends();
|
||||
|
||||
return <FriendCarousel {...props} title="Best Friends" dontSortByActivity friends={friends} />;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { ContentMetadata } from "@/lib/omniRecommendation";
|
||||
import LazyLoadedImage from "./lazyLoadedImage";
|
||||
import LazyLoadedImage from "../util/LazyLoadedImage";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger
|
||||
} from "./ui/context-menu";
|
||||
} from "../ui/context-menu";
|
||||
import { ContextMenuItem } from "@radix-ui/react-context-menu";
|
||||
import React from "react";
|
||||
|
||||
@@ -2,9 +2,8 @@ import { PremiumIconSmall, VerifiedIcon } from "./RobloxIcons";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger
|
||||
} from "./ui/tooltip";
|
||||
} from "../ui/tooltip";
|
||||
|
||||
export function RobloxPremiumSmall(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
@@ -1,21 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import LazyLoadedImage from "./lazyLoadedImage";
|
||||
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
||||
import LazyLoadedImage from "../util/LazyLoadedImage";
|
||||
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
||||
import { OctagonXIcon } from "lucide-react";
|
||||
import { RobloxPremiumSmall, RobloxVerifiedSmall } from "./RobloxTooltipStuff";
|
||||
import {
|
||||
RobloxPremiumSmall,
|
||||
RobloxVerifiedSmall
|
||||
} from "@/components/roblox/RobloxTooltips";
|
||||
import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount";
|
||||
import { Skeleton } from "./ui/skeleton";
|
||||
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="space-x-2">
|
||||
<Alert variant="destructive" className="bg-base/50 space-x-2">
|
||||
<OctagonXIcon />
|
||||
<AlertTitle>Failed to fetch account info</AlertTitle>
|
||||
<AlertDescription>
|
||||
@@ -29,24 +34,26 @@ export function HomeLoggedInHeader() {
|
||||
|
||||
const presence = useFriendsPresence(profile ? [profile.id] : []);
|
||||
|
||||
const userActivity = presence.find((b) => b.userId === profile?.id)
|
||||
const userActivity = presence.find((b) => b.userId === profile?.id);
|
||||
const userPresence = userActivity?.userPresenceType;
|
||||
const borderColor =
|
||||
userPresence === 1
|
||||
? "border-blue bg-blue/50"
|
||||
? "border-blue/25 bg-blue/25"
|
||||
: userPresence === 2
|
||||
? "border-green bg-green/50"
|
||||
? "border-green/25 bg-green/25"
|
||||
: userPresence === 3
|
||||
? "border-yellow bg-yellow/50"
|
||||
? "border-yellow/25 bg-yellow/25"
|
||||
: userPresence === 0
|
||||
? "border-surface2 bg-surface2/50"
|
||||
: "border-red bg-red/50";
|
||||
? "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 bg-base rounded-xl px-8 py-6 w-fit mt-8 ml-0">
|
||||
{!profile ? (
|
||||
<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
|
||||
@@ -57,22 +64,39 @@ export function HomeLoggedInHeader() {
|
||||
)}
|
||||
<div className="flex flex-col justify-center">
|
||||
<span className="text-3xl font-bold text-text flex items-center gap-2">
|
||||
{profile ? (
|
||||
<>Hello, {profile.displayName}</>
|
||||
{isLoaded ? (
|
||||
<>
|
||||
{!!accountSettings &&
|
||||
accountSettings.IsPremium === true
|
||||
? `Howdy, ${profile.displayName}`
|
||||
: `${profile.displayName}`}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Skeleton className="w-96 h-8 rounded-lg" />
|
||||
</>
|
||||
)}
|
||||
{/* TODO: Fetch the User's Roblox Premium subscription state */}
|
||||
<RobloxPremiumSmall className="w-6 h-6 fill-transparent" />
|
||||
<RobloxVerifiedSmall className="w-6 h-6 fill-blue text-base" />
|
||||
{!!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">
|
||||
{profile ? (
|
||||
{isLoaded ? (
|
||||
<>
|
||||
@{profile.name}
|
||||
{(!!userActivity && userPresence === 2) ? <> - {userActivity.lastLocation}</> : <></> }
|
||||
{!!userActivity && userPresence === 2 ? (
|
||||
<> - {userActivity.lastLocation}</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton className="w-64 h-6 rounded-lg" />
|
||||
52
components/site/OutfitQuickChooser.tsx
Normal file
52
components/site/OutfitQuickChooser.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
99
components/site/QuickTopUI.tsx
Normal file
99
components/site/QuickTopUI.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { useThumbnailURL } from "@/hooks/use-lazy-load";
|
||||
import { Skeleton } from "./ui/skeleton";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
interface LazyLoadedImageProps {
|
||||
16
components/util/MiscStuff.tsx
Normal file
16
components/util/MiscStuff.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
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 }) {
|
||||
return (
|
||||
<Tooltip {...props}>
|
||||
<TooltipTrigger asChild>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-surface0 text-text m-2">
|
||||
<span className="text-sm flex items-center">{text}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user