mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
fix: share component
This commit is contained in:
parent
6a056cb30c
commit
8966dcee30
@ -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(
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user