From f37f8d91e1a16a391a8788b45f1dce888f789c4a Mon Sep 17 00:00:00 2001 From: Vasanth T Date: Sat, 12 Sep 2026 13:48:12 +0530 Subject: [PATCH 1/3] feat(app): add watchlist and open layouts Make token discovery less boxed and add browser-local catch-up activity. Reuse Base UI controls and preserve existing transaction flows. --- app/src/app/(home)/loading.tsx | 51 ++- app/src/app/(home)/page.tsx | 19 +- app/src/app/api/launch/watchlist/route.ts | 27 ++ app/src/app/feed/loading.tsx | 16 +- app/src/app/globals.css | 24 ++ app/src/app/launch/loading.tsx | 22 +- app/src/app/launch/page.tsx | 2 +- app/src/app/rules/page.tsx | 12 +- app/src/app/t/[chain]/[token]/loading.tsx | 16 +- app/src/app/t/[chain]/[token]/page.tsx | 14 +- app/src/components/Footer.module.css | 8 +- app/src/components/HeaderNav.tsx | 34 +- app/src/components/Skeleton.tsx | 15 +- app/src/components/feed-loading.test.ts | 7 +- .../components/launchpad/ChainSelector.tsx | 25 ++ app/src/components/launchpad/CollectPanel.tsx | 14 +- .../components/launchpad/LaunchBrowser.tsx | 31 ++ app/src/components/launchpad/LaunchForm.tsx | 74 ++--- app/src/components/launchpad/LaunchList.tsx | 75 ++--- app/src/components/launchpad/LaunchRow.tsx | 34 +- app/src/components/launchpad/LaunchTape.tsx | 2 +- .../launchpad/MeDashboard.module.css | 80 +++-- app/src/components/launchpad/MeDashboard.tsx | 6 +- app/src/components/launchpad/Posts.tsx | 4 +- app/src/components/launchpad/TokenDetails.tsx | 4 +- app/src/components/launchpad/TradePanel.tsx | 20 +- .../launchpad/TradingChart.module.css | 28 +- .../components/launchpad/TrendingStrip.tsx | 29 +- app/src/components/launchpad/WatchButton.tsx | 34 ++ .../components/launchpad/WatchlistPanel.tsx | 150 +++++++++ .../launchpad/market-cap-sites.test.ts | 2 +- app/src/components/launchpad/useWatchlist.ts | 38 +++ app/src/components/navigation-shell.tsx | 11 +- app/src/components/open-ui.test.ts | 56 ++++ app/src/components/sections/Agents.module.css | 47 ++- .../sections/CommunityFeed.module.css | 47 ++- app/src/components/sections/CommunityFeed.tsx | 18 +- .../components/sections/RulesGuide.module.css | 36 ++- app/src/components/sections/SectionIntro.tsx | 2 +- .../sections/SectionShell.module.css | 17 +- app/src/components/ui.ts | 6 +- app/src/components/vendor/popover.tsx | 20 ++ app/src/components/vendor/tabs.tsx | 2 +- app/src/components/vendor/toggle-group.tsx | 6 +- app/src/components/workspace-layout.test.ts | 51 +++ app/src/lib/launchpad/queries-burn.test.ts | 35 +++ app/src/lib/launchpad/queries.ts | 18 +- app/src/lib/launchpad/watchlist.test.ts | 291 ++++++++++++++++++ app/src/lib/launchpad/watchlist.ts | 187 +++++++++++ app/src/lib/launchpad/watchlistData.test.ts | 240 +++++++++++++++ app/src/lib/launchpad/watchlistData.ts | 141 +++++++++ 51 files changed, 1766 insertions(+), 382 deletions(-) create mode 100644 app/src/app/api/launch/watchlist/route.ts create mode 100644 app/src/components/launchpad/ChainSelector.tsx create mode 100644 app/src/components/launchpad/LaunchBrowser.tsx create mode 100644 app/src/components/launchpad/WatchButton.tsx create mode 100644 app/src/components/launchpad/WatchlistPanel.tsx create mode 100644 app/src/components/launchpad/useWatchlist.ts create mode 100644 app/src/components/open-ui.test.ts create mode 100644 app/src/components/vendor/popover.tsx create mode 100644 app/src/components/workspace-layout.test.ts create mode 100644 app/src/lib/launchpad/queries-burn.test.ts create mode 100644 app/src/lib/launchpad/watchlist.test.ts create mode 100644 app/src/lib/launchpad/watchlist.ts create mode 100644 app/src/lib/launchpad/watchlistData.test.ts create mode 100644 app/src/lib/launchpad/watchlistData.ts diff --git a/app/src/app/(home)/loading.tsx b/app/src/app/(home)/loading.tsx index 9d91e2e..a7b73de 100644 --- a/app/src/app/(home)/loading.tsx +++ b/app/src/app/(home)/loading.tsx @@ -1,10 +1,11 @@ import { Sk, SkPost, SkRow } from "@/components/Skeleton"; +import { LaunchListHeader } from "@/components/launchpad/LaunchRow"; -/** Home skeleton: hero + totals, list header, rows, side column. Same grid as page.tsx so nothing shifts. */ +/** The readable hero, open market toolbar and support rail follow page.tsx. */ export default function Loading() { return ( -
-
+
+
{[0, 1, 2].map((i) => )} @@ -28,27 +29,36 @@ export default function Loading() {
-
+
+
+ +
+ {[0, 1, 2].map((i) =>
)} +
+
-
-
- - +
+
+ +
+
+ +
+
+ {[0, 1, 2, 3, 4, 5].map((i) => )}
-
- - - - +
+
+
    {Array.from({ length: 8 }, (_, i) => ( ))}
-
); } diff --git a/app/src/app/(home)/page.tsx b/app/src/app/(home)/page.tsx index 85701f1..349df72 100644 --- a/app/src/app/(home)/page.tsx +++ b/app/src/app/(home)/page.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import LaunchHero from "@/components/launchpad/LaunchHero"; import LaunchList from "@/components/launchpad/LaunchList"; +import LaunchBrowser from "@/components/launchpad/LaunchBrowser"; import LaunchTape from "@/components/launchpad/LaunchTape"; import { PostsFeed } from "@/components/launchpad/Posts"; import { listFeed } from "@/lib/launchpad/postsServer"; @@ -28,29 +29,31 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s return ( <> -
- +
+
+
- -
+
); diff --git a/app/src/app/api/launch/watchlist/route.ts b/app/src/app/api/launch/watchlist/route.ts new file mode 100644 index 0000000..5037e9e --- /dev/null +++ b/app/src/app/api/launch/watchlist/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { dbConfigured } from "@/lib/db"; +import { rateLimited } from "@/lib/launchpad/editServer"; +import { ethUsd } from "@/lib/launchpad/ethPrice"; +import { WatchlistInputError, getWatchlistData, parseWatchlistRequest, readWatchlistBody } from "@/lib/launchpad/watchlistData"; + +export const dynamic = "force-dynamic"; + +/** Read-only POST keeps the user's device-local token list out of URLs. Nothing is persisted. */ +export async function POST(req: Request) { + const headers = { "cache-control": "no-store" }; + const ip = (req.headers.get("fly-client-ip") || req.headers.get("x-forwarded-for") || "").split(",")[0].trim() || "0.0.0.0"; + if (rateLimited(`watchlist:ip:${ip}`, 60)) { + return NextResponse.json({ error: "Too many refreshes. Try again shortly." }, { status: 429, headers: { ...headers, "retry-after": "60" } }); + } + try { + const body = await readWatchlistBody(req); + const at = Date.now(); + const items = parseWatchlistRequest(body, at); + if (!dbConfigured()) return NextResponse.json({ error: "Watchlist activity is unavailable.", indexed: false }, { status: 503, headers }); + const data = await getWatchlistData(items, at, items.length ? await ethUsd() : null); + return NextResponse.json(data, { status: data.indexed ? 200 : 503, headers }); + } catch (error) { + if (error instanceof WatchlistInputError) return NextResponse.json({ error: error.message }, { status: error.status, headers }); + return NextResponse.json({ error: "Watchlist activity could not be refreshed. Your saved tokens are unchanged.", indexed: false }, { status: 503, headers }); + } +} diff --git a/app/src/app/feed/loading.tsx b/app/src/app/feed/loading.tsx index 80da540..2cfab79 100644 --- a/app/src/app/feed/loading.tsx +++ b/app/src/app/feed/loading.tsx @@ -10,11 +10,11 @@ export default function Loading() { return (
-
-
+
+
@@ -24,9 +24,11 @@ export default function Loading() {
-
-
-

+
+
+
+

+
    {Array.from({ length: 6 }, (_, i) => ( @@ -36,8 +38,8 @@ export default function Loading() {