Skip to content

Commit

Permalink
refactor: Frame uses FrameMetadata
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasc committed Mar 7, 2024
1 parent 6ef37ea commit 3789889
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 43 deletions.
62 changes: 25 additions & 37 deletions framegear/components/Frame/Frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAtom } from 'jotai';
import { ChangeEvent, PropsWithChildren, useCallback, useMemo, useState } from 'react';
import { ExternalLinkIcon, ResetIcon, RocketIcon } from '@radix-ui/react-icons';
import { useRedirectModal } from '@/components/RedirectModalContext/RedirectModalContext';
import { FrameMetadataWithImageObject } from '@/utils/frameResultToFrameMetadata';

export function Frame() {
const [results] = useAtom(frameResultsAtom);
Expand All @@ -21,33 +22,11 @@ export function Frame() {
return <ValidFrame tags={latestFrame.tags} />;
}

function ValidFrame({ tags }: { tags: Record<string, string> }) {
function ValidFrame({ tags }: { tags: FrameMetadataWithImageObject }) {
const [inputText, setInputText] = useState('');
const { image, imageAspectRatioClassname, input, buttons } = useMemo(() => {
const image = tags['fc:frame:image'];
const imageAspectRatioClassname =
tags['fc:frame:image:aspect_ratio'] === '1:1' ? 'aspect-square' : 'aspect-[1.91/1]';
const input = tags['fc:frame:input:text'];
// TODO: when debugger is live we will also need to extract actions, etc.
const buttons = [1, 2, 3, 4].map((index) => {
const key = `fc:frame:button:${index}`;
const actionKey = `${key}:action`;
const targetKey = `${key}:target`;
const value = tags[key];
const action = tags[actionKey] || 'post';
const target = tags[targetKey] || tags['fc:frame:post_url'];

// If value exists, we can return the whole object (incl. default values).
// If it doesn't, then the truth is there is no button.
return value ? { key, value, action, target, index } : undefined;
});
return {
image,
imageAspectRatioClassname,
input,
buttons,
};
}, [tags]);
const { image, input, buttons } = tags;
const imageAspectRatioClassname =
tags.image.aspectRatio === '1:1' ? 'aspect-square' : 'aspect-[1.91/1]';

const handleInputChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => setInputText(e.target.value),
Expand All @@ -59,23 +38,28 @@ function ValidFrame({ tags }: { tags: Record<string, string> }) {
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className={`w-full rounded-t-xl ${imageAspectRatioClassname} object-cover`}
src={image}
src={image.src}
alt=""
/>
<div className="bg-button-gutter-light dark:bg-content-light flex flex-col gap-2 rounded-b-xl px-4 py-2">
{!!input && (
<input
className="bg-input-light border-light rounded-lg border p-2 text-black"
type="text"
placeholder={input}
placeholder={input.text}
onChange={handleInputChange}
/>
)}
<div className="flex flex-wrap gap-4">
{buttons.map((button) =>
{buttons?.map((button, index) =>
button ? (
<FrameButton inputText={inputText} key={button.key} button={button}>
{button.value}
<FrameButton
inputText={inputText}
key={button.label}
index={index + 1}
button={button}
>
{button.label}
</FrameButton>
) : null,
)}
Expand All @@ -97,7 +81,9 @@ function PlaceholderFrame() {
<div className="flex flex-col">
<div className="bg-farcaster flex aspect-[1.91/1] w-full rounded-t-xl"></div>
<div className="bg-button-gutter-light dark:bg-content-light flex flex-wrap gap-2 rounded-b-xl px-4 py-2">
<FrameButton inputText="">Get Started</FrameButton>
<FrameButton index={1} inputText="">
Get Started
</FrameButton>
</div>
</div>
);
Expand All @@ -106,10 +92,11 @@ function PlaceholderFrame() {
function FrameButton({
children,
button,
index,
inputText,
}: PropsWithChildren<{
// TODO: this type should probably be extracted
button?: { key: string; value: string; action: string; target: string; index: number };
button?: NonNullable<FrameMetadataWithImageObject['buttons']>[0];
index: number;
inputText: string;
}>) {
const { openModal } = useRedirectModal();
Expand All @@ -121,8 +108,8 @@ function FrameButton({
// TODO: collect user options (follow, like, etc.) and include
const confirmAction = async () => {
const result = await postFrame({
buttonIndex: button.index,
url: button.target,
buttonIndex: index,
url: button.target!,
// TODO: make these user-input-driven
castId: {
fid: 0,
Expand All @@ -133,6 +120,7 @@ function FrameButton({
messageHash: '0xthisisnotreal',
network: 0,
timestamp: 0,
state: '',
});
// TODO: handle when result is not defined
if (result) {
Expand All @@ -152,7 +140,7 @@ function FrameButton({
openModal(onConfirm);
}
// TODO: implement other actions (mint, etc.)
}, [button?.action, button?.index, button?.target, inputText, openModal, setResults]);
}, [button?.action, button?.target, index, inputText, openModal, setResults]);

const buttonIcon = useMemo(() => {
switch (button?.action) {
Expand Down
7 changes: 6 additions & 1 deletion framegear/utils/fetchFrame.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { frameResultToFrameMetadata } from './frameResultToFrameMetadata';
import { parseHtml } from './parseHtml';

export async function fetchFrame(url: string) {
Expand All @@ -11,5 +12,9 @@ export async function fetchFrame(url: string) {

const json = (await response.json()) as { html: string };
const html = json.html;
return parseHtml(html);
const parsedHtml = parseHtml(html);
return {
...parsedHtml,
tags: frameResultToFrameMetadata(parsedHtml.tags),
};
}
22 changes: 18 additions & 4 deletions framegear/utils/frameResultToFrameMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { FrameMetadataType } from '@coinbase/onchainkit';
import { FrameImageMetadata, FrameMetadataType } from '@coinbase/onchainkit';

export function frameResultToFrameMetadata(result: Record<string, string>): FrameMetadataType {
export type FrameMetadataWithImageObject = FrameMetadataType & {
image: FrameImageMetadata;
};

export function frameResultToFrameMetadata(
result: Record<string, string>,
): FrameMetadataWithImageObject {
const buttons = [1, 2, 3, 4].map((idx) =>
result[`fc:frame:button:${idx}`]
? {
Expand All @@ -10,7 +16,8 @@ export function frameResultToFrameMetadata(result: Record<string, string>): Fram
}
: undefined,
);
const image = result['fc:frame:image'];
const imageSrc = result['fc:frame:image'];
const imageAspectRatio = result['fc:frame:image:aspect_ratio'];
const inputText = result['fc:frame:input'];
const input = inputText ? { text: inputText } : undefined;
const postUrl = result['fc:frame:post_url'];
Expand All @@ -19,5 +26,12 @@ export function frameResultToFrameMetadata(result: Record<string, string>): Fram
const refreshPeriod = rawRefreshPeriod ? parseInt(rawRefreshPeriod, 10) : undefined;
const state = rawState ? JSON.parse(result['fc:frame:state']) : undefined;

return { buttons: buttons as any, image, input, postUrl, state, refreshPeriod };
return {
buttons: buttons as any,
image: { src: imageSrc, aspectRatio: imageAspectRatio as any },
input,
postUrl,
state,
refreshPeriod,
};
}
7 changes: 6 additions & 1 deletion framegear/utils/postFrame.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FrameRequest, MockFrameRequestOptions } from '@coinbase/onchainkit';
import { parseHtml } from './parseHtml';
import { frameResultToFrameMetadata } from './frameResultToFrameMetadata';

type FrameData = FrameRequest['untrustedData'];

Expand All @@ -23,5 +24,9 @@ export async function postFrame(frameData: FrameData, options?: MockFrameRequest
}

const html = json.html;
return parseHtml(html);
const parsedHtml = parseHtml(html);
return {
...parsedHtml,
tags: frameResultToFrameMetadata(parsedHtml.tags),
};
}

0 comments on commit 3789889

Please sign in to comment.