From 9fa9030473a4db848de2971695b462b158fd1570 Mon Sep 17 00:00:00 2001 From: Bhavya Gor Date: Sun, 30 Jun 2024 19:31:21 +0530 Subject: [PATCH] made orchestrator and frames work --- packages/nextjs/app/api/frame/[id]/route.ts | 6 +- .../app/api/orchestrator/[frameId]/route.ts | 16 +++-- packages/nextjs/app/frame/[frameId]/page.tsx | 37 +++++++--- packages/nextjs/components/ButtonEditor.tsx | 60 +++++++++++++--- packages/nextjs/components/ButtonsList.tsx | 8 ++- packages/nextjs/components/FarcasterModal.tsx | 72 +++++++++++++++++++ packages/nextjs/components/FrameEditor.tsx | 21 +++++- packages/nextjs/constants/index.ts | 5 +- packages/nextjs/providers/ProductProvider.tsx | 1 + packages/nextjs/services/frames.ts | 21 +++++- 10 files changed, 207 insertions(+), 40 deletions(-) create mode 100644 packages/nextjs/components/FarcasterModal.tsx diff --git a/packages/nextjs/app/api/frame/[id]/route.ts b/packages/nextjs/app/api/frame/[id]/route.ts index feefd20..1477b27 100644 --- a/packages/nextjs/app/api/frame/[id]/route.ts +++ b/packages/nextjs/app/api/frame/[id]/route.ts @@ -6,6 +6,7 @@ export async function GET(req: NextRequest, { params }: { params: { id: string } await connectDB(); const frame_id = params.id; const frame = await Frame.findById(frame_id); + console.log(frame); if (!frame) { return new NextResponse(JSON.stringify({ message: "Frame not found" }), { status: 404 }); } @@ -16,10 +17,7 @@ export async function PUT(req: NextRequest, { params }: { params: { id: string } await connectDB(); const frame_id = params.id; const payload = await req.json(); - - const { frameJson } = payload; - console.log({ frameJson }); - const frame = await Frame.findByIdAndUpdate(frame_id, { frameJson }, { new: true }); + const frame = await Frame.findByIdAndUpdate(frame_id, payload, { new: true }); if (!frame) { return new NextResponse(JSON.stringify({ message: "Frame not found" }), { status: 404 }); } diff --git a/packages/nextjs/app/api/orchestrator/[frameId]/route.ts b/packages/nextjs/app/api/orchestrator/[frameId]/route.ts index f57a05e..89e5fe9 100644 --- a/packages/nextjs/app/api/orchestrator/[frameId]/route.ts +++ b/packages/nextjs/app/api/orchestrator/[frameId]/route.ts @@ -1,8 +1,9 @@ import { NextRequest, NextResponse } from "next/server"; import { FrameRequest, getFrameHtmlResponse } from "@coinbase/onchainkit"; +import { APP_URL } from "~~/constants"; import Analytics from "~~/model/analytics"; import connectDB from "~~/services/connectDB"; -import { getFrameById } from "~~/services/frames"; +import { getFrameAtServer } from "~~/services/frames"; const storeAnalytics = async (body: FrameRequest, state: any) => { const analyticsEntry = new Analytics({ @@ -18,10 +19,11 @@ const storeAnalytics = async (body: FrameRequest, state: any) => { async function getResponse(req: NextRequest): Promise { await connectDB(); - const body: FrameRequest = await req.json(); + const url = req.nextUrl.pathname; + const frameId = url.replace(`/api/orchestrator`, ""); + const body = await req.json(); const state = JSON.parse(decodeURIComponent(body.untrustedData.state as string)); - const frameId = state?.frameId; - console.log(state); + // Creating Analytics for the frame asynchronously storeAnalytics(body, state).catch(err => console.error("Error Saving Analytics", err)); // Adding State for Button Press and Inputted Text on last frame @@ -30,7 +32,11 @@ async function getResponse(req: NextRequest): Promise { [`${frameId}ButtonPressed`]: body.untrustedData.buttonIndex, [`${frameId}InputtedText`]: body.untrustedData.inputText, }; - const nextFrame = await getFrameById(frameId); + const dbFrame = await getFrameAtServer(frameId); + if (!dbFrame) { + return new NextResponse(JSON.stringify({ message: "Frame not found" }), { status: 404 }); + } + const nextFrame = dbFrame.frameJson; nextFrame.state = { ...stateUpdate, }; diff --git a/packages/nextjs/app/frame/[frameId]/page.tsx b/packages/nextjs/app/frame/[frameId]/page.tsx index b38a47c..cbcced8 100644 --- a/packages/nextjs/app/frame/[frameId]/page.tsx +++ b/packages/nextjs/app/frame/[frameId]/page.tsx @@ -1,17 +1,32 @@ import { FrameMetadataType, getFrameMetadata } from "@coinbase/onchainkit"; -import { getFrameById } from "~~/services/frames"; +import { Metadata } from "next"; +import { APP_URL } from "~~/constants"; -export async function generateMetadata({ params }: any) { - const frameId = params.frameId; - const frame = await getFrameById(frameId); - return { - title: "Frames Made by Frames made easy!", - other: { - ...getFrameMetadata(frame as FrameMetadataType), - }, - }; -} +type Props = { + params: { frameId: string }; + searchParams: { [key: string]: string | string[] | undefined }; +}; + +export async function generateMetadata({ params, searchParams }: Props): Promise { + const id = params.frameId; + try { + const response = await fetch(`${APP_URL}/api/frame/${id}`); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + const data = await response.json(); + return { + title: "Frames Made by Frames made easy!", + description: "Frames Made by Frames made easy!", + other: { + ...getFrameMetadata(data.frameJson as FrameMetadataType), + }, + }; + } catch (error: any) { + throw new Error(error.message); + } +} export default function Page() { return ( <> diff --git a/packages/nextjs/components/ButtonEditor.tsx b/packages/nextjs/components/ButtonEditor.tsx index 5bc6d9d..3e1f5d8 100644 --- a/packages/nextjs/components/ButtonEditor.tsx +++ b/packages/nextjs/components/ButtonEditor.tsx @@ -1,12 +1,12 @@ import { useEffect, useState } from "react"; import InputField from "./InputField"; import { FrameButtonMetadata } from "@coinbase/onchainkit"; -import { IconButton, MenuItem, Select } from "@mui/material"; +import { IconButton, MenuItem, Select, TextField } from "@mui/material"; import { TrashIcon } from "@heroicons/react/24/outline"; +import { APP_URL } from "~~/constants"; import { useProductJourney } from "~~/providers/ProductProvider"; -import { getFrameById } from "~~/services/frames"; +import { getFrameById, removeUrl } from "~~/services/frames"; import { Frame } from "~~/types/commontypes"; -import { APP_URL } from "~~/constants"; interface ButtonEditorProps { button: FrameButtonMetadata; @@ -15,7 +15,7 @@ interface ButtonEditorProps { } const ButtonEditor = ({ button, onSave, onDelete }: ButtonEditorProps) => { - const { frames: dbFrames } = useProductJourney(); + const { frames: dbFrames, frame } = useProductJourney(); const [frames, setFrames] = useState(); useEffect(() => { @@ -46,6 +46,7 @@ const ButtonEditor = ({ button, onSave, onDelete }: ButtonEditorProps) => { onSave({ ...button, target: `${APP_URL}`+e.target.value as FrameButtonMetadata["target"] })} + value={removeUrl(button.target as string)} + onChange={e => + onSave({ ...button, target: (`${APP_URL}/api/orchestrator/` + e.target.value) as FrameButtonMetadata["target"] }) + } variant="outlined" > - {frames?.map((frame, index) => ( - - {frame.name} - - ))} + {frames?.map( + (f, index) => + f._id !== frame?._id && ( + + {f.name} + + ), + )} )} + {button.action === "link" && ( + onSave({ ...button, target: e.target.value })} + fullWidth + /> + )} + {button.action === "tx" && ( + <> + + + )} ); }; diff --git a/packages/nextjs/components/ButtonsList.tsx b/packages/nextjs/components/ButtonsList.tsx index 13a67d1..4327e3e 100644 --- a/packages/nextjs/components/ButtonsList.tsx +++ b/packages/nextjs/components/ButtonsList.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import ButtonEditor from "./ButtonEditor"; +import FarcasterModal from "./FarcasterModal"; import { FrameButtonMetadata, FrameMetadataType } from "@coinbase/onchainkit"; import { IconButton } from "@mui/material"; import { PlusIcon } from "@heroicons/react/24/outline"; @@ -9,7 +10,7 @@ import { notification } from "~~/utils/scaffold-eth"; const ButtonList = () => { const { currentFrame, setCurrentFrame, frame, saveFrame, deleteFrame } = useProductJourney(); const [activeButtonIndex, setActiveButtonIndex] = useState(0); - + const [open, setOpen] = useState(false); if (!currentFrame) return null; const handleAddButton = () => { // @ts-ignore @@ -32,6 +33,7 @@ const ButtonList = () => { const handleSaveFrame = async () => { notification.info("Frame saved successfully"); + console.log(frame.name); await saveFrame.mutateAsync({ _id: frame?._id as string, name: frame?.name as string, @@ -104,7 +106,11 @@ const ButtonList = () => { + + {open && setOpen(false)} />} ); }; diff --git a/packages/nextjs/components/FarcasterModal.tsx b/packages/nextjs/components/FarcasterModal.tsx new file mode 100644 index 0000000..30c5111 --- /dev/null +++ b/packages/nextjs/components/FarcasterModal.tsx @@ -0,0 +1,72 @@ +import React, { useEffect, useState } from "react"; +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Select, TextField } from "@mui/material"; +import { APP_URL } from "~~/constants"; +import { useProductJourney } from "~~/providers/ProductProvider"; +import { getFrameById } from "~~/services/frames"; +import { Frame } from "~~/types/commontypes"; +import { notification } from "~~/utils/scaffold-eth"; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; +} + +const FarcasterModal: React.FC = ({ isOpen, onClose }) => { + const { productQuery, frame } = useProductJourney(); + const [frames, setFrames] = useState(undefined); + const [currentFrameId, setCurrentFrameId] = useState(frame?._id as string); + + useEffect(() => { + if (productQuery.data) { + Promise.all(productQuery.data.frames.map(frame => getFrameById(frame))) + .then(data => setFrames(data)) + .catch(error => console.error("Error fetching frames:", error)); + } + }, [productQuery.data]); + + useEffect(() => { + setCurrentFrameId(frame?._id as string); + }, [frame]); + + const handleClose = () => { + setFrames(undefined); + setCurrentFrameId(""); + onClose(); + }; + return ( + + Select Starting Frame + + + + + + + + + ); +}; + +export default FarcasterModal; diff --git a/packages/nextjs/components/FrameEditor.tsx b/packages/nextjs/components/FrameEditor.tsx index a4557f5..f6baa88 100644 --- a/packages/nextjs/components/FrameEditor.tsx +++ b/packages/nextjs/components/FrameEditor.tsx @@ -1,15 +1,16 @@ import React, { useEffect, useState } from "react"; import ButtonList from "./ButtonsList"; +import FarcasterModal from "./FarcasterModal"; import InputField from "./InputField"; -import { MenuItem, Select } from "@mui/material"; +import { Button, MenuItem, Select, TextField } from "@mui/material"; import { useProductJourney } from "~~/providers/ProductProvider"; const FrameEditor = () => { - const { currentFrame, setCurrentFrame } = useProductJourney(); + const { frame, setFrame, currentFrame, setCurrentFrame } = useProductJourney(); const [imageUrlOption, setImageUrlOption] = useState("url"); const [htmlInput, setHtmlInput] = useState(""); const [imageUrl, setImageUrl] = useState(currentFrame?.image?.src || ""); - + const [open, setOpen] = useState(false); const getImageResponse = async (html: string) => { const response = await fetch(`/api/imageGeneration`, { body: JSON.stringify({ html }), @@ -57,6 +58,20 @@ const FrameEditor = () => { if (!currentFrame) return null; return (
+ { + if (!frame) return; + setFrame({ + ...frame, + name: e.target.value, + }); + }} + /> diff --git a/packages/nextjs/constants/index.ts b/packages/nextjs/constants/index.ts index 0ec5a88..4a02c25 100644 --- a/packages/nextjs/constants/index.ts +++ b/packages/nextjs/constants/index.ts @@ -1,9 +1,6 @@ import { FrameMetadataType } from "@coinbase/onchainkit"; -import { ButtonProps } from "@mui/material"; -export const APP_URL = "https://cd4d-103-216-213-71.ngrok-free.app"; -// test using ${APP_URL}/frame/${frameID} -// +export const APP_URL = "https://950a-103-216-213-71.ngrok-free.app"; export const txFrame = { buttons: [ { diff --git a/packages/nextjs/providers/ProductProvider.tsx b/packages/nextjs/providers/ProductProvider.tsx index 2ceff9d..e76507d 100644 --- a/packages/nextjs/providers/ProductProvider.tsx +++ b/packages/nextjs/providers/ProductProvider.tsx @@ -102,6 +102,7 @@ const useProduct = () => { const saveFrame = useMutation({ mutationFn: async (frame: Frame) => { + console.log({frame}) const response = await fetch(`/api/frame/${frame._id}`, { method: "PUT", headers: { diff --git a/packages/nextjs/services/frames.ts b/packages/nextjs/services/frames.ts index 09ff77e..e7f64ca 100644 --- a/packages/nextjs/services/frames.ts +++ b/packages/nextjs/services/frames.ts @@ -1,4 +1,4 @@ -import { DEFAULT_FRAME } from "~~/constants"; +import { APP_URL, DEFAULT_FRAME } from "~~/constants"; import { Frame, Journey } from "~~/types/commontypes"; export const getFrameById = async (id: string) => { @@ -10,10 +10,29 @@ export const getFrameById = async (id: string) => { const data = await response.json(); return data; } catch (error: any) { + console.log(error); throw new Error(error.message); } }; +export const getFrameAtServer = async (id: string) => { + try { + const response = await fetch(`${APP_URL}/api/frame/${id}`); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + const data = await response.json(); + return data; + } catch (error: any) { + console.log(error); + throw new Error(error.message); + } +}; + +export const removeUrl = (url: string) => { + if (!url) return ""; + return url.replace(`${APP_URL}/api/orchestrator/`, ""); +}; export const createFrame = async (frame: Omit) => { try { const response = await fetch(`/api/frame`, {