fix: share component

This commit is contained in:
veganbeef 2025-06-13 15:22:25 -07:00
parent 6a056cb30c
commit 8966dcee30
No known key found for this signature in database
4 changed files with 35 additions and 91 deletions

View File

@ -1,4 +1,3 @@
import { NeynarAPIClient } from '@neynar/nodejs-sdk';
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
export async function GET(request: Request) { export async function GET(request: Request) {
@ -21,8 +20,6 @@ export async function GET(request: Request) {
} }
try { try {
const neynar = new NeynarAPIClient({ apiKey });
// TODO: update to use best friends endpoint once SDK merged in
const response = await fetch( const response = await fetch(
`https://api.neynar.com/v2/farcaster/user/best_friends?fid=${fid}&limit=3`, `https://api.neynar.com/v2/farcaster/user/best_friends?fid=${fid}&limit=3`,
{ {
@ -38,12 +35,7 @@ export async function GET(request: Request) {
const { users } = await response.json() as { users: { user: { fid: number; username: string } }[] }; const { users } = await response.json() as { users: { user: { fid: number; username: string } }[] };
const bestFriends = users.map(user => ({ return NextResponse.json({ bestFriends: users });
fid: user.user?.fid,
username: user.user?.username,
}));
return NextResponse.json({ bestFriends });
} catch (error) { } catch (error) {
console.error('Failed to fetch best friends:', error); console.error('Failed to fetch best friends:', error);
return NextResponse.json( return NextResponse.json(

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client"; "use client";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
@ -22,6 +23,7 @@ import {
useWallet as useSolanaWallet, useWallet as useSolanaWallet,
} from '@solana/wallet-adapter-react'; } from '@solana/wallet-adapter-react';
import { useHasSolanaProvider } from "./providers/SafeFarcasterSolanaProvider"; import { useHasSolanaProvider } from "./providers/SafeFarcasterSolanaProvider";
import { ShareButton } from "./ui/Share";
import { config } from "~/components/providers/WagmiProvider"; import { config } from "~/components/providers/WagmiProvider";
import { Button } from "~/components/ui/Button"; import { Button } from "~/components/ui/Button";
@ -30,7 +32,6 @@ import { base, degen, mainnet, optimism, unichain } from "wagmi/chains";
import { BaseError, UserRejectedRequestError } from "viem"; import { BaseError, UserRejectedRequestError } from "viem";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useMiniApp } from "@neynar/react"; import { useMiniApp } from "@neynar/react";
import { Label } from "~/components/ui/label";
import { PublicKey, SystemProgram, Transaction } from '@solana/web3.js'; import { PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
import { Header } from "~/components/ui/Header"; import { Header } from "~/components/ui/Header";
import { Footer } from "~/components/ui/Footer"; import { Footer } from "~/components/ui/Footer";
@ -38,6 +39,11 @@ import { USE_WALLET } from "~/lib/constants";
export type Tab = 'home' | 'actions' | 'context' | 'wallet'; export type Tab = 'home' | 'actions' | 'context' | 'wallet';
interface NeynarUser {
fid: number;
score: number;
}
export default function Demo( export default function Demo(
{ title }: { title?: string } = { title: "Frames v2 Demo" } { title }: { title?: string } = { title: "Frames v2 Demo" }
) { ) {
@ -53,17 +59,13 @@ export default function Demo(
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<any | null>(null); const [neynarUser, setNeynarUser] = useState<NeynarUser | null>(null);
const { address, isConnected } = useAccount(); const { address, isConnected } = useAccount();
const chainId = useChainId(); const chainId = useChainId();
const hasSolanaProvider = useHasSolanaProvider(); const hasSolanaProvider = useHasSolanaProvider();
let solanaWallet, solanaPublicKey, solanaSignMessage, solanaAddress; const solanaWallet = useSolanaWallet();
if (hasSolanaProvider) { const { publicKey: solanaPublicKey } = solanaWallet;
solanaWallet = useSolanaWallet();
({ publicKey: solanaPublicKey, signMessage: solanaSignMessage } = solanaWallet);
solanaAddress = solanaPublicKey?.toBase58();
}
useEffect(() => { useEffect(() => {
console.log("isSDKLoaded", isSDKLoaded); console.log("isSDKLoaded", isSDKLoaded);
@ -220,7 +222,7 @@ export default function Demo(
paddingRight: context?.client.safeAreaInsets?.right ?? 0, paddingRight: context?.client.safeAreaInsets?.right ?? 0,
}} }}
> >
<div className="mx-auto py-2 px-2 pb-20"> <div className="mx-auto py-2 px-4 pb-20">
<Header neynarUser={neynarUser} /> <Header neynarUser={neynarUser} />
<h1 className="text-2xl font-bold text-center mb-4">{title}</h1> <h1 className="text-2xl font-bold text-center mb-4">{title}</h1>
@ -236,47 +238,22 @@ export default function Demo(
{activeTab === 'actions' && ( {activeTab === '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">
<div className="p-2 bg-gray-100 dark:bg-gray-800 rounded-lg w-full"> <ShareButton
<pre className="font-mono text-xs whitespace-pre-wrap break-words w-full"> buttonText="Share Mini App"
sdk.actions.signIn cast={{
</pre> text: "Check out this awesome frame @1 @2 @3! 🚀🪐",
</div> bestFriends: true,
embeds: [`${APP_URL}/share?fid=${context?.user?.fid || 'unknown'}`]
}}
className="w-full"
/>
<SignIn /> <SignIn />
<div className="p-2 bg-gray-100 dark:bg-gray-800 rounded-lg w-full">
<pre className="font-mono text-xs whitespace-pre-wrap break-words w-full">
sdk.actions.openUrl
</pre>
</div>
<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>
<div className="p-2 bg-gray-100 dark:bg-gray-800 rounded-lg w-full">
<pre className="font-mono text-xs whitespace-pre-wrap break-words w-full">
sdk.actions.viewProfile
</pre>
</div>
<ViewProfile />
<div className="p-2 bg-gray-100 dark:bg-gray-800 rounded-lg w-full">
<pre className="font-mono text-xs whitespace-pre-wrap break-words w-full">
sdk.actions.close
</pre>
</div>
<Button onClick={actions.close} className="w-full">Close Frame</Button> <Button onClick={actions.close} className="w-full">Close Frame</Button>
<div className="text-sm w-full">
Client fid {context?.client.clientFid},
{added ? " frame added to client," : " frame not added to client,"}
{notificationDetails
? " notifications enabled"
: " notifications disabled"}
</div>
<div className="p-2 bg-gray-100 dark:bg-gray-800 rounded-lg w-full">
<pre className="font-mono text-xs whitespace-pre-wrap break-words w-full">
sdk.actions.addMiniApp
</pre>
</div>
<Button onClick={actions.addMiniApp} disabled={added} className="w-full"> <Button onClick={actions.addMiniApp} disabled={added} className="w-full">
Add frame to client Add frame to client
</Button> </Button>
@ -727,41 +704,6 @@ function SignIn() {
); );
} }
function ViewProfile() {
const [fid, setFid] = useState("3");
return (
<>
<div>
<Label
className="text-xs font-semibold text-gray-500 mb-1"
htmlFor="view-profile-fid"
>
Fid
</Label>
<Input
id="view-profile-fid"
type="number"
value={fid}
className="mb-2"
onChange={(e) => {
setFid(e.target.value);
}}
step="1"
min="1"
/>
</div>
<Button
onClick={() => {
sdk.actions.viewProfile({ fid: parseInt(fid) });
}}
>
View Profile
</Button>
</>
);
}
const renderError = (error: Error | null) => { const renderError = (error: Error | null) => {
if (!error) return null; if (!error) return null;
if (error instanceof BaseError) { if (error instanceof BaseError) {

View File

@ -6,7 +6,10 @@ import sdk from "@farcaster/frame-sdk";
import { useMiniApp } from "@neynar/react"; import { useMiniApp } from "@neynar/react";
type HeaderProps = { type HeaderProps = {
neynarUser: any; neynarUser?: {
fid: number;
score: number;
} | null;
}; };
export function Header({ neynarUser }: HeaderProps) { export function Header({ neynarUser }: HeaderProps) {

View File

@ -5,8 +5,15 @@ import { Button } from './Button';
import { useMiniApp } from '@neynar/react'; import { useMiniApp } from '@neynar/react';
import { type ComposeCast } from "@farcaster/frame-sdk"; import { type ComposeCast } from "@farcaster/frame-sdk";
interface CastConfig extends ComposeCast.Options { interface EmbedConfig {
path?: string;
url?: string;
imageUrl?: () => Promise<string>;
}
interface CastConfig extends Omit<ComposeCast.Options, 'embeds'> {
bestFriends?: boolean; bestFriends?: boolean;
embeds?: (string | EmbedConfig)[];
} }
interface ShareButtonProps { interface ShareButtonProps {
@ -82,7 +89,7 @@ export function ShareButton({ buttonText, cast, className = '', isLoading = fals
text: finalText, text: finalText,
embeds: processedEmbeds as [string] | [string, string] | undefined, embeds: processedEmbeds as [string] | [string, string] | undefined,
parent: cast.parent, parent: cast.parent,
channel: cast.channelKey, channelKey: cast.channelKey,
close: cast.close, close: cast.close,
}, 'share-button'); }, 'share-button');
} catch (error) { } catch (error) {