mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-15 23:58:56 -05:00
Merge pull request #7 from neynarxyz/veganbeef/mini-app-provider
feat: update to use MiniAppProvider [NEYN-5381]
This commit is contained in:
commit
42152c3492
14
bin/init.js
14
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": "^1.2.2",
|
||||
"@radix-ui/react-label": "^2.1.1",
|
||||
"@solana/wallet-adapter-react": "^0.15.38",
|
||||
"@tanstack/react-query": "^5.61.0",
|
||||
@ -338,10 +339,10 @@ export async function init() {
|
||||
"dotenv": "^16.4.7",
|
||||
"lucide-react": "^0.469.0",
|
||||
"mipd": "^0.0.7",
|
||||
"next": "15.0.3",
|
||||
"next": "^15",
|
||||
"next-auth": "^4.24.11",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"viem": "^2.23.6",
|
||||
@ -351,8 +352,8 @@ export async function init() {
|
||||
|
||||
packageJson.devDependencies = {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"crypto": "^1.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.0.3",
|
||||
@ -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));
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@neynar/create-farcaster-mini-app",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"type": "module",
|
||||
"private": false,
|
||||
"access": "public",
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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("");
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { createConfig, http, WagmiProvider } from "wagmi";
|
||||
import { base, degen, mainnet, optimism, unichain } from "wagmi/chains";
|
||||
import { base, degen, mainnet, optimism, unichain, celo } from "wagmi/chains";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { farcasterFrame } from "@farcaster/frame-wagmi-connector";
|
||||
import { coinbaseWallet, metaMask } from 'wagmi/connectors';
|
||||
@ -42,13 +42,14 @@ function useCoinbaseWalletAutoConnect() {
|
||||
}
|
||||
|
||||
export const config = createConfig({
|
||||
chains: [base, optimism, mainnet, degen, unichain],
|
||||
chains: [base, optimism, mainnet, degen, unichain, celo],
|
||||
transports: {
|
||||
[base.id]: http(),
|
||||
[optimism.id]: http(),
|
||||
[mainnet.id]: http(),
|
||||
[degen.id]: http(),
|
||||
[unichain.id]: http(),
|
||||
[celo.id]: http(),
|
||||
},
|
||||
connectors: [
|
||||
farcasterFrame(),
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
import { APP_URL } from "./constants";
|
||||
|
||||
// Amplitude tracking -- only runs if configured via the CLI or in the .env file
|
||||
export function logEvent(
|
||||
eventType: string,
|
||||
eventProperties: Record<string, any> = {},
|
||||
deviceId: string | null = null
|
||||
) {
|
||||
if (process.env.NEXT_PUBLIC_ANALYTICS_ENABLED?.toLowerCase() !== 'true' || process.env.NODE_ENV !== "production") {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = {
|
||||
event_type: eventType,
|
||||
api_key: '0c4fe46171b9bb8eca2ca61eb71f2e19',
|
||||
time: Date.now(),
|
||||
user_id: APP_URL,
|
||||
...(deviceId && { device_id: deviceId }),
|
||||
...(Object.keys(eventProperties).length && { event_properties: eventProperties })
|
||||
};
|
||||
|
||||
fetch('https://api2.amplitude.com/2/httpapi', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
api_key: '0c4fe46171b9bb8eca2ca61eb71f2e19',
|
||||
events: [event]
|
||||
})
|
||||
}).catch(error => {
|
||||
console.error('Amplitude tracking error:', error);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user