diff --git a/.env.sample b/.env.sample index 47e2b7c..8c5c439 100644 --- a/.env.sample +++ b/.env.sample @@ -1,15 +1,8 @@ # Used by new API examples APP_URL="http://localhost:3000" +# secure image worker for frames +IMAGE_WORKER_SECRET="" -# Used by deprecated examples -NEXT_PUBLIC_HOST="http://localhost:3000" - -# Optional - Hub URL to use for the debugger. If not set, the debugger will use the default hub URL. -DEBUG_HUB_HTTP_URL= - -# Optional - Slow request example -KV_REST_API_URL= -KV_REST_API_TOKEN= - -# Optional - debugging URL for the examples index page -NEXT_PUBLIC_DEBUGGER_URL= +# Turso SQLite db +TURSO_DATABASE_URL="" +TURSO_AUTH_TOKEN="" diff --git a/bun.lockb b/bun.lockb index 25bd1fb..892e790 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/public/images/default-pfp.png b/public/images/default-pfp.png new file mode 100644 index 0000000..7bdd0e0 Binary files /dev/null and b/public/images/default-pfp.png differ diff --git a/src/app/api/token/route.ts b/src/app/api/token/route.ts deleted file mode 100644 index c3486e7..0000000 --- a/src/app/api/token/route.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getTokenFromAddress } from "@/utils/db"; -import { NextRequest, NextResponse } from "next/server"; - -export const GET = async (req: NextRequest) => { - try { - const url = new URL(req.url); - const searchParams = new URLSearchParams(url.searchParams); - - const tokenAddress = searchParams.get("address"); - - if (!tokenAddress) { - return NextResponse.json( - { - status: "nok", - error: { - message: "Address missing", - }, - }, - { - status: 400, - statusText: "Bad Request", - } - ); - } - - const token = await getTokenFromAddress(tokenAddress); - if (!token) { - return NextResponse.json( - { - status: "nok", - error: { - message: "Token not found", - }, - }, - { - status: 404, - statusText: "Not Found", - } - ); - } - return NextResponse.json({ - status: "ok", - data: token, - }); - } catch (error) { - return NextResponse.json( - { - status: "nok", - error: { - message: "Internal Server Error", - }, - }, - { - status: 500, - statusText: "Internal Server Error", - } - ); - } -}; diff --git a/src/app/components/token-details.tsx b/src/app/components/token-details.tsx index 55121c7..2d2ee2d 100644 --- a/src/app/components/token-details.tsx +++ b/src/app/components/token-details.tsx @@ -1,41 +1,21 @@ -"use client"; - import { Token } from "@/utils/schemas/db.schema"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; import { Button } from "./ui/button"; -export const TokenDetails = () => { - const pathname = usePathname(); - const tokenAddress = pathname.split("/").pop(); - - const [token, setToken] = useState(undefined); - - if (!tokenAddress) { - return
Token not found
; - } - - useEffect(() => { - async function getTokenFromDb(tokenAddress: string) { - const tokenResult = await fetch(`/api/token?address=${tokenAddress}`); - const { data: token } = await tokenResult.json(); - setToken(token); - } - if (tokenAddress) { - getTokenFromDb(tokenAddress); - } - }, [tokenAddress]); +import Image from "next/image"; +export const TokenDetails = ({ token }: { token: Token | undefined }) => { return ( <> {token && (
- {token.name}

{token.ticker}

diff --git a/src/app/frames/frames.ts b/src/app/frames/frames.ts index f0aa5fd..3902492 100644 --- a/src/app/frames/frames.ts +++ b/src/app/frames/frames.ts @@ -2,13 +2,8 @@ import { farcasterHubContext } from "frames.js/middleware"; import { imagesWorkerMiddleware } from "frames.js/middleware/images-worker"; import { createFrames } from "frames.js/next"; -type State = { - counter: number; -}; - -export const frames = createFrames({ +export const frames = createFrames({ basePath: "/frames", - initialState: { counter: 0 }, debug: process.env.NODE_ENV === "development", middleware: [ farcasterHubContext(), diff --git a/src/app/frames/route.tsx b/src/app/frames/route.tsx index 95f1982..abe28e4 100644 --- a/src/app/frames/route.tsx +++ b/src/app/frames/route.tsx @@ -4,12 +4,6 @@ import { frames } from "./frames"; import { appURL } from "../utils"; const frameHandler = frames(async (ctx) => { - const counter = ctx.message - ? ctx.searchParams.op === "+" - ? ctx.state.counter + 1 - : ctx.state.counter - 1 - : ctx.state.counter; - return { image: (
@@ -17,12 +11,6 @@ const frameHandler = frames(async (ctx) => {
), buttons: [ - , - , , @@ -30,7 +18,6 @@ const frameHandler = frames(async (ctx) => { Token , ], - state: { counter: counter }, }; }); diff --git a/src/app/frames/token/[address]/route.tsx b/src/app/frames/token/[address]/route.tsx new file mode 100644 index 0000000..a6abc6b --- /dev/null +++ b/src/app/frames/token/[address]/route.tsx @@ -0,0 +1,211 @@ +/* eslint-disable @next/next/no-img-element */ +/* eslint-disable react/jsx-key */ +import { frames } from "@/app/frames/frames"; +import { appURL } from "@/app/utils"; +import { Button } from "frames.js/next"; +import { Container } from "@/app/frames/components/container"; +import { Column } from "@/app/frames/components/column"; +import { Text } from "@/app/frames/components/text"; +import { Row } from "@/app/frames/components/row"; +import { UserBanner } from "@/app/frames/components/user-banner"; +import { BrianClankerBanner } from "@/app/frames/components/brian-clanker-banner"; +import { BackgroundImage } from "@/app/frames/components/background-image"; +import { getTokenFromAddress } from "@/utils/db"; + +import { getAddress, isAddress } from "viem"; +import { getUserDataForFid } from "frames.js"; + +function truncate(text: string, max: number) { + return text.slice(0, max - 1) + (text.length > max ? "..." : ""); +} + +const frameHandler = frames(async (ctx) => { + const address = ctx.url.pathname.split("/").pop(); + try { + if (!address) { + throw new Error("No address provided"); + } + if (!isAddress(address, { strict: false })) { + throw new Error("Invalid address"); + } + const token = await getTokenFromAddress(getAddress(address)); + if (!token) { + throw new Error("Token not found"); + } + const requestor = await getUserDataForFid({ fid: token.requestedBy }); + return { + image: ( + + + + {`Token + + + + {truncate(token.name, 25)} + + + {truncate(token.ticker, 15)} + + + + + {token ? "Requested by" : "Who dis?"} + + + + + + + + + ), + buttons: [ + , + , + , + ], + imageOptions: { + aspectRatio: "1:1", + }, + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + return { + image: ( + + + + {`Token + + + + An error occured + + + {errorMessage} + + + + + {"1) What"} + + + + + + + + + ), + buttons: [ + , + , + , + ], + imageOptions: { + aspectRatio: "1:1", + }, + }; + } +}); + +export const GET = frameHandler; +export const POST = frameHandler; diff --git a/src/app/frames/token/route.tsx b/src/app/frames/token/route.tsx deleted file mode 100644 index d51d39a..0000000 --- a/src/app/frames/token/route.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* eslint-disable react/jsx-key */ -import { frames } from "@/app/frames/frames"; -import { appURL } from "@/app/utils"; -import { Button } from "frames.js/next"; -import { Container } from "@/app/frames/components/container"; -import { Column } from "@/app/frames/components/column"; -import { Text } from "@/app/frames/components/text"; -import { Row } from "@/app/frames/components/row"; -import { UserBanner } from "@/app/frames/components/user-banner"; -import { BrianClankerBanner } from "@/app/frames/components/brian-clanker-banner"; -import { BackgroundImage } from "../components/background-image"; - -const frameHandler = frames(async (ctx) => { - return { - image: ( - - - - {/* Copy Trade on Interface */} - - - - - Copy Trade on Interface - - - $copy - - - - - Requested by - - - - - - - - - ), - buttons: [ - , - , - , - ], - imageOptions: { - aspectRatio: "1:1", - }, - }; -}); - -export const GET = frameHandler; -export const POST = frameHandler; diff --git a/src/app/token/[address]/page.tsx b/src/app/token/[address]/page.tsx index 33c9ab0..7eebdc1 100644 --- a/src/app/token/[address]/page.tsx +++ b/src/app/token/[address]/page.tsx @@ -1,9 +1,41 @@ +import type { Metadata } from "next"; +import { fetchMetadata } from "frames.js/next"; +import { getAddress, isAddress } from "viem"; + +import { createExampleURL } from "@/app/utils"; import { TokenDetails } from "@/app/components/token-details"; +import { getTokenFromAddress } from "@/utils/db"; + +export async function generateMetadata({ + params, +}: { + params: { address: string }; +}): Promise { + return { + title: "Brian Clanker", + description: "Brian Clanker is better", + other: { + ...(await fetchMetadata( + createExampleURL(`/frames/token/${params.address}`) + )), + }, + }; +} + +export default async function TokenPage({ + params, +}: { + params: { address: string }; +}) { + const address = params.address; + if (!address || !isAddress(address, { strict: false })) { + return
Invalid address
; + } + const token = await getTokenFromAddress(getAddress(address)); -export default async function TokenPage() { return (
- +
); }