Skip to content

Commit

Permalink
made orchestrator and frames work
Browse files Browse the repository at this point in the history
  • Loading branch information
bhavyagor12 committed Jun 30, 2024
1 parent 9b04dee commit 9fa9030
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 40 deletions.
6 changes: 2 additions & 4 deletions packages/nextjs/app/api/frame/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
Expand All @@ -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 });
}
Expand Down
16 changes: 11 additions & 5 deletions packages/nextjs/app/api/orchestrator/[frameId]/route.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -18,10 +19,11 @@ const storeAnalytics = async (body: FrameRequest, state: any) => {

async function getResponse(req: NextRequest): Promise<NextResponse> {
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
Expand All @@ -30,7 +32,11 @@ async function getResponse(req: NextRequest): Promise<NextResponse> {
[`${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,
};
Expand Down
37 changes: 26 additions & 11 deletions packages/nextjs/app/frame/[frameId]/page.tsx
Original file line number Diff line number Diff line change
@@ -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<Metadata> {
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 (
<>
Expand Down
60 changes: 49 additions & 11 deletions packages/nextjs/components/ButtonEditor.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Frame[] | undefined>();

useEffect(() => {
Expand Down Expand Up @@ -46,6 +46,7 @@ const ButtonEditor = ({ button, onSave, onDelete }: ButtonEditorProps) => {
<Select
id="buttonAction"
value={button.action}
// @ts-ignore
onChange={e => onSave({ ...button, action: e.target.value as FrameButtonMetadata["action"] })}
variant="outlined"
>
Expand All @@ -57,17 +58,54 @@ const ButtonEditor = ({ button, onSave, onDelete }: ButtonEditorProps) => {
{button.action === "post" && (
<Select
id="post"
value={button.target}
onChange={e => 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) => (
<MenuItem key={index} value={frame._id}>
{frame.name}
</MenuItem>
))}
{frames?.map(
(f, index) =>
f._id !== frame?._id && (
<MenuItem key={index} value={f._id}>
{f.name}
</MenuItem>
),
)}
</Select>
)}
{button.action === "link" && (
<TextField
id="link"
label="Enter Link"
variant="outlined"
value={button.target}
onChange={e => onSave({ ...button, target: e.target.value })}
fullWidth
/>
)}
{button.action === "tx" && (
<>
<Select
id="post"
value={button.target}
onChange={e =>
onSave({ ...button, postUrl: `${APP_URL}/api/orchestrator/` + e.target.value, target: `${APP_URL}/api/tx` })
}
variant="outlined"
>
{frames?.map((f, index) => (
<>
{f._id !== frame?._id && (
<MenuItem key={index} value={f._id}>
{f.name}
</MenuItem>
)}
</>
))}
</Select>
</>
)}
</div>
);
};
Expand Down
8 changes: 7 additions & 1 deletion packages/nextjs/components/ButtonsList.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -9,7 +10,7 @@ import { notification } from "~~/utils/scaffold-eth";
const ButtonList = () => {
const { currentFrame, setCurrentFrame, frame, saveFrame, deleteFrame } = useProductJourney();
const [activeButtonIndex, setActiveButtonIndex] = useState<number>(0);

const [open, setOpen] = useState(false);
if (!currentFrame) return null;
const handleAddButton = () => {
// @ts-ignore
Expand All @@ -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,
Expand Down Expand Up @@ -104,7 +106,11 @@ const ButtonList = () => {
<button onClick={handleSaveFrame} className="btn btn-success mt-2 flex items-center justify-center">
Save Frame
</button>
<button onClick={() => setOpen(!open)} className="btn btn-primary mt-2 flex items-center justify-center">
Export Product
</button>
</div>
{open && <FarcasterModal isOpen={open} onClose={() => setOpen(false)} />}
</div>
);
};
Expand Down
72 changes: 72 additions & 0 deletions packages/nextjs/components/FarcasterModal.tsx
Original file line number Diff line number Diff line change
@@ -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<ModalProps> = ({ isOpen, onClose }) => {
const { productQuery, frame } = useProductJourney();
const [frames, setFrames] = useState<Frame[] | undefined>(undefined);
const [currentFrameId, setCurrentFrameId] = useState<string>(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 (
<Dialog open={isOpen} onClose={handleClose} className="fixed z-50 overflow-y-auto w-[100%]">
<DialogTitle className="text-center">Select Starting Frame</DialogTitle>
<DialogContent className="flex flex-col gap-4 w-[600px]">
<Select
id="post"
value={currentFrameId}
onChange={e => setCurrentFrameId(e.target.value as string)}
variant="outlined"
>
{frames?.map((frame, index) => (
<MenuItem key={index} value={frame._id}>
{frame.name}
</MenuItem>
))}
</Select>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="secondary" className="text-gray-500 hover:text-gray-600">
Cancel
</Button>
<Button
className="btn btn-success"
variant="contained"
onClick={() => {
window.navigator.clipboard.writeText(`${APP_URL}/frame/${currentFrameId}`);
notification.success("Link copied to clipboard");
}}
>
Link For Warpcast
</Button>
</DialogActions>
</Dialog>
);
};

export default FarcasterModal;
21 changes: 18 additions & 3 deletions packages/nextjs/components/FrameEditor.tsx
Original file line number Diff line number Diff line change
@@ -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 }),
Expand Down Expand Up @@ -57,6 +58,20 @@ const FrameEditor = () => {
if (!currentFrame) return null;
return (
<div className="bg-white flex flex-col gap-4 p-4">
<TextField
id="outlined-basic"
label="Frame Name"
variant="outlined"
value={frame?.name}
fullWidth
onChange={e => {
if (!frame) return;
setFrame({
...frame,
name: e.target.value,
});
}}
/>
<label htmlFor="imageInput" className="block text-sm font-medium text-gray-700">
Image/HTML{" "}
</label>
Expand Down
5 changes: 1 addition & 4 deletions packages/nextjs/constants/index.ts
Original file line number Diff line number Diff line change
@@ -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: [
{
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/providers/ProductProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading

0 comments on commit 9fa9030

Please sign in to comment.