feat: update to use MiniAppProvider

This commit is contained in:
veganbeef 2025-05-30 16:35:31 -07:00
parent 9cd07170b9
commit 5420b8fca0
No known key found for this signature in database
5 changed files with 20 additions and 187 deletions

View File

@ -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));

View File

@ -1,6 +1,6 @@
{
"name": "@neynar/create-farcaster-mini-app",
"version": "1.3.0",
"version": "1.3.1",
"type": "module",
"private": false,
"access": "public",

View File

@ -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 (
<SessionProvider session={session}>
<WagmiProvider>
<FrameProvider>
<MiniAppProvider analyticsEnabled={true}>
<SafeFarcasterSolanaProvider endpoint={solanaEndpoint}>
{children}
</SafeFarcasterSolanaProvider>
</FrameProvider>
</MiniAppProvider>
</WagmiProvider>
</SessionProvider>
);

View File

@ -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<string | null>(null);
const [sendNotificationResult, setSendNotificationResult] = useState("");

View File

@ -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<void>;
close: () => Promise<void>;
added: boolean;
notificationDetails: FrameNotificationDetails | null;
lastEvent: string;
addFrame: () => Promise<void>;
addFrameResult: string;
}
const FrameContext = React.createContext<FrameContextType | undefined>(undefined);
export function useFrame() {
const [isSDKLoaded, setIsSDKLoaded] = useState(false);
const [context, setContext] = useState<Context.FrameContext>();
const [added, setAdded] = useState(false);
const [notificationDetails, setNotificationDetails] = useState<FrameNotificationDetails | null>(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 <div>Loading...</div>;
}
return (
<FrameContext.Provider value={frameContext}>
{children}
</FrameContext.Provider>
);
}