diff --git a/app/api/user/authenticated/route.ts b/app/api/user/authenticated/route.ts new file mode 100644 index 0000000..cf7102c --- /dev/null +++ b/app/api/user/authenticated/route.ts @@ -0,0 +1,25 @@ +import { type NextRequest, NextResponse } from "next/server" + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const apiUrl = "https://users.roblox.com/v1/users/authenticated"; + + const url = `${apiUrl}?${searchParams.toString()}`; + + try { + const response = await fetch(url, { + headers: { + "User-Agent": "OCbwoy3ChanAI/1.0", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Content-Type": "application/json;charset=UTF-8", + "Cookie": request.headers.get("Authorization") || "", + }, + }); + + return NextResponse.json(await response.json()); + } catch (error) { + console.error("Error proxying request:", error); + return NextResponse.json({ error: "Internal Server Error" }); + } +} diff --git a/app/api/user/route.ts b/app/api/user/route.ts new file mode 100644 index 0000000..6b18954 --- /dev/null +++ b/app/api/user/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + const u = new URL(request.url); + const apiUrl = `https://users.roblox.com/v1/users/${u.searchParams.get("id")}`; + + try { + const response = await fetch(apiUrl, { + headers: { + "User-Agent": "OCbwoy3ChanAI/1.0", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Content-Type": "application/json;charset=UTF-8", + "Cookie": request.headers.get("Authorization") || "", + }, + }); + + return NextResponse.json(await response.json()); + } catch (error) { + console.error("Error proxying request:", error); + return NextResponse.json({ error: "Internal Server Error" }); + } +} diff --git a/app/globals.css b/app/globals.css index a4d605b..078412b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -8,31 +8,42 @@ body { @layer base { :root { - --background: 0 0% 100%; - --foreground: 0 0% 3.9%; - --card: 0 0% 100%; - --card-foreground: 0 0% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 0 0% 3.9%; - --primary: 0 0% 9%; - --primary-foreground: 0 0% 98%; - --secondary: 0 0% 96.1%; - --secondary-foreground: 0 0% 9%; - --muted: 0 0% 96.1%; - --muted-foreground: 0 0% 45.1%; - --accent: 0 0% 96.1%; - --accent-foreground: 0 0% 9%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 89.8%; - --input: 0 0% 89.8%; - --ring: 0 0% 3.9%; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; + --background: 240 21.052631735801697% 14.901961386203766%; /* base */ + --foreground: 226 63.93442749977112% 88.03921341896057%; /* text */ + + --muted: 237 16.239316761493683% 22.94117659330368%; /* surface0 */ + --muted-foreground: 227 35.29411852359772% 80.0000011920929%; /* subtext1 */ + + --popover: 240 21.052631735801697% 14.901961386203766%; /* base */ + --popover-foreground: 226 63.93442749977112% 88.03921341896057%; /* text */ + + --card: 240 21.052631735801697% 14.901961386203766%; /* base */ + --card-foreground: 226 63.93442749977112% 88.03921341896057%; /* text */ + + --border: 234 13.20754736661911% 31.176471710205078%; /* surface1 */ + --input: 234 13.20754736661911% 31.176471710205078%; /* surface1 */ + + --primary: 217 91.86992049217224% 75.88235139846802%; /* accent - Blue */ + --primary-foreground: 240 21.052631735801697% 14.901961386203766%; /* base */ + + --secondary: 237 16.239316761493683% 22.94117659330368%; /* surface0 */ + --secondary-foreground: 226 63.93442749977112% 88.03921341896057%; /* text */ + + --accent: 237 16.239316761493683% 22.94117659330368%; /* surface0 */ + --accent-foreground: 226 63.93442749977112% 88.03921341896057%; /* text */ + + --destructive: 343 81.25% 74.90196228027344%; /* red */ + --destructive-foreground: 240 21.311475336551666% 11.96078434586525%; /* mantle */ + + --ring: 226 63.93442749977112% 88.03921341896057%; /* text */ + --radius: 0.5rem; + + --chart-1: 343 81.25% 74.90196228027344%; /* red */ + --chart-2: 170 57.35294222831726% 73.33333492279053%; /* teal */ + --chart-3: 217 91.86992049217224% 75.88235139846802%; /* blue */ + --chart-4: 41 86.04651093482971% 83.13725590705872%; /* yellow */ + --chart-5: 115 54.09836173057556% 76.07843279838562%; /* green */ --sidebar-background: 0 0% 98%; --sidebar-foreground: 240 5.3% 26.1%; --sidebar-primary: 240 5.9% 10%; @@ -42,40 +53,6 @@ body { --sidebar-border: 220 13% 91%; --sidebar-ring: 217.2 91.2% 59.8%; } - .dark { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --card: 0 0% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 0 0% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; - --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --accent: 0 0% 14.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 14.9%; - --input: 0 0% 14.9%; - --ring: 0 0% 83.1%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; - } } @layer base { diff --git a/app/page.tsx b/app/page.tsx index 09233ec..273e2aa 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,11 +1,13 @@ "use client"; import { GameCard } from "@/components/gameCard"; +import { HomeLoggedInHeader } from "@/components/loggedInHeader"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { getOmniRecommendationsHome, OmniRecommendation, } from "@/lib/omniRecommendation"; +import { getCookie } from "@/lib/roblox"; import { loadThumbnails, ThumbnailRequest } from "@/lib/thumbnailLoader"; import { useEffect, useState } from "react"; @@ -27,8 +29,8 @@ export default function Home() { }) }) }); - loadThumbnails(th).catch(a=>console.error(a)) setRec(r); + loadThumbnails(th).catch(a=>console.error(a)) })(); }, []); @@ -50,9 +52,9 @@ export default function Home() { return (
- {"roblox x next.js when"} -
- {"experimental (functional) roblox.com clone"} + + {"roblox in nextjs"}
+ {"require(\"@/lib/roblox\").getCookie().length = "}{getCookie().length}{";"}
{rec.sorts .filter((a) => SORTS_ALLOWED_IDS.includes(a.topicId)) diff --git a/bun.lockb b/bun.lockb index 7856b31..40f1514 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/gameCard.tsx b/components/gameCard.tsx index 9f83944..a0b9b8c 100644 --- a/components/gameCard.tsx +++ b/components/gameCard.tsx @@ -1,4 +1,4 @@ -import { useGameThumbnailLazyLoad } from "@/hooks/use-lazy-load"; +import { useThumbnailLazyLoad } from "@/hooks/use-lazy-load"; import { ContentMetadata } from "@/lib/omniRecommendation"; import LazyLoadedImage from "./lazyLoadedImage"; import { ContextMenu, ContextMenuContent, ContextMenuSeparator, ContextMenuTrigger } from "./ui/context-menu"; @@ -16,7 +16,7 @@ export function GameCard({ game }: GameCardProps) {
{game.primaryMediaAsset ? ( diff --git a/components/lazyLoadedImage.tsx b/components/lazyLoadedImage.tsx index 55b2dee..03b6b0a 100644 --- a/components/lazyLoadedImage.tsx +++ b/components/lazyLoadedImage.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useGameThumbnailLazyLoad } from '@/hooks/use-lazy-load'; +import { useThumbnailLazyLoad } from '@/hooks/use-lazy-load'; interface LazyLoadedImageProps { imgId: string; @@ -8,7 +8,7 @@ interface LazyLoadedImageProps { } const LazyLoadedImage: React.FC = ({ imgId, alt, ...props }: LazyLoadedImageProps) => { - const imgUrl = useGameThumbnailLazyLoad(imgId); + const imgUrl = useThumbnailLazyLoad(imgId); return (
diff --git a/components/loggedInHeader.tsx b/components/loggedInHeader.tsx new file mode 100644 index 0000000..60be100 --- /dev/null +++ b/components/loggedInHeader.tsx @@ -0,0 +1,31 @@ +import { + getLoggedInUser, + getUserByUserId, + UserProfileDetails, +} from "@/lib/profile"; +import { useEffect, useState } from "react"; + +export function HomeLoggedInHeader() { + const [profileDetails, setProfileDetails] = + useState(null); + + useEffect(() => { + (async () => { + const authed = await getLoggedInUser(); + setProfileDetails(await getUserByUserId(authed.id.toString())); + })(); + }, []); + + if (!profileDetails) { + return (<>) + } + + return ( +
+ Hello, {profileDetails.displayName} + {"@"}{profileDetails.name} +
+ {profileDetails.id} +
+ ); +} diff --git a/hooks/use-lazy-load.ts b/hooks/use-lazy-load.ts index 76a509e..a78320b 100644 --- a/hooks/use-lazy-load.ts +++ b/hooks/use-lazy-load.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; let gameImages: { [id: string]: string } = {}; -export function useGameThumbnailLazyLoad(img: string) { +export function useThumbnailLazyLoad(img: string) { const [status, setStatus] = useState(undefined); useEffect(() => { @@ -19,6 +19,6 @@ export function useGameThumbnailLazyLoad(img: string) { return status; } -export function addGameThumbnail(id: string, url: string) { +export function addThumbnail(id: string, url: string) { gameImages[id] = url; } \ No newline at end of file diff --git a/lib/profile.ts b/lib/profile.ts new file mode 100644 index 0000000..d6d3b13 --- /dev/null +++ b/lib/profile.ts @@ -0,0 +1,40 @@ +import { getCookie } from "./roblox" + +export type UserProfileDetails = { + description: string, + created: Date, + isBanned: boolean, + externalAppDisplayName: string, + hasVerifiedBadge: boolean, + id: number, + name: string, + displayName: string +} + +export async function getLoggedInUser(): Promise<{ + id: number, + name: string, + displayName: string +}> { + const data = await fetch(`${document.baseURI}api/user/authenticated`, { + method: "GET", + headers: { + Authorization: `${getCookie()}` + }, + }) + return (await data.json() as any) as { + id: number, + name: string, + displayName: string + } +} + +export async function getUserByUserId(userid: string): Promise { + const data = await fetch(`${document.baseURI}api/user?id=${userid}`, { + method: "GET", + headers: { + Authorization: `${getCookie()}` + }, + }) + return (await data.json() as any) as UserProfileDetails +} \ No newline at end of file diff --git a/lib/thumbnailLoader.ts b/lib/thumbnailLoader.ts index 773ab82..c7dc3b1 100644 --- a/lib/thumbnailLoader.ts +++ b/lib/thumbnailLoader.ts @@ -1,6 +1,6 @@ "use client"; -import { addGameThumbnail } from "@/hooks/use-lazy-load"; +import { addThumbnail } from "@/hooks/use-lazy-load"; import { getCookie } from "./roblox"; export type AssetThumbnail = { @@ -45,7 +45,9 @@ export async function getThumbnails(b: ThumbnailRequest[]): Promise { const th = await getThumbnails(b); th.forEach(a=>{ - addGameThumbnail(a.targetId.toString(), a.imageUrl) + // match GameThumbnail from 4972273297::GameThumbnail:384x216:webp:regular and any like- string + const ty = b.find(c=>c.targetId==a.targetId)! + addThumbnail(ty.type+'_'+a.targetId.toString(), a.imageUrl) }) } diff --git a/package.json b/package.json index c9ce8fb..5a5835c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@catppuccin/tailwindcss": "^0.1.6", "@hookform/resolvers": "^4.0.0", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-alert-dialog": "^1.1.6", diff --git a/tailwind.config.ts b/tailwind.config.ts index 4197ec5..bc68ff6 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,94 +1,100 @@ import type { Config } from "tailwindcss"; export default { - darkMode: ["class"], - content: [ - "./pages/**/*.{js,ts,jsx,tsx,mdx}", - "./components/**/*.{js,ts,jsx,tsx,mdx}", - "./app/**/*.{js,ts,jsx,tsx,mdx}", - ], - theme: { - extend: { - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - }, - sidebar: { - DEFAULT: 'hsl(var(--sidebar-background))', - foreground: 'hsl(var(--sidebar-foreground))', - primary: 'hsl(var(--sidebar-primary))', - 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', - accent: 'hsl(var(--sidebar-accent))', - 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', - border: 'hsl(var(--sidebar-border))', - ring: 'hsl(var(--sidebar-ring))' - } - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - keyframes: { - 'accordion-down': { - from: { - height: '0' - }, - to: { - height: 'var(--radix-accordion-content-height)' - } - }, - 'accordion-up': { - from: { - height: 'var(--radix-accordion-content-height)' - }, - to: { - height: '0' - } - } - }, - animation: { - 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out' - } - } - }, - plugins: [require("tailwindcss-animate")], + darkMode: ["class"], + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + }, + sidebar: { + DEFAULT: 'hsl(var(--sidebar-background))', + foreground: 'hsl(var(--sidebar-foreground))', + primary: 'hsl(var(--sidebar-primary))', + 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', + accent: 'hsl(var(--sidebar-accent))', + 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', + border: 'hsl(var(--sidebar-border))', + ring: 'hsl(var(--sidebar-ring))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + keyframes: { + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out' + } + } + }, + plugins: [ + require("tailwindcss-animate"), + require("@catppuccin/tailwindcss")({ + prefix: false, + defaultFlavour: "mocha" + }) + ], } satisfies Config;