diff --git a/bin/init.js b/bin/init.js index 388d81b..4bc70b9 100644 --- a/bin/init.js +++ b/bin/init.js @@ -329,6 +329,7 @@ export async function init() { "@farcaster/frame-sdk": ">=0.0.31 <1.0.0", "@farcaster/frame-wagmi-connector": ">=0.0.19 <1.0.0", "@farcaster/mini-app-solana": "^0.0.5", + "@neynar/react": "^0.9.7", "@radix-ui/react-label": "^2.1.1", "@solana/wallet-adapter-react": "^0.15.38", "@tanstack/react-query": "^5.61.0", @@ -363,10 +364,9 @@ export async function init() { "typescript": "^5" }; - // Add Neynar dependencies if selected + // Add Neynar SDK if selected if (useNeynar) { packageJson.dependencies['@neynar/nodejs-sdk'] = '^2.19.0'; - packageJson.dependencies['@neynar/react'] = '^0.9.7'; } fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); diff --git a/package.json b/package.json index 599fed0..968e0dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@neynar/create-farcaster-mini-app", - "version": "1.3.0", + "version": "1.3.1", "type": "module", "private": false, "access": "public", diff --git a/src/app/providers.tsx b/src/app/providers.tsx index e506826..7b5160a 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -1,9 +1,9 @@ "use client"; import dynamic from "next/dynamic"; -import type { Session } from "next-auth" -import { SessionProvider } from "next-auth/react" -import { FrameProvider } from "~/components/providers/FrameProvider"; +import type { Session } from "next-auth"; +import { SessionProvider } from "next-auth/react"; +import { MiniAppProvider } from "@neynar/react"; import { SafeFarcasterSolanaProvider } from "~/components/providers/SafeFarcasterSolanaProvider"; const WagmiProvider = dynamic( @@ -18,11 +18,11 @@ export function Providers({ session, children }: { session: Session | null, chil return ( - + {children} - + ); diff --git a/src/components/Demo.tsx b/src/components/Demo.tsx index 980b046..4c46d81 100644 --- a/src/components/Demo.tsx +++ b/src/components/Demo.tsx @@ -29,14 +29,24 @@ 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 { useMiniApp } from "@neynar/react"; import { Label } from "~/components/ui/label"; -import { useFrame } from "~/components/providers/FrameProvider"; import { PublicKey, SystemProgram, Transaction } from '@solana/web3.js'; export default function Demo( { title }: { title?: string } = { title: "Frames v2 Demo" } ) { - const { isSDKLoaded, context, added, notificationDetails, lastEvent, addFrame, addFrameResult, openUrl, close } = useFrame(); + const { + isSDKLoaded, + context, + added, + notificationDetails, + lastEvent, + addFrame, + addFrameResult, + openUrl, + close, + } = useMiniApp(); const [isContextOpen, setIsContextOpen] = useState(false); const [txHash, setTxHash] = useState(null); const [sendNotificationResult, setSendNotificationResult] = useState(""); diff --git a/src/components/providers/FrameProvider.tsx b/src/components/providers/FrameProvider.tsx deleted file mode 100644 index a4d29d9..0000000 --- a/src/components/providers/FrameProvider.tsx +++ /dev/null @@ -1,177 +0,0 @@ -"use client"; - -import { useEffect, useState, useCallback } from "react"; -import sdk, { type Context, type FrameNotificationDetails, AddMiniApp } from "@farcaster/frame-sdk"; -import { createStore } from "mipd"; -import React from "react"; -import { logEvent } from "../../lib/amplitude"; - -interface FrameContextType { - isSDKLoaded: boolean; - context: Context.FrameContext | undefined; - openUrl: (url: string) => Promise; - close: () => Promise; - added: boolean; - notificationDetails: FrameNotificationDetails | null; - lastEvent: string; - addFrame: () => Promise; - addFrameResult: string; -} - -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(""); - - // SDK actions only work in mini app clients, so this pattern supports browser actions as well - const openUrl = useCallback(async (url: string) => { - if (context) { - await sdk.actions.openUrl(url); - } else { - window.open(url, '_blank'); - } - }, [context]); - - const close = useCallback(async () => { - if (context) { - await sdk.actions.close(); - } else { - window.close(); - } - }, [context]); - - 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 AddMiniApp.RejectedByUser || error instanceof AddMiniApp.InvalidDomainManifest) { - setAddFrameResult(`Not added: ${error.message}`); - }else { - setAddFrameResult(`Error: ${error}`); - } - } - }, []); - - useEffect(() => { - const load = async () => { - const context = await sdk.context; - setContext(context); - setIsSDKLoaded(true); - - const amplitudeBaseEvent = { - fid: context.user.fid, - username: context.user.username, - clientFid: context.client.clientFid, - }; - const amplitudeUserId = `${context.user.fid}-${context.client.clientFid}`; - - logEvent("Frame Opened", { - ...amplitudeBaseEvent, - location: context.location, - added: context.client.added, - }, amplitudeUserId); - - // Set up event listeners - sdk.on("frameAdded", ({ notificationDetails }) => { - console.log("Frame added", notificationDetails); - setAdded(true); - setNotificationDetails(notificationDetails ?? null); - setLastEvent("Frame added"); - logEvent("Frame Added", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("frameAddRejected", ({ reason }) => { - console.log("Frame add rejected", reason); - setAdded(false); - setLastEvent(`Frame add rejected: ${reason}`); - logEvent("Frame Add Rejected", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("frameRemoved", () => { - console.log("Frame removed"); - setAdded(false); - setLastEvent("Frame removed"); - logEvent("Frame Removed", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("notificationsEnabled", ({ notificationDetails }) => { - console.log("Notifications enabled", notificationDetails); - setNotificationDetails(notificationDetails ?? null); - setLastEvent("Notifications enabled"); - logEvent("Notifications Enabled", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("notificationsDisabled", () => { - console.log("Notifications disabled"); - setNotificationDetails(null); - setLastEvent("Notifications disabled"); - logEvent("Notifications Disabled", amplitudeBaseEvent, amplitudeUserId); - }); - - 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, - openUrl, - close, - }; -} - -export function FrameProvider({ children }: { children: React.ReactNode }) { - const frameContext = useFrame(); - - if (!frameContext.isSDKLoaded) { - return
Loading...
; - } - - return ( - - {children} - - ); -} \ No newline at end of file