Skip to content

Commit

Permalink
fix frame and fe: fetch dynamic token from db
Browse files Browse the repository at this point in the history
  • Loading branch information
bianc8 committed Nov 29, 2024
1 parent f38e4cb commit b696e44
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 219 deletions.
17 changes: 5 additions & 12 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -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=""
Binary file modified bun.lockb
Binary file not shown.
Binary file added public/images/default-pfp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 0 additions & 59 deletions src/app/api/token/route.ts

This file was deleted.

36 changes: 8 additions & 28 deletions src/app/components/token-details.tsx
Original file line number Diff line number Diff line change
@@ -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<Token | undefined>(undefined);

if (!tokenAddress) {
return <div>Token not found</div>;
}

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 && (
<div className="flex flex-col-reverse md:flex-row w-full gap-2">
<div className="flex flex-col gap-2 bg-slate-400 p-2 rounded-xl w-full md:w-[60%]">
<div className="flex flex-row gap-2">
<img
src={`/images/copy.png`}
alt={token.name}
className="w-[60px] h-[60px] rounded-lg"
<Image
src={token.image || `/images/copy.png`}
alt={`${token.name} logo`}
className="w-[60px] h-[60px] rounded-lg object-contain"
width={60}
height={60}
/>
<div className="flex flex-col">
<p className="text-xl font-bold">{token.ticker}</p>
Expand Down
7 changes: 1 addition & 6 deletions src/app/frames/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<State>({
export const frames = createFrames({
basePath: "/frames",
initialState: { counter: 0 },
debug: process.env.NODE_ENV === "development",
middleware: [
farcasterHubContext(),
Expand Down
13 changes: 0 additions & 13 deletions src/app/frames/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,20 @@ 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: (
<div tw="flex flex-col">
<div tw="flex">Brian Clanker will destroy the current Clanker</div>
</div>
),
buttons: [
<Button action="post" target={{ pathname: "/", query: { op: "+" } }}>
Increment
</Button>,
<Button action="post" target={{ pathname: "/", query: { op: "-" } }}>
Decrement
</Button>,
<Button action="link" target={appURL()}>
External
</Button>,
<Button action="post" target={{ pathname: "/token" }}>
Token
</Button>,
],
state: { counter: counter },
};
});

Expand Down
211 changes: 211 additions & 0 deletions src/app/frames/token/[address]/route.tsx
Original file line number Diff line number Diff line change
@@ -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: (
<BackgroundImage
src={`${appURL()}/images/square_desert_painted.png`}
width="1080px"
height="1080px"
container="absolute"
>
<Container>
<Column tw="my-auto mx-auto bg-black/75 text-white rounded-xl w-[800px] py-[20px]">
<img
src={token.image || `${appURL()}/images/copy.png`}
tw="w-[760px] h-[760px] rounded-xl mx-auto"
alt={`Token ${token.name} logo`}
style={{
objectFit: "contain",
}}
/>
<Row tw="mt-8 justify-between w-[760px] mx-auto">
<Column tw="">
<Text
tw="text-3xl mb-4"
style={{
fontFamily: "ChakraPetch-Regular",
}}
>
{truncate(token.name, 25)}
</Text>
<Text
tw="text-4xl h-[60px] items-center"
style={{
fontFamily: "ChakraPetch-Bold",
}}
>
{truncate(token.ticker, 15)}
</Text>
</Column>
<Column tw="w-[350px] flex items-end">
<Text
tw="text-3xl mb-4"
style={{
fontFamily: "ChakraPetch-Regular",
}}
>
{token ? "Requested by" : "Who dis?"}
</Text>
<UserBanner
user={{
displayName: truncate(
`@${requestor?.username}` ||
token.requestedBy.toString(),
15
),
pfp: requestor?.profileImage || "",
}}
size="sm"
tw="text-4xl"
style={{
fontFamily: "ChakraPetch-Bold",
}}
/>
</Column>
</Row>
</Column>
</Container>
<BrianClankerBanner />
</BackgroundImage>
),
buttons: [
<Button action="link" target={`${appURL()}/token/${address}`}>
View
</Button>,
<Button
action="link"
target={`https://dexscreener.com/base/${address}`}
>
DexScreener
</Button>,
<Button action="link" target={`${appURL()}/token/${address}`}>
Buy
</Button>,
],
imageOptions: {
aspectRatio: "1:1",
},
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return {
image: (
<BackgroundImage
src={`${appURL()}/images/square_desert_painted.png`}
width="1080px"
height="1080px"
container="absolute"
>
<Container>
<Column tw="my-auto mx-auto bg-black/75 text-white rounded-xl w-[800px] py-[20px]">
<img
src={`${appURL()}/images/copy.png`}
tw="w-[760px] h-[760px] rounded-xl mx-auto"
alt={`Token not found logo`}
style={{
objectFit: "contain",
}}
/>
<Row tw="mt-8 justify-between w-[760px] mx-auto">
<Column tw="">
<Text
tw="text-3xl mb-4"
style={{
fontFamily: "ChakraPetch-Regular",
}}
>
An error occured
</Text>
<Text
tw="text-4xl h-[60px] items-center"
style={{
fontFamily: "ChakraPetch-Bold",
}}
>
{errorMessage}
</Text>
</Column>
<Column>
<Text
tw="text-3xl mb-4"
style={{
fontFamily: "ChakraPetch-Regular",
}}
>
{"1) What"}
</Text>
<UserBanner
user={{
displayName: "Brian Clanker",
pfp: `${appURL()}/images/default-pfp.png`,
}}
size="sm"
tw="text-4xl"
style={{
fontFamily: "ChakraPetch-Bold",
}}
/>
</Column>
</Row>
</Column>
</Container>
<BrianClankerBanner />
</BackgroundImage>
),
buttons: [
<Button action="link" target={`${appURL()}/token/${address}`}>
View
</Button>,
<Button
action="link"
target={`https://dexscreener.com/base/${address}`}
>
DexScreener
</Button>,
<Button action="link" target={`${appURL()}/token/${address}`}>
Buy
</Button>,
],
imageOptions: {
aspectRatio: "1:1",
},
};
}
});

export const GET = frameHandler;
export const POST = frameHandler;
Loading

0 comments on commit b696e44

Please sign in to comment.