diff --git a/app/layout.tsx b/app/layout.tsx index f469a84..1382b1c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,6 +3,8 @@ import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { TooltipProvider } from "@/components/ui/tooltip"; import { Toaster } from "@/components/ui/toaster"; +import Image from "next/image"; +import { QuickTopUI, QuickTopUILogoPart } from "@/components/site/QuickTopUI"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -15,7 +17,7 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "ocbwoy3-chan-blox", + title: "home | ocbwoy3-chan's roblox", description: "roblox meets next.js i think" }; @@ -30,7 +32,21 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased overflow-x-hidden`} > -
{children}
+
+ +
+ + + {children} +
+
diff --git a/app/page.tsx b/app/page.tsx index 0561d29..cfaee16 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,8 +6,6 @@ import { } from "@/components/roblox/FriendsOnline"; import { GameCard } from "@/components/roblox/GameCard"; import { HomeLoggedInHeader } from "@/components/site/HomeUserHeader"; -import { OutfitSelector } from "@/components/site/OutfitQuickChooser"; -import { QuickTopUI, QuickTopUILogoPart } from "@/components/site/QuickTopUI"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Card, CardContent } from "@/components/ui/card"; import { @@ -16,7 +14,6 @@ import { } from "@/lib/omniRecommendation"; import { loadThumbnails } from "@/lib/thumbnailLoader"; import { AlertTriangleIcon } from "lucide-react"; -import Image from "next/image"; import { useEffect, useState } from "react"; export default function Home() { @@ -41,65 +38,56 @@ export default function Home() { return ( <> - -
- - - -
- - -
- - - Warning - - This is work in progess, you can follow the - development process on GitHub. - - -
-
- {!rec ? ( - - -
-
- {"Loading..."} -
+ +
+ + +
+ + + Warning + + This is work in progess, you can follow the development + process on GitHub. + + +
+
+ {!rec ? ( + + +
+
+ {"Loading..."}
- - - ) : ( - rec.sorts - .filter((a) => - SORTS_ALLOWED_IDS.includes(a.topicId) - ) - .map((sort, idx) => ( -
-

- {sort.topic} -

-
- {(sort.recommendationList || []).map( - (recommendation, idxb) => { - const game = - rec.contentMetadata.Game[ - recommendation.contentId.toString() - ]; - return ( - - ); - } - )} -
+
+ + + ) : ( + rec.sorts + .filter((a) => SORTS_ALLOWED_IDS.includes(a.topicId)) + .map((sort, idx) => ( +
+

{sort.topic}

+
+ {(sort.recommendationList || []).map( + (recommendation, idxb) => { + const game = + rec.contentMetadata.Game[ + recommendation.contentId.toString() + ]; + return ( + + ); + } + )}
- )) - )} -
+
+ )) + )}
); diff --git a/app/test/page.tsx b/app/test/page.tsx new file mode 100644 index 0000000..3889dce --- /dev/null +++ b/app/test/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +export default function Page() { + return <> + hi + +} diff --git a/components/roblox/FriendCarousel.tsx b/components/roblox/FriendCarousel.tsx index 7ece789..39ac6fc 100644 --- a/components/roblox/FriendCarousel.tsx +++ b/components/roblox/FriendCarousel.tsx @@ -63,9 +63,10 @@ export function FriendCarousel({ } setFriendsLabel( [ - `${friends.length}`, + // `${friends.length}`, + (numOnline+numGame+numStudio === 0 || numOnline === 0) ? null : `${numOnline+numGame+numStudio} online`, numGame === 0 ? null : `${numGame} in-game`, - numStudio === 0 ? null : `${numStudio} studio` + ] .filter((a) => !!a) .join(" | ") diff --git a/components/site/QuickTopUI.tsx b/components/site/QuickTopUI.tsx index 856bf33..c1f772b 100644 --- a/components/site/QuickTopUI.tsx +++ b/components/site/QuickTopUI.tsx @@ -10,13 +10,18 @@ import { toast } from "sonner"; import { OutfitSelector } from "./OutfitQuickChooser"; import { proxyFetch } from "@/lib/utils"; import { loadThumbnails } from "@/lib/thumbnailLoader"; +import Link from "next/link"; +import { useFriendsHome } from "@/hooks/roblox/useFriends"; +import { useBestFriends } from "@/hooks/roblox/useBestFriends"; +import { useCurrentAccount } from "@/hooks/roblox/useCurrentAccount"; +import { useFriendsPresence } from "@/hooks/roblox/usePresence"; /** 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}) { +async function updateOutfit(outfit: { id: number }, acc: { id: number }) { try { const J = (await ( await proxyFetch( @@ -32,7 +37,7 @@ async function updateOutfit(outfit: { id: number }, acc: {id: number}) { { method: "POST", body: JSON.stringify({ - assetIds: J.assets.map(a=>a.id).filter(a=>!!a) + assetIds: J.assets.map((a) => a.id).filter((a) => !!a) }) } ); @@ -47,21 +52,28 @@ async function updateOutfit(outfit: { id: number }, acc: {id: number}) { } catch {} } - - export const QuickTopUI = React.memo(function () { + const f = useFriendsHome(); + const bf = useBestFriends(); + useCurrentAccount(); + + useFriendsPresence([...(f ? f : []), ...(bf ? bf : [])].map(a=>a.id)) + const robux = useRobuxBalance(); const [isOutfitSelectorVisible, setIsOutfitSelectorVisible] = useState(false); return ( <> - {/* {isOutfitSelectorVisible ? ( - + {isOutfitSelectorVisible ? ( + ) : ( <> - )} */} + )}
- {/* + - */} + {robux ? ( -

{robux || "???"}

+

{robux.toLocaleString()}

) : ( <> )} @@ -92,8 +104,12 @@ export const QuickTopUI = React.memo(function () { export const QuickTopUILogoPart = React.memo(function () { return (
- -

{"not roblox lol"}

+ + + + +

{"ocbwoy3-chan's roblox"}

+
); }); diff --git a/lib/utils.ts b/lib/utils.ts index dad49e3..e2e45de 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -7,7 +7,10 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export async function proxyFetch( +/** + * ! Do not use in actual code, only used by proxy fetch to fix the CSRF thing +*/ +async function proxyFetchRaw( input: RequestInfo | URL, init?: RequestInit ): Promise { @@ -15,13 +18,14 @@ export async function proxyFetch( typeof input === "string" ? input : input instanceof Request - ? input.url - : ""; + ? input.url + : ""; + const proxyUrl = `/api/proxy?url=${encodeURIComponent(url)}`; - // fix headers + // Fix headers const headers = new Headers(init?.headers || {}); - headers.delete("accept-encoding"); // prevent stupid encoding bug + headers.delete("accept-encoding"); // prevent encoding issues const fetchInit: RequestInit = { ...init, @@ -32,3 +36,33 @@ export async function proxyFetch( return window.fetch(proxyUrl, fetchInit); } + +// CSRF-aware proxy fetch +export async function proxyFetch( + input: RequestInfo | URL, + init?: RequestInit +): Promise { + const xsrfRequestMethods = ["POST", "PATCH", "DELETE"]; + const csrfTokenHeader = "x-csrf-token"; + const csrfInvalidResponseCode = 403; + + const method = init?.method?.toUpperCase() || "GET"; + + let response = await proxyFetchRaw(input, init); + + if ( + xsrfRequestMethods.includes(method) && + response.status === csrfInvalidResponseCode && + response.headers.has(csrfTokenHeader) + ) { + const newHeaders = new Headers(init?.headers || {}); + newHeaders.set(csrfTokenHeader, response.headers.get(csrfTokenHeader)!); + + response = await proxyFetchRaw(input, { + ...init, + headers: newHeaders + }); + } + + return response; +}