diff --git a/app/games/[id]/content.tsx b/app/games/[id]/content.tsx new file mode 100644 index 0000000..5212ccb --- /dev/null +++ b/app/games/[id]/content.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useEffect } from "react"; +import Link from "next/link"; +import { usePlaceDetails } from "@/hooks/roblox/usePlaceDetails"; +import { RobloxVerifiedSmall } from "@/components/roblox/RobloxTooltips"; +import { Button } from "@/components/ui/button"; + +interface GamePageContentProps { + placeId: string; +} + +export default function GamePageContent({ placeId }: GamePageContentProps) { + const game = usePlaceDetails(placeId); + + // Set dynamic document title + useEffect(() => { + if (!!game) { + document.title = `${game.name} | ocbwoy3-chan's roblox`; + } + }, [game]); + + if (!game) return
Loading game...
; + + return ( +
+ +
+ {game.name} +
+
+ + + {game.creator.name} + + {game.creator.hasVerifiedBadge && ( + + )} + +
+ +
+ {game.description} +
+
+ ); +} diff --git a/app/games/[id]/page.tsx b/app/games/[id]/page.tsx new file mode 100644 index 0000000..dab76c8 --- /dev/null +++ b/app/games/[id]/page.tsx @@ -0,0 +1,11 @@ +import { Suspense } from "react"; +import GamePageContentF from "./content"; + +// page.tsx (Server Component) +export default async function GamePageContent({ params }: { params: { id: string } }) { + return ( + Loading profile…}> + + + ); +} diff --git a/app/globals.css b/app/globals.css index 1c7ceb5..c4c124d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3,7 +3,11 @@ @tailwind utilities; body { - font-family: Geist; + font-family: SF Pro Display, Geist; +} + +.font-super-mono { + font-family: SF Mono, Geist Mono; } @layer base { diff --git a/app/layout.tsx b/app/layout.tsx index 425a958..57f0ebd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -42,8 +42,8 @@ export default function RootLayout({ className="w-screen h-screen bg-blend-hard-light fixed top-0 left-0 opacity-25" alt="" /> */} +
- {children}
diff --git a/app/users/[id]/content.tsx b/app/users/[id]/content.tsx index e9d5696..a862f17 100644 --- a/app/users/[id]/content.tsx +++ b/app/users/[id]/content.tsx @@ -4,13 +4,32 @@ import { useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { notFound } from "next/navigation"; import { Separator } from "@/components/ui/separator"; -import { getUserByUserId } from "@/lib/profile"; +import { getUserByUserId, UserProfileDetails } from "@/lib/profile"; import { UserProfileHeader } from "@/components/roblox/UserProfileHeader"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { ShieldBanIcon } from "lucide-react"; +import Link from "next/link"; +import { useFriendsHome } from "@/hooks/roblox/useFriends"; +import { FriendCarousel } from "@/components/roblox/FriendCarousel"; interface UserProfileContentProps { userId: string; } +function ProfileMoreDetails({ profile }: { profile: UserProfileDetails }) { + const theirFriends = useFriendsHome(profile.id.toString()); + + return ( + <> + {!theirFriends && } + {/* + //@ts-expect-error */} + Friends} className="overflow-visible -ml-4" friends={theirFriends || []} /> + + ); +} + export default function UserProfileContent({ userId }: UserProfileContentProps) { @@ -34,9 +53,34 @@ export default function UserProfileContent({
-
+
{profile.description}
+ {profile.isBanned && ( + <> +
+ + + This user is banned + + Their Roblox account appears to be terminated + from the platform. You can see their inventory + and RAP history on{" "} + + Rolimons + + + +
+ + )} + {!profile.isBanned && }
); } diff --git a/components/providers/ReactQueryProvider.tsx b/components/providers/ReactQueryProvider.tsx index 91f4bc3..e54210c 100644 --- a/components/providers/ReactQueryProvider.tsx +++ b/components/providers/ReactQueryProvider.tsx @@ -15,12 +15,15 @@ export function ReactQueryProvider({ children }: Props) { defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutes - retry: 1 + retry: true } } }); + // will cause bun to SEGFAULT + useEffect(() => { + if (!window) return; // Persist to localStorage (safe, runs client-side) const localStoragePersister = createAsyncStoragePersister({ storage: window.localStorage diff --git a/components/roblox/FriendCarousel.tsx b/components/roblox/FriendCarousel.tsx index 644a1e7..732b097 100644 --- a/components/roblox/FriendCarousel.tsx +++ b/components/roblox/FriendCarousel.tsx @@ -1,6 +1,6 @@ import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount"; import { useFriendsPresence } from "@/hooks/roblox/usePresence"; -import React, { useMemo } from "react"; +import React, { ReactNode, useMemo } from "react"; import LazyLoadedImage from "../util/LazyLoadedImage"; import { StupidHoverThing } from "../util/MiscStuff"; import { VerifiedIcon } from "./RobloxIcons"; @@ -15,7 +15,7 @@ export function FriendCarousel({ React.HTMLAttributes, HTMLDivElement > & { - title: string; + title: Element | string; dontSortByActivity?: boolean; friends: | { diff --git a/components/roblox/GameCard.tsx b/components/roblox/GameCard.tsx index 97aaa54..4246c13 100644 --- a/components/roblox/GameCard.tsx +++ b/components/roblox/GameCard.tsx @@ -10,6 +10,7 @@ import { } from "../ui/context-menu"; import { ContextMenuItem } from "@radix-ui/react-context-menu"; import React from "react"; +import Link from "next/link"; interface GameCardProps { game: ContentMetadata; @@ -71,9 +72,9 @@ export const GameCard = React.memo(function GameCard({ game }: GameCardProps) { - - Open URL - + + Open + { diff --git a/components/roblox/RobloxTooltips.tsx b/components/roblox/RobloxTooltips.tsx index 7717161..7e18937 100644 --- a/components/roblox/RobloxTooltips.tsx +++ b/components/roblox/RobloxTooltips.tsx @@ -1,5 +1,6 @@ import { PremiumIconSmall, VerifiedIcon } from "./RobloxIcons"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; +import { ShieldBanIcon } from "lucide-react"; export function RobloxPremiumSmall(props: React.SVGProps) { return ( @@ -28,3 +29,18 @@ export function RobloxVerifiedSmall( ); } + +export function RobloxBannedSmall( + props: React.SVGProps & { useDefault?: boolean } +) { + return ( + + + + + +

Banned from Roblox

+
+
+ ); +} diff --git a/components/roblox/UserProfileHeader.tsx b/components/roblox/UserProfileHeader.tsx index 46627de..56f08fb 100644 --- a/components/roblox/UserProfileHeader.tsx +++ b/components/roblox/UserProfileHeader.tsx @@ -5,6 +5,7 @@ import LazyLoadedImage from "../util/LazyLoadedImage"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { OctagonXIcon } from "lucide-react"; import { + RobloxBannedSmall, RobloxPremiumSmall, RobloxVerifiedSmall } from "@/components/roblox/RobloxTooltips"; @@ -90,13 +91,14 @@ export function UserProfileHeader({ user }: { user: UserProfileDetails }) { )} - {isLoaded && user.hasVerifiedBadge ? ( + {isLoaded && user.hasVerifiedBadge && ( - ) : ( - <> + )} + {isLoaded && user.isBanned && ( + )} - + {isLoaded ? ( <> @{user.name} diff --git a/components/site/HomeUserHeader.tsx b/components/site/HomeUserHeader.tsx index 983566a..b539a8a 100644 --- a/components/site/HomeUserHeader.tsx +++ b/components/site/HomeUserHeader.tsx @@ -116,7 +116,7 @@ export function HomeLoggedInHeader() { <> )} - + {isLoaded ? ( <> @{profile.name} diff --git a/components/site/OutfitQuickChooser.tsx b/components/site/OutfitQuickChooser.tsx index 9115d47..6cea594 100644 --- a/components/site/OutfitQuickChooser.tsx +++ b/components/site/OutfitQuickChooser.tsx @@ -7,6 +7,7 @@ import LazyLoadedImage from "../util/LazyLoadedImage"; import { StupidHoverThing } from "../util/MiscStuff"; import { loadThumbnails } from "@/lib/thumbnailLoader"; import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount"; +import { useEffect } from "react"; type OutfitSelectorProps = { setVisible: (visible: boolean) => void; @@ -23,6 +24,18 @@ export function OutfitSelector({ const outfits = useAvatarOutfits(); const acc = useCurrentAccount(); + useEffect(() => { + if (!outfits) return; + loadThumbnails( + outfits.map((a) => ({ + type: "Outfit", + targetId: a.id, + format: "webp", + size: "420x420" + })) + ).catch(() => {}); + }, [acc, outfits]); + if (!outfits || !acc) return null; return ( @@ -35,22 +48,22 @@ export function OutfitSelector({ />
{(outfits || []).map((outfit: { id: number; name: string }) => ( - + + ))}
diff --git a/components/site/QuickTopUI.tsx b/components/site/QuickTopUI.tsx index 36c0538..a8fc8dc 100644 --- a/components/site/QuickTopUI.tsx +++ b/components/site/QuickTopUI.tsx @@ -112,7 +112,7 @@ export const QuickTopUI = React.memo(function () { ) : ( <> )} -
+