feat: add support for back button and haptics

This commit is contained in:
veganbeef 2025-06-23 12:05:13 -07:00
parent f97a697f88
commit e349724267
No known key found for this signature in database
5 changed files with 51 additions and 12 deletions

View File

@ -347,7 +347,7 @@ export async function init() {
"@farcaster/frame-sdk": ">=0.0.31 <1.0.0", "@farcaster/frame-sdk": ">=0.0.31 <1.0.0",
"@farcaster/frame-wagmi-connector": ">=0.0.19 <1.0.0", "@farcaster/frame-wagmi-connector": ">=0.0.19 <1.0.0",
"@farcaster/mini-app-solana": ">=0.0.17 <1.0.0", "@farcaster/mini-app-solana": ">=0.0.17 <1.0.0",
"@neynar/react": "^1.2.2", "@neynar/react": "^1.2.5",
"@radix-ui/react-label": "^2.1.1", "@radix-ui/react-label": "^2.1.1",
"@solana/wallet-adapter-react": "^0.15.38", "@solana/wallet-adapter-react": "^0.15.38",
"@tanstack/react-query": "^5.61.0", "@tanstack/react-query": "^5.61.0",

View File

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

View File

@ -18,7 +18,7 @@ export function Providers({ session, children }: { session: Session | null, chil
return ( return (
<SessionProvider session={session}> <SessionProvider session={session}>
<WagmiProvider> <WagmiProvider>
<MiniAppProvider analyticsEnabled={true}> <MiniAppProvider analyticsEnabled={true} backButtonEnabled={true}>
<SafeFarcasterSolanaProvider endpoint={solanaEndpoint}> <SafeFarcasterSolanaProvider endpoint={solanaEndpoint}>
{children} {children}
</SafeFarcasterSolanaProvider> </SafeFarcasterSolanaProvider>

View File

@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { signIn, signOut, getCsrfToken } from "next-auth/react"; import { signIn, signOut, getCsrfToken } from "next-auth/react";
import sdk, { import sdk, {
SignIn as SignInCore, SignIn as SignInCore,
type Haptics,
} from "@farcaster/frame-sdk"; } from "@farcaster/frame-sdk";
import { import {
useAccount, useAccount,
@ -52,13 +53,17 @@ export default function Demo(
added, added,
notificationDetails, notificationDetails,
actions, actions,
setInitialTab,
setActiveTab,
currentTab,
haptics,
} = useMiniApp(); } = useMiniApp();
const [isContextOpen, setIsContextOpen] = useState(false); const [isContextOpen, setIsContextOpen] = useState(false);
const [activeTab, setActiveTab] = useState<Tab>('home');
const [txHash, setTxHash] = useState<string | null>(null); const [txHash, setTxHash] = useState<string | null>(null);
const [sendNotificationResult, setSendNotificationResult] = useState(""); const [sendNotificationResult, setSendNotificationResult] = useState("");
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [neynarUser, setNeynarUser] = useState<NeynarUser | null>(null); const [neynarUser, setNeynarUser] = useState<NeynarUser | null>(null);
const [hapticIntensity, setHapticIntensity] = useState<Haptics.ImpactOccurredType>('medium');
const { address, isConnected } = useAccount(); const { address, isConnected } = useAccount();
const chainId = useChainId(); const chainId = useChainId();
@ -66,6 +71,13 @@ export default function Demo(
const solanaWallet = useSolanaWallet(); const solanaWallet = useSolanaWallet();
const { publicKey: solanaPublicKey } = solanaWallet; const { publicKey: solanaPublicKey } = solanaWallet;
// Set initial tab to home on page load
useEffect(() => {
if (isSDKLoaded) {
setInitialTab('home');
}
}, [isSDKLoaded, setInitialTab]);
useEffect(() => { useEffect(() => {
console.log("isSDKLoaded", isSDKLoaded); console.log("isSDKLoaded", isSDKLoaded);
console.log("context", context); console.log("context", context);
@ -226,7 +238,7 @@ export default function Demo(
<h1 className="text-2xl font-bold text-center mb-4">{title}</h1> <h1 className="text-2xl font-bold text-center mb-4">{title}</h1>
{activeTab === 'home' && ( {currentTab === 'home' && (
<div className="flex items-center justify-center h-[calc(100vh-200px)] px-6"> <div className="flex items-center justify-center h-[calc(100vh-200px)] px-6">
<div className="text-center w-full max-w-md mx-auto"> <div className="text-center w-full max-w-md mx-auto">
<p className="text-lg mb-2">Put your content here!</p> <p className="text-lg mb-2">Put your content here!</p>
@ -235,7 +247,7 @@ export default function Demo(
</div> </div>
)} )}
{activeTab === 'actions' && ( {currentTab === 'actions' && (
<div className="space-y-3 px-6 w-full max-w-md mx-auto"> <div className="space-y-3 px-6 w-full max-w-md mx-auto">
<ShareButton <ShareButton
buttonText="Share Mini App" buttonText="Share Mini App"
@ -251,8 +263,6 @@ export default function Demo(
<Button onClick={() => actions.openUrl("https://www.youtube.com/watch?v=dQw4w9WgXcQ")} className="w-full">Open Link</Button> <Button onClick={() => actions.openUrl("https://www.youtube.com/watch?v=dQw4w9WgXcQ")} className="w-full">Open Link</Button>
<Button onClick={actions.close} className="w-full">Close Mini App</Button>
<Button onClick={actions.addMiniApp} disabled={added} className="w-full"> <Button onClick={actions.addMiniApp} disabled={added} className="w-full">
Add Mini App to Client Add Mini App to Client
</Button> </Button>
@ -280,10 +290,39 @@ export default function Demo(
> >
{copied ? "Copied!" : "Copy share URL"} {copied ? "Copied!" : "Copy share URL"}
</Button> </Button>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Haptic Intensity
</label>
<select
value={hapticIntensity}
onChange={(e) => setHapticIntensity(e.target.value as typeof hapticIntensity)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="light">Light</option>
<option value="medium">Medium</option>
<option value="heavy">Heavy</option>
<option value="soft">Soft</option>
<option value="rigid">Rigid</option>
</select>
<Button
onClick={async () => {
try {
await haptics.impactOccurred(hapticIntensity);
} catch (error) {
console.error('Haptic feedback failed:', error);
}
}}
className="w-full"
>
Trigger Haptic Feedback
</Button>
</div>
</div> </div>
)} )}
{activeTab === 'context' && ( {currentTab === 'context' && (
<div className="mx-6"> <div className="mx-6">
<h2 className="text-lg font-semibold mb-2">Context</h2> <h2 className="text-lg font-semibold mb-2">Context</h2>
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg"> <div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
@ -294,7 +333,7 @@ export default function Demo(
</div> </div>
)} )}
{activeTab === 'wallet' && USE_WALLET && ( {currentTab === 'wallet' && USE_WALLET && (
<div className="space-y-3 px-6 w-full max-w-md mx-auto"> <div className="space-y-3 px-6 w-full max-w-md mx-auto">
{address && ( {address && (
<div className="text-xs w-full"> <div className="text-xs w-full">
@ -389,7 +428,7 @@ export default function Demo(
</div> </div>
)} )}
<Footer activeTab={activeTab} setActiveTab={setActiveTab} showWallet={USE_WALLET} /> <Footer activeTab={currentTab as Tab} setActiveTab={setActiveTab} showWallet={USE_WALLET} />
</div> </div>
</div> </div>
); );

View File

@ -97,7 +97,7 @@ export function ShareButton({ buttonText, cast, className = '', isLoading = fals
parent: cast.parent, parent: cast.parent,
channelKey: cast.channelKey, channelKey: cast.channelKey,
close: cast.close, close: cast.close,
}, 'share-button'); });
} catch (error) { } catch (error) {
console.error('Failed to share:', error); console.error('Failed to share:', error);
} finally { } finally {