diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 24be2ac..794a016 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -3,7 +3,7 @@ import dynamic from "next/dynamic"; import type { Session } from "next-auth" import { SessionProvider } from "next-auth/react" - +import { FrameProvider } from "~/components/providers/FrameProvider"; const WagmiProvider = dynamic( () => import("~/components/providers/WagmiProvider"), @@ -16,7 +16,9 @@ export function Providers({ session, children }: { session: Session | null, chil return ( - {children} + + {children} + ); diff --git a/src/components/Demo.tsx b/src/components/Demo.tsx index 7c1d673..13a4235 100644 --- a/src/components/Demo.tsx +++ b/src/components/Demo.tsx @@ -1,13 +1,10 @@ "use client"; -import { useEffect, useCallback, useState, useMemo } from "react"; +import { useCallback, useMemo, useState } from "react"; import { Input } from "../components/ui/input"; import { signIn, signOut, getCsrfToken } from "next-auth/react"; import sdk, { - AddFrame, - FrameNotificationDetails, SignIn as SignInCore, - type Context, } from "@farcaster/frame-sdk"; import { useAccount, @@ -27,30 +24,18 @@ import { truncateAddress } from "~/lib/truncateAddress"; import { base, degen, mainnet, optimism, unichain } from "wagmi/chains"; import { BaseError, UserRejectedRequestError } from "viem"; import { useSession } from "next-auth/react"; -import { createStore } from "mipd"; import { Label } from "~/components/ui/label"; +import { useFrame } from "~/components/providers/FrameProvider"; export default function Demo( { title }: { title?: string } = { title: "Frames v2 Demo" } ) { - const [isSDKLoaded, setIsSDKLoaded] = useState(false); - const [context, setContext] = useState(); + const { isSDKLoaded, context, added, notificationDetails, lastEvent, addFrame, addFrameResult } = useFrame(); const [isContextOpen, setIsContextOpen] = useState(false); const [txHash, setTxHash] = useState(null); - const [added, setAdded] = useState(false); - const [notificationDetails, setNotificationDetails] = - useState(null); - - const [lastEvent, setLastEvent] = useState(""); - - const [addFrameResult, setAddFrameResult] = useState(""); const [sendNotificationResult, setSendNotificationResult] = useState(""); - useEffect(() => { - setNotificationDetails(context?.client.notificationDetails ?? null); - }, [context]); - const { address, isConnected } = useAccount(); const chainId = useChainId(); @@ -101,68 +86,6 @@ export default function Demo( switchChain({ chainId: nextChain.id }); }, [switchChain, nextChain.id]); - useEffect(() => { - const load = async () => { - const context = await sdk.context; - setContext(context); - setAdded(context.client.added); - - sdk.on("frameAdded", ({ notificationDetails }) => { - setLastEvent( - `frameAdded${!!notificationDetails ? ", notifications enabled" : ""}` - ); - - setAdded(true); - if (notificationDetails) { - setNotificationDetails(notificationDetails); - } - }); - - sdk.on("frameAddRejected", ({ reason }) => { - setLastEvent(`frameAddRejected, reason ${reason}`); - }); - - sdk.on("frameRemoved", () => { - setLastEvent("frameRemoved"); - setAdded(false); - setNotificationDetails(null); - }); - - sdk.on("notificationsEnabled", ({ notificationDetails }) => { - setLastEvent("notificationsEnabled"); - setNotificationDetails(notificationDetails); - }); - sdk.on("notificationsDisabled", () => { - setLastEvent("notificationsDisabled"); - setNotificationDetails(null); - }); - - sdk.on("primaryButtonClicked", () => { - console.log("primaryButtonClicked"); - }); - - console.log("Calling ready"); - sdk.actions.ready({}); - - // Set up a MIPD Store, and request Providers. - const store = createStore(); - - // Subscribe to the MIPD Store. - store.subscribe((providerDetails) => { - console.log("PROVIDER DETAILS", providerDetails); - // => [EIP6963ProviderDetail, EIP6963ProviderDetail, ...] - }); - }; - if (sdk && !isSDKLoaded) { - console.log("Calling load"); - setIsSDKLoaded(true); - load(); - return () => { - sdk.removeAllListeners(); - }; - } - }, [isSDKLoaded]); - const openUrl = useCallback(() => { sdk.actions.openUrl("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); }, []); @@ -175,33 +98,6 @@ export default function Demo( sdk.actions.close(); }, []); - const addFrame = useCallback(async () => { - try { - setNotificationDetails(null); - - const result = await sdk.actions.addFrame(); - - if (result.notificationDetails) { - setNotificationDetails(result.notificationDetails); - } - setAddFrameResult( - result.notificationDetails - ? `Added, got notificaton token ${result.notificationDetails.token} and url ${result.notificationDetails.url}` - : "Added, got no notification details" - ); - } catch (error) { - if (error instanceof AddFrame.RejectedByUser) { - setAddFrameResult(`Not added: ${error.message}`); - } - - if (error instanceof AddFrame.InvalidDomainManifest) { - setAddFrameResult(`Not added: ${error.message}`); - } - - setAddFrameResult(`Error: ${error}`); - } - }, []); - const sendNotification = useCallback(async () => { setSendNotificationResult(""); if (!notificationDetails || !context) { @@ -713,7 +609,6 @@ function ViewProfile() { ); } - const renderError = (error: Error | null) => { if (!error) return null; if (error instanceof BaseError) { @@ -728,3 +623,4 @@ const renderError = (error: Error | null) => { return
{error.message}
; }; + diff --git a/src/components/providers/FrameProvider.tsx b/src/components/providers/FrameProvider.tsx new file mode 100644 index 0000000..c18d512 --- /dev/null +++ b/src/components/providers/FrameProvider.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import sdk, { type Context, type FrameNotificationDetails, AddFrame } from "@farcaster/frame-sdk"; +import { createStore } from "mipd"; +import React from "react"; + +interface FrameContextType { + isSDKLoaded: boolean; + context: Context.FrameContext | undefined; +} + +const FrameContext = React.createContext(undefined); + +export function useFrame() { + const [isSDKLoaded, setIsSDKLoaded] = useState(false); + const [context, setContext] = useState(); + const [added, setAdded] = useState(false); + const [notificationDetails, setNotificationDetails] = useState(null); + const [lastEvent, setLastEvent] = useState(""); + const [addFrameResult, setAddFrameResult] = useState(""); + + const addFrame = useCallback(async () => { + try { + setNotificationDetails(null); + + const result = await sdk.actions.addFrame(); + + if (result.notificationDetails) { + setNotificationDetails(result.notificationDetails); + } + setAddFrameResult( + result.notificationDetails + ? `Added, got notificaton token ${result.notificationDetails.token} and url ${result.notificationDetails.url}` + : "Added, got no notification details" + ); + } catch (error) { + if (error instanceof AddFrame.RejectedByUser) { + setAddFrameResult(`Not added: ${error.message}`); + } + + if (error instanceof AddFrame.InvalidDomainManifest) { + setAddFrameResult(`Not added: ${error.message}`); + } + + setAddFrameResult(`Error: ${error}`); + } + }, []); + + useEffect(() => { + const load = async () => { + const context = await sdk.context; + setContext(context); + setIsSDKLoaded(true); + + // Set up event listeners + sdk.on("frameAdded", ({ notificationDetails }) => { + console.log("Frame added", notificationDetails); + setAdded(true); + setNotificationDetails(notificationDetails ?? null); + setLastEvent("Frame added"); + }); + + sdk.on("frameAddRejected", ({ reason }) => { + console.log("Frame add rejected", reason); + setAdded(false); + setLastEvent(`Frame add rejected: ${reason}`); + }); + + sdk.on("frameRemoved", () => { + console.log("Frame removed"); + setAdded(false); + setLastEvent("Frame removed"); + }); + + sdk.on("notificationsEnabled", ({ notificationDetails }) => { + console.log("Notifications enabled", notificationDetails); + setNotificationDetails(notificationDetails ?? null); + setLastEvent("Notifications enabled"); + }); + + sdk.on("notificationsDisabled", () => { + console.log("Notifications disabled"); + setNotificationDetails(null); + setLastEvent("Notifications disabled"); + }); + + sdk.on("primaryButtonClicked", () => { + console.log("Primary button clicked"); + setLastEvent("Primary button clicked"); + }); + + // Call ready action + console.log("Calling ready"); + sdk.actions.ready({}); + + // Set up MIPD Store + const store = createStore(); + store.subscribe((providerDetails) => { + console.log("PROVIDER DETAILS", providerDetails); + }); + }; + + if (sdk && !isSDKLoaded) { + console.log("Calling load"); + setIsSDKLoaded(true); + load(); + return () => { + sdk.removeAllListeners(); + }; + } + }, [isSDKLoaded]); + + return { isSDKLoaded, context, added, notificationDetails, lastEvent, addFrame, addFrameResult }; +} + +export function FrameProvider({ children }: { children: React.ReactNode }) { + const { isSDKLoaded, context } = useFrame(); + + if (!isSDKLoaded) { + return
Loading...
; + } + + return ( + + {children} + + ); +} \ No newline at end of file