Skip to content

Commit

Permalink
v2
Browse files Browse the repository at this point in the history
  • Loading branch information
julien51 committed Feb 4, 2024
1 parent 96cabec commit b0f7d33
Show file tree
Hide file tree
Showing 14 changed files with 329 additions and 237 deletions.
2 changes: 1 addition & 1 deletion src/app/Components/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Renders a message, any message!
export const Message = ({ content }: { content: string }) => {
const classes =
"flex flex-wrap flex-col h-full justify-center bg-white text-5xl p-10 items-center ";
"flex flex-wrap flex-col h-full justify-center w-[1200px] bg-white text-5xl p-10 items-center ";
return (
<div tw={classes} className={classes}>
{content}
Expand Down
129 changes: 129 additions & 0 deletions src/app/api/[message]/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { getMessage } from "@/lib/messages";
import { getUserAddresses } from "@/lib/farcaster";
import { meetsRequirement } from "@/lib/unlock";
import { getImage } from "@/lib/utils";

interface Button {
label: string;
action: string;
}

export const renderMessageForFid = async (
origin: string,
messageId: string,
fid: string | null
) => {
const message = await getMessage(messageId);
if (!message) {
return new Response("Message not found", { status: 404 });
}

if (!fid) {
return render(origin, message, "pending", `${origin}/api/${message.id}/`, [
{
label: "Reveal 🔓",
action: "post",
},
]);
}

const addresses = await getUserAddresses(fid);
if (addresses.length === 0) {
return render(origin, message, "no-wallet");
}

const isMember = (
await Promise.all(
addresses.map((userAddress: string) => {
return meetsRequirement(
userAddress as `0x${string}`,
message.frame.gate
);
})
)
).some((balance) => !!balance);

if (isMember) {
// No action (yet!)
return render(origin, message, "clear");
} else if (message.frame.checkoutUrl) {
// Show the checkout button
return render(
origin,
message,
"hidden",
`${origin}/api/${message.id}/checkout`,
[
{
label: "Get the tokens!",
action: "post_redirect",
},
]
);
} else {
// No checkout button!
return render(origin, message, "hidden");
}
};

export const render = async (
base: string,
message: { id: string; author: string; frame: any },
status: "pending" | "hidden" | "clear" | "no-wallet",
postUrl?: string,
buttons?: Button[]
) => {
const image = getImage(base, message, status);
return new Response(
`<!DOCTYPE html>
<html>
<head>
<meta name="description" content="${message.frame.description}">
<meta property="og:description" content="${message.frame.description}">
<meta property="og:url" content="${base}/c/${message.id}">
<meta property="og:image" content="${base}/api/og/${message.id}">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="${message.frame.description}">
<meta name="twitter:image" content="${base}/api/og/${message.id}">
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${image}" />
${
postUrl
? `<meta property="fc:frame:post_url" content="${postUrl}" />`
: ``
}
${(buttons || [])
.map((button, i) => {
return `<meta property="fc:frame:button:${i + 1}" content="${
button.label
}" />
<meta property="fc:frame:button:${i + 1}:action" content="${
button.action
}" />`;
})
.join("\n")}
</head>
<body>
<img src="${image}" />
<p>
${(buttons || [])
.map((button) => {
return `<button>${button.label}</button>`;
})
.join("<br />")}
</p>
</body>
</html>`,
{
headers: {
"Content-Type": "text/html",
},
status: 200,
}
);
};
105 changes: 17 additions & 88 deletions src/app/api/[message]/route.tsx
Original file line number Diff line number Diff line change
@@ -1,105 +1,34 @@
import { getMessage } from "@/lib/messages";
import { getUserAddresses, validateMessage } from "@/lib/farcaster";
import { balanceOf } from "@/lib/unlock";
import { getImage } from "@/lib/utils";
import { AppConfig } from "@/app/AppConfig";
import { validateMessage } from "@/lib/farcaster";
import { renderMessageForFid } from "./render";

export async function POST(
request: Request,
{ params }: { params: { message: string } }
) {
const message = await getMessage(params.message);
if (!message) {
return new Response("Message not found", { status: 404 });
}
const u = new URL(request.url);
const body = await request.json();
const { trustedData } = body;

if (!trustedData) {
return new Response("Missing trustedData", { status: 441 });
}
const fcMessage = await validateMessage(trustedData.messageBytes);
if (!fcMessage.valid) {
if (!fcMessage.valid || !fcMessage.message.data.fid) {
return new Response("Invalid message", { status: 442 });
}

const addresses = await getUserAddresses(fcMessage.message.data.fid);
if (addresses.length === 0) {
return new Response(
`<!DOCTYPE html>
<html>
<head>
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${new URL(
`${AppConfig.siteUrl}/api/og/no-wallet`
).toString()}" />
</head>
</html>`
);
}

const balances = await Promise.all(
addresses.map((userAddress: string) => {
return balanceOf(
userAddress as `0x${string}`,
message.frame.gate.contract as `0x${string}`,
message.frame.gate.network
);
})
return renderMessageForFid(
u.origin,
params.message,
fcMessage.message.data.fid
);
}

const isMember = balances.some((balance) => balance > 0);

if (isMember) {
return new Response(
`<!DOCTYPE html>
<html>
<head>
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${getImage(
message,
"clear"
)}" />
</head>
</html>`
);
} else if (message.frame.checkoutUrl) {
return new Response(
`<!DOCTYPE html>
<html>
<head>
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${getImage(
message,
"hidden"
)}" />
<meta property="fc:frame:button:1" content="Get Membership NFT!" />
<meta property="fc:frame:button:1:action" content="post_redirect" />
<meta property="fc:frame:post_url" content="${
AppConfig.siteUrl
}/api/${message.id}/checkout" />
</head>
</html>`,
{
status: 200,
}
);
} else {
return new Response(
`<!DOCTYPE html>
<html>
<head>
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${getImage(
message,
"hidden"
)}" />
</head>
</html>`,
{
status: 200,
}
);
}
// REMOVE ME FOR PROD!
export async function GET(
request: Request,
{ params }: { params: { message: string } }
) {
const u = new URL(request.url);
const fid = u.searchParams.get("fid");
return renderMessageForFid(u.origin, params.message, fid);
}
8 changes: 4 additions & 4 deletions src/app/api/og/[message]/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export async function GET(
{ params }: { params: { message: string } }
) {
const u = new URL(request.url);

let content = "";
if (params.message === "no-wallet") {
if (u.searchParams.get("state") === "no-wallet") {
content = "Please link your farcaster account to a wallet!";
} else {
const message = await getMessage(params.message);
Expand All @@ -19,8 +18,9 @@ export async function GET(
content = message.frame.description;
if (u.searchParams.get("state") === "clear") {
content = message.frame.body;
} else if (u.searchParams.get("state") === "hidden") {
content = "You need to get a membership! Click below ⬇️";
} else if (u.searchParams.get("state") === "denied") {
content =
message.frame.denied || "You need to get a membership! Click below ⬇️";
}
}
return new ImageResponse(<Message content={content} />, {
Expand Down
76 changes: 0 additions & 76 deletions src/app/c/[message]/page.tsx

This file was deleted.

10 changes: 10 additions & 0 deletions src/app/c/[message]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { renderMessageForFid } from "@/app/api/[message]/render";

export async function GET(
request: Request,
{ params }: { params: { message: string } }
) {
const u = new URL(request.url);
const fid = u.searchParams.get("fid");
return renderMessageForFid(u.origin, params.message, fid);
}
8 changes: 4 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={inter.className}>
<main className="p-8">{children}</main>
<footer className="prose">
<p className="p-10">
<body className={`${inter.className} flex flex-col min-h-screen`}>
<main className="md:p-8 grow">{children}</main>
<footer className="prose p-2 md:p-10 mt-8">
<p className="">
<Link
target="_blank"
className="link"
Expand Down
Loading

0 comments on commit b0f7d33

Please sign in to comment.