formatting

This commit is contained in:
Shreyaschorge
2025-07-07 14:10:47 +05:30
parent f42a5f8d33
commit 193dffe03a
64 changed files with 6398 additions and 985 deletions

View File

@@ -1,15 +1,15 @@
"use client";
'use client';
import { useCallback, useState } from "react";
import { useMiniApp } from "@neynar/react";
import { ShareButton } from "../Share";
import { Button } from "../Button";
import { SignIn } from "../wallet/SignIn";
import { type Haptics } from "@farcaster/frame-sdk";
import { useCallback, useState } from 'react';
import { useMiniApp } from '@neynar/react';
import { ShareButton } from '../Share';
import { Button } from '../Button';
import { SignIn } from '../wallet/SignIn';
import { type Haptics } from '@farcaster/frame-sdk';
/**
* ActionsTab component handles mini app actions like sharing, notifications, and haptic feedback.
*
*
* This component provides the main interaction interface for users to:
* - Share the mini app with others
* - Sign in with Farcaster
@@ -17,10 +17,10 @@ import { type Haptics } from "@farcaster/frame-sdk";
* - Trigger haptic feedback
* - Add the mini app to their client
* - Copy share URLs
*
*
* The component uses the useMiniApp hook to access Farcaster context and actions.
* All state is managed locally within this component.
*
*
* @example
* ```tsx
* <ActionsTab />
@@ -28,63 +28,65 @@ import { type Haptics } from "@farcaster/frame-sdk";
*/
export function ActionsTab() {
// --- Hooks ---
const {
actions,
added,
notificationDetails,
haptics,
context,
} = useMiniApp();
const { actions, added, notificationDetails, haptics, context } =
useMiniApp();
// --- State ---
const [notificationState, setNotificationState] = useState({
sendStatus: "",
sendStatus: '',
shareUrlCopied: false,
});
const [selectedHapticIntensity, setSelectedHapticIntensity] = useState<Haptics.ImpactOccurredType>('medium');
const [selectedHapticIntensity, setSelectedHapticIntensity] =
useState<Haptics.ImpactOccurredType>('medium');
// --- Handlers ---
/**
* Sends a notification to the current user's Farcaster account.
*
*
* This function makes a POST request to the /api/send-notification endpoint
* with the user's FID and notification details. It handles different response
* statuses including success (200), rate limiting (429), and errors.
*
*
* @returns Promise that resolves when the notification is sent or fails
*/
const sendFarcasterNotification = useCallback(async () => {
setNotificationState((prev) => ({ ...prev, sendStatus: "" }));
setNotificationState(prev => ({ ...prev, sendStatus: '' }));
if (!notificationDetails || !context) {
return;
}
try {
const response = await fetch("/api/send-notification", {
method: "POST",
mode: "same-origin",
headers: { "Content-Type": "application/json" },
const response = await fetch('/api/send-notification', {
method: 'POST',
mode: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fid: context.user.fid,
notificationDetails,
}),
});
if (response.status === 200) {
setNotificationState((prev) => ({ ...prev, sendStatus: "Success" }));
setNotificationState(prev => ({ ...prev, sendStatus: 'Success' }));
return;
} else if (response.status === 429) {
setNotificationState((prev) => ({ ...prev, sendStatus: "Rate limited" }));
setNotificationState(prev => ({ ...prev, sendStatus: 'Rate limited' }));
return;
}
const responseText = await response.text();
setNotificationState((prev) => ({ ...prev, sendStatus: `Error: ${responseText}` }));
setNotificationState(prev => ({
...prev,
sendStatus: `Error: ${responseText}`,
}));
} catch (error) {
setNotificationState((prev) => ({ ...prev, sendStatus: `Error: ${error}` }));
setNotificationState(prev => ({
...prev,
sendStatus: `Error: ${error}`,
}));
}
}, [context, notificationDetails]);
/**
* Copies the share URL for the current user to the clipboard.
*
*
* This function generates a share URL using the user's FID and copies it
* to the clipboard. It shows a temporary "Copied!" message for 2 seconds.
*/
@@ -92,14 +94,18 @@ export function ActionsTab() {
if (context?.user?.fid) {
const userShareUrl = `${process.env.NEXT_PUBLIC_URL}/share/${context.user.fid}`;
await navigator.clipboard.writeText(userShareUrl);
setNotificationState((prev) => ({ ...prev, shareUrlCopied: true }));
setTimeout(() => setNotificationState((prev) => ({ ...prev, shareUrlCopied: false })), 2000);
setNotificationState(prev => ({ ...prev, shareUrlCopied: true }));
setTimeout(
() =>
setNotificationState(prev => ({ ...prev, shareUrlCopied: false })),
2000
);
}
}, [context?.user?.fid]);
/**
* Triggers haptic feedback with the selected intensity.
*
*
* This function calls the haptics.impactOccurred method with the current
* selectedHapticIntensity setting. It handles errors gracefully by logging them.
*/
@@ -115,12 +121,14 @@ export function ActionsTab() {
return (
<div className="space-y-3 px-6 w-full max-w-md mx-auto">
{/* Share functionality */}
<ShareButton
<ShareButton
buttonText="Share Mini App"
cast={{
text: "Check out this awesome frame @1 @2 @3! 🚀🪐",
text: 'Check out this awesome frame @1 @2 @3! 🚀🪐',
bestFriends: true,
embeds: [`${process.env.NEXT_PUBLIC_URL}/share/${context?.user?.fid || ''}`]
embeds: [
`${process.env.NEXT_PUBLIC_URL}/share/${context?.user?.fid || ''}`,
],
}}
className="w-full"
/>
@@ -129,7 +137,14 @@ export function ActionsTab() {
<SignIn />
{/* Mini app actions */}
<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.addMiniApp} disabled={added} className="w-full">
Add Mini App to Client
@@ -141,17 +156,21 @@ export function ActionsTab() {
Send notification result: {notificationState.sendStatus}
</div>
)}
<Button onClick={sendFarcasterNotification} disabled={!notificationDetails} className="w-full">
<Button
onClick={sendFarcasterNotification}
disabled={!notificationDetails}
className="w-full"
>
Send notification
</Button>
{/* Share URL copying */}
<Button
<Button
onClick={copyUserShareUrl}
disabled={!context?.user?.fid}
className="w-full"
>
{notificationState.shareUrlCopied ? "Copied!" : "Copy share URL"}
{notificationState.shareUrlCopied ? 'Copied!' : 'Copy share URL'}
</Button>
{/* Haptic feedback controls */}
@@ -161,7 +180,11 @@ export function ActionsTab() {
</label>
<select
value={selectedHapticIntensity}
onChange={(e) => setSelectedHapticIntensity(e.target.value as Haptics.ImpactOccurredType)}
onChange={e =>
setSelectedHapticIntensity(
e.target.value as Haptics.ImpactOccurredType
)
}
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-primary"
>
<option value={'light'}>Light</option>
@@ -170,13 +193,10 @@ export function ActionsTab() {
<option value={'soft'}>Soft</option>
<option value={'rigid'}>Rigid</option>
</select>
<Button
onClick={triggerHapticFeedback}
className="w-full"
>
<Button onClick={triggerHapticFeedback} className="w-full">
Trigger Haptic Feedback
</Button>
</div>
</div>
);
}
}

View File

@@ -1,19 +1,19 @@
"use client";
'use client';
import { useMiniApp } from "@neynar/react";
import { useMiniApp } from '@neynar/react';
/**
* ContextTab component displays the current mini app context in JSON format.
*
*
* This component provides a developer-friendly view of the Farcaster mini app context,
* including user information, client details, and other contextual data. It's useful
* for debugging and understanding what data is available to the mini app.
*
*
* The context includes:
* - User information (FID, username, display name, profile picture)
* - Client information (safe area insets, platform details)
* - Mini app configuration and state
*
*
* @example
* ```tsx
* <ContextTab />
@@ -21,7 +21,7 @@ import { useMiniApp } from "@neynar/react";
*/
export function ContextTab() {
const { context } = useMiniApp();
return (
<div className="mx-6">
<h2 className="text-lg font-semibold mb-2">Context</h2>
@@ -32,4 +32,4 @@ export function ContextTab() {
</div>
</div>
);
}
}

View File

@@ -1,12 +1,12 @@
"use client";
'use client';
/**
* HomeTab component displays the main landing content for the mini app.
*
*
* This is the default tab that users see when they first open the mini app.
* It provides a simple welcome message and placeholder content that can be
* customized for specific use cases.
*
*
* @example
* ```tsx
* <HomeTab />
@@ -21,4 +21,4 @@ export function HomeTab() {
</div>
</div>
);
}
}

View File

@@ -1,22 +1,32 @@
"use client";
'use client';
import { useCallback, useMemo, useState, useEffect } from "react";
import { useAccount, useSendTransaction, useSignTypedData, useWaitForTransactionReceipt, useDisconnect, useConnect, useSwitchChain, useChainId, type Connector } from "wagmi";
import { useCallback, useMemo, useState, useEffect } from 'react';
import {
useAccount,
useSendTransaction,
useSignTypedData,
useWaitForTransactionReceipt,
useDisconnect,
useConnect,
useSwitchChain,
useChainId,
type Connector,
} from 'wagmi';
import { useWallet as useSolanaWallet } from '@solana/wallet-adapter-react';
import { base, degen, mainnet, optimism, unichain } from "wagmi/chains";
import { Button } from "../Button";
import { truncateAddress } from "../../../lib/truncateAddress";
import { renderError } from "../../../lib/errorUtils";
import { SignEvmMessage } from "../wallet/SignEvmMessage";
import { SendEth } from "../wallet/SendEth";
import { SignSolanaMessage } from "../wallet/SignSolanaMessage";
import { SendSolana } from "../wallet/SendSolana";
import { USE_WALLET, APP_NAME } from "../../../lib/constants";
import { useMiniApp } from "@neynar/react";
import { base, degen, mainnet, optimism, unichain } from 'wagmi/chains';
import { Button } from '../Button';
import { truncateAddress } from '../../../lib/truncateAddress';
import { renderError } from '../../../lib/errorUtils';
import { SignEvmMessage } from '../wallet/SignEvmMessage';
import { SendEth } from '../wallet/SendEth';
import { SignSolanaMessage } from '../wallet/SignSolanaMessage';
import { SendSolana } from '../wallet/SendSolana';
import { USE_WALLET, APP_NAME } from '../../../lib/constants';
import { useMiniApp } from '@neynar/react';
/**
* WalletTab component manages wallet-related UI for both EVM and Solana chains.
*
*
* This component provides a comprehensive wallet interface that supports:
* - EVM wallet connections (Farcaster Frame, Coinbase Wallet, MetaMask)
* - Solana wallet integration
@@ -24,10 +34,10 @@ import { useMiniApp } from "@neynar/react";
* - Transaction sending for both chains
* - Chain switching for EVM chains
* - Auto-connection in Farcaster clients
*
*
* The component automatically detects when running in a Farcaster client
* and attempts to auto-connect using the Farcaster Frame connector.
*
*
* @example
* ```tsx
* <WalletTab />
@@ -47,7 +57,8 @@ function WalletStatus({ address, chainId }: WalletStatusProps) {
<>
{address && (
<div className="text-xs w-full">
Address: <pre className="inline w-full">{truncateAddress(address)}</pre>
Address:{' '}
<pre className="inline w-full">{truncateAddress(address)}</pre>
</div>
)}
{chainId && (
@@ -90,13 +101,19 @@ function ConnectionControls({
if (context) {
return (
<div className="space-y-2 w-full">
<Button onClick={() => connect({ connector: connectors[0] })} className="w-full">
<Button
onClick={() => connect({ connector: connectors[0] })}
className="w-full"
>
Connect (Auto)
</Button>
<Button
onClick={() => {
console.log("Manual Farcaster connection attempt");
console.log("Connectors:", connectors.map((c, i) => `${i}: ${c.name}`));
console.log('Manual Farcaster connection attempt');
console.log(
'Connectors:',
connectors.map((c, i) => `${i}: ${c.name}`)
);
connect({ connector: connectors[0] });
}}
className="w-full"
@@ -108,10 +125,16 @@ function ConnectionControls({
}
return (
<div className="space-y-3 w-full">
<Button onClick={() => connect({ connector: connectors[1] })} className="w-full">
<Button
onClick={() => connect({ connector: connectors[1] })}
className="w-full"
>
Connect Coinbase Wallet
</Button>
<Button onClick={() => connect({ connector: connectors[2] })} className="w-full">
<Button
onClick={() => connect({ connector: connectors[2] })}
className="w-full"
>
Connect MetaMask
</Button>
</div>
@@ -120,8 +143,10 @@ function ConnectionControls({
export function WalletTab() {
// --- State ---
const [evmContractTransactionHash, setEvmContractTransactionHash] = useState<string | null>(null);
const [evmContractTransactionHash, setEvmContractTransactionHash] = useState<
string | null
>(null);
// --- Hooks ---
const { context } = useMiniApp();
const { address, isConnected } = useAccount();
@@ -137,10 +162,12 @@ export function WalletTab() {
isPending: isEvmTransactionPending,
} = useSendTransaction();
const { isLoading: isEvmTransactionConfirming, isSuccess: isEvmTransactionConfirmed } =
useWaitForTransactionReceipt({
hash: evmContractTransactionHash as `0x${string}`,
});
const {
isLoading: isEvmTransactionConfirming,
isSuccess: isEvmTransactionConfirmed,
} = useWaitForTransactionReceipt({
hash: evmContractTransactionHash as `0x${string}`,
});
const {
signTypedData,
@@ -162,38 +189,47 @@ export function WalletTab() {
// --- Effects ---
/**
* Auto-connect when Farcaster context is available.
*
*
* This effect detects when the app is running in a Farcaster client
* and automatically attempts to connect using the Farcaster Frame connector.
* It includes comprehensive logging for debugging connection issues.
*/
useEffect(() => {
// Check if we're in a Farcaster client environment
const isInFarcasterClient = typeof window !== 'undefined' &&
(window.location.href.includes('warpcast.com') ||
window.location.href.includes('farcaster') ||
window.ethereum?.isFarcaster ||
context?.client);
if (context?.user?.fid && !isConnected && connectors.length > 0 && isInFarcasterClient) {
console.log("Attempting auto-connection with Farcaster context...");
console.log("- User FID:", context.user.fid);
console.log("- Available connectors:", connectors.map((c, i) => `${i}: ${c.name}`));
console.log("- Using connector:", connectors[0].name);
console.log("- In Farcaster client:", isInFarcasterClient);
const isInFarcasterClient =
typeof window !== 'undefined' &&
(window.location.href.includes('warpcast.com') ||
window.location.href.includes('farcaster') ||
window.ethereum?.isFarcaster ||
context?.client);
if (
context?.user?.fid &&
!isConnected &&
connectors.length > 0 &&
isInFarcasterClient
) {
console.log('Attempting auto-connection with Farcaster context...');
console.log('- User FID:', context.user.fid);
console.log(
'- Available connectors:',
connectors.map((c, i) => `${i}: ${c.name}`)
);
console.log('- Using connector:', connectors[0].name);
console.log('- In Farcaster client:', isInFarcasterClient);
// Use the first connector (farcasterFrame) for auto-connection
try {
connect({ connector: connectors[0] });
} catch (error) {
console.error("Auto-connection failed:", error);
console.error('Auto-connection failed:', error);
}
} else {
console.log("Auto-connection conditions not met:");
console.log("- Has context:", !!context?.user?.fid);
console.log("- Is connected:", isConnected);
console.log("- Has connectors:", connectors.length > 0);
console.log("- In Farcaster client:", isInFarcasterClient);
console.log('Auto-connection conditions not met:');
console.log('- Has context:', !!context?.user?.fid);
console.log('- Is connected:', isConnected);
console.log('- Has connectors:', connectors.length > 0);
console.log('- In Farcaster client:', isInFarcasterClient);
}
}, [context?.user?.fid, isConnected, connectors, connect, context?.client]);
@@ -227,7 +263,7 @@ export function WalletTab() {
/**
* Sends a transaction to call the yoink() function on the Yoink contract.
*
*
* This function sends a transaction to a specific contract address with
* the encoded function call data for the yoink() function.
*/
@@ -235,11 +271,11 @@ export function WalletTab() {
sendTransaction(
{
// call yoink() on Yoink contract
to: "0x4bBFD120d9f352A0BEd7a014bd67913a2007a878",
data: "0x9846cd9efc000023c0",
to: '0x4bBFD120d9f352A0BEd7a014bd67913a2007a878',
data: '0x9846cd9efc000023c0',
},
{
onSuccess: (hash) => {
onSuccess: hash => {
setEvmContractTransactionHash(hash);
},
}
@@ -248,7 +284,7 @@ export function WalletTab() {
/**
* Signs typed data using EIP-712 standard.
*
*
* This function creates a typed data structure with the app name, version,
* and chain ID, then requests the user to sign it.
*/
@@ -256,16 +292,16 @@ export function WalletTab() {
signTypedData({
domain: {
name: APP_NAME,
version: "1",
version: '1',
chainId,
},
types: {
Message: [{ name: "content", type: "string" }],
Message: [{ name: 'content', type: 'string' }],
},
message: {
content: `Hello from ${APP_NAME}!`,
},
primaryType: "Message",
primaryType: 'Message',
});
}, [chainId, signTypedData]);
@@ -308,12 +344,12 @@ export function WalletTab() {
<div className="text-xs w-full">
<div>Hash: {truncateAddress(evmContractTransactionHash)}</div>
<div>
Status:{" "}
Status:{' '}
{isEvmTransactionConfirming
? "Confirming..."
? 'Confirming...'
: isEvmTransactionConfirmed
? "Confirmed!"
: "Pending"}
? 'Confirmed!'
: 'Pending'}
</div>
</div>
)}
@@ -347,4 +383,4 @@ export function WalletTab() {
)}
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
export { HomeTab } from './HomeTab';
export { ActionsTab } from './ActionsTab';
export { ContextTab } from './ContextTab';
export { WalletTab } from './WalletTab';
export { WalletTab } from './WalletTab';