mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
move conditional next-auth code to alternate files
This commit is contained in:
parent
d0f8c75a2e
commit
8c5b8d8329
62
bin/init.js
62
bin/init.js
@ -747,9 +747,12 @@ export async function init(projectName = null, autoAcceptDefaults = false, apiKe
|
|||||||
fs.rmSync(binPath, { recursive: true, force: true });
|
fs.rmSync(binPath, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove NeynarAuthButton directory, NextAuth API routes, and auth directory if SIWN is not enabled (no seed phrase)
|
// Handle SIWN-related files based on whether seed phrase is provided
|
||||||
if (!answers.seedPhrase) {
|
if (!answers.seedPhrase) {
|
||||||
console.log('\nRemoving NeynarAuthButton directory, NextAuth API routes, and auth directory (SIWN not enabled)...');
|
// Remove SIWN-related files when SIWN is not enabled (no seed phrase)
|
||||||
|
console.log('\nRemoving SIWN-related files (SIWN not enabled)...');
|
||||||
|
|
||||||
|
// Remove NeynarAuthButton directory
|
||||||
const neynarAuthButtonPath = path.join(projectPath, 'src', 'components', 'ui', 'NeynarAuthButton');
|
const neynarAuthButtonPath = path.join(projectPath, 'src', 'components', 'ui', 'NeynarAuthButton');
|
||||||
if (fs.existsSync(neynarAuthButtonPath)) {
|
if (fs.existsSync(neynarAuthButtonPath)) {
|
||||||
fs.rmSync(neynarAuthButtonPath, { recursive: true, force: true });
|
fs.rmSync(neynarAuthButtonPath, { recursive: true, force: true });
|
||||||
@ -772,19 +775,56 @@ export async function init(projectName = null, autoAcceptDefaults = false, apiKe
|
|||||||
fs.rmSync(authFilePath, { force: true });
|
fs.rmSync(authFilePath, { force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace NeynarAuthButton import in ActionsTab.tsx with null component
|
// Remove SIWN-specific files
|
||||||
|
const actionsTabNeynarAuthPath = path.join(projectPath, 'src', 'components', 'ui', 'tabs', 'ActionsTab.NeynarAuth.tsx');
|
||||||
|
if (fs.existsSync(actionsTabNeynarAuthPath)) {
|
||||||
|
fs.rmSync(actionsTabNeynarAuthPath, { force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutNeynarAuthPath = path.join(projectPath, 'src', 'app', 'layout.NeynarAuth.tsx');
|
||||||
|
if (fs.existsSync(layoutNeynarAuthPath)) {
|
||||||
|
fs.rmSync(layoutNeynarAuthPath, { force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const providersNeynarAuthPath = path.join(projectPath, 'src', 'app', 'providers.NeynarAuth.tsx');
|
||||||
|
if (fs.existsSync(providersNeynarAuthPath)) {
|
||||||
|
fs.rmSync(providersNeynarAuthPath, { force: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Move SIWN-specific files to replace the regular versions when SIWN is enabled
|
||||||
|
console.log('\nMoving SIWN-specific files to replace regular versions (SIWN enabled)...');
|
||||||
|
|
||||||
|
// Move ActionsTab.NeynarAuth.tsx to ActionsTab.tsx
|
||||||
|
const actionsTabNeynarAuthPath = path.join(projectPath, 'src', 'components', 'ui', 'tabs', 'ActionsTab.NeynarAuth.tsx');
|
||||||
const actionsTabPath = path.join(projectPath, 'src', 'components', 'ui', 'tabs', 'ActionsTab.tsx');
|
const actionsTabPath = path.join(projectPath, 'src', 'components', 'ui', 'tabs', 'ActionsTab.tsx');
|
||||||
|
if (fs.existsSync(actionsTabNeynarAuthPath)) {
|
||||||
if (fs.existsSync(actionsTabPath)) {
|
if (fs.existsSync(actionsTabPath)) {
|
||||||
let actionsTabContent = fs.readFileSync(actionsTabPath, 'utf8');
|
fs.rmSync(actionsTabPath, { force: true }); // Delete original
|
||||||
|
}
|
||||||
|
fs.renameSync(actionsTabNeynarAuthPath, actionsTabPath);
|
||||||
|
console.log('✅ Moved ActionsTab.NeynarAuth.tsx to ActionsTab.tsx');
|
||||||
|
}
|
||||||
|
|
||||||
// Replace the dynamic import of NeynarAuthButton with a null component
|
// Move layout.NeynarAuth.tsx to layout.tsx
|
||||||
actionsTabContent = actionsTabContent.replace(
|
const layoutNeynarAuthPath = path.join(projectPath, 'src', 'app', 'layout.NeynarAuth.tsx');
|
||||||
/const NeynarAuthButton = dynamic\([\s\S]*?\);/,
|
const layoutPath = path.join(projectPath, 'src', 'app', 'layout.tsx');
|
||||||
'// NeynarAuthButton disabled - SIWN not enabled\nconst NeynarAuthButton = () => {\n return null;\n};'
|
if (fs.existsSync(layoutNeynarAuthPath)) {
|
||||||
);
|
if (fs.existsSync(layoutPath)) {
|
||||||
|
fs.rmSync(layoutPath, { force: true }); // Delete original
|
||||||
|
}
|
||||||
|
fs.renameSync(layoutNeynarAuthPath, layoutPath);
|
||||||
|
console.log('✅ Moved layout.NeynarAuth.tsx to layout.tsx');
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync(actionsTabPath, actionsTabContent);
|
// Move providers.NeynarAuth.tsx to providers.tsx
|
||||||
console.log('✅ Replaced NeynarAuthButton import in ActionsTab.tsx with null component');
|
const providersNeynarAuthPath = path.join(projectPath, 'src', 'app', 'providers.NeynarAuth.tsx');
|
||||||
|
const providersPath = path.join(projectPath, 'src', 'app', 'providers.tsx');
|
||||||
|
if (fs.existsSync(providersNeynarAuthPath)) {
|
||||||
|
if (fs.existsSync(providersPath)) {
|
||||||
|
fs.rmSync(providersPath, { force: true }); // Delete original
|
||||||
|
}
|
||||||
|
fs.renameSync(providersNeynarAuthPath, providersPath);
|
||||||
|
console.log('✅ Moved providers.NeynarAuth.tsx to providers.tsx');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neynar/create-farcaster-mini-app",
|
"name": "@neynar/create-farcaster-mini-app",
|
||||||
"version": "1.7.14",
|
"version": "1.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": false,
|
"private": false,
|
||||||
"access": "public",
|
"access": "public",
|
||||||
|
|||||||
29
src/app/layout.NeynarAuth.tsx
Normal file
29
src/app/layout.NeynarAuth.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
import { getSession } from '~/auth';
|
||||||
|
import '~/app/globals.css';
|
||||||
|
import { Providers } from '~/app/providers';
|
||||||
|
import { APP_NAME, APP_DESCRIPTION } from '~/lib/constants';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: APP_NAME,
|
||||||
|
description: APP_DESCRIPTION,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
const session = await getSession();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<Providers session={session}>
|
||||||
|
{children}
|
||||||
|
</Providers>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -14,25 +14,10 @@ export default async function RootLayout({
|
|||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
// Only get session if sponsored signer is enabled or seed phrase is provided
|
|
||||||
const sponsorSigner = process.env.SPONSOR_SIGNER === 'true';
|
|
||||||
const hasSeedPhrase = !!process.env.SEED_PHRASE;
|
|
||||||
const shouldUseSession = sponsorSigner || hasSeedPhrase;
|
|
||||||
|
|
||||||
let session = null;
|
|
||||||
if (shouldUseSession) {
|
|
||||||
try {
|
|
||||||
const { getSession } = await import('~/auth');
|
|
||||||
session = await getSession();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to get session:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<Providers session={session} shouldUseSession={shouldUseSession}>
|
<Providers>
|
||||||
{children}
|
{children}
|
||||||
</Providers>
|
</Providers>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
43
src/app/providers.NeynarAuth.tsx
Normal file
43
src/app/providers.NeynarAuth.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import type { Session } from 'next-auth';
|
||||||
|
import { SessionProvider } from 'next-auth/react';
|
||||||
|
import { AuthKitProvider } from '@farcaster/auth-kit';
|
||||||
|
import { MiniAppProvider } from '@neynar/react';
|
||||||
|
import { SafeFarcasterSolanaProvider } from '~/components/providers/SafeFarcasterSolanaProvider';
|
||||||
|
import { ANALYTICS_ENABLED } from '~/lib/constants';
|
||||||
|
|
||||||
|
const WagmiProvider = dynamic(
|
||||||
|
() => import('~/components/providers/WagmiProvider'),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export function Providers({
|
||||||
|
session,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
session: Session | null;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const solanaEndpoint =
|
||||||
|
process.env.SOLANA_RPC_ENDPOINT || 'https://solana-rpc.publicnode.com';
|
||||||
|
return (
|
||||||
|
<SessionProvider session={session}>
|
||||||
|
<WagmiProvider>
|
||||||
|
<MiniAppProvider
|
||||||
|
analyticsEnabled={ANALYTICS_ENABLED}
|
||||||
|
backButtonEnabled={true}
|
||||||
|
>
|
||||||
|
<SafeFarcasterSolanaProvider endpoint={solanaEndpoint}>
|
||||||
|
<AuthKitProvider config={{}}>
|
||||||
|
{children}
|
||||||
|
</AuthKitProvider>
|
||||||
|
</SafeFarcasterSolanaProvider>
|
||||||
|
</MiniAppProvider>
|
||||||
|
</WagmiProvider>
|
||||||
|
</SessionProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import dynamic from 'next/dynamic';
|
|||||||
import { MiniAppProvider } from '@neynar/react';
|
import { MiniAppProvider } from '@neynar/react';
|
||||||
import { SafeFarcasterSolanaProvider } from '~/components/providers/SafeFarcasterSolanaProvider';
|
import { SafeFarcasterSolanaProvider } from '~/components/providers/SafeFarcasterSolanaProvider';
|
||||||
import { ANALYTICS_ENABLED } from '~/lib/constants';
|
import { ANALYTICS_ENABLED } from '~/lib/constants';
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
const WagmiProvider = dynamic(
|
const WagmiProvider = dynamic(
|
||||||
() => import('~/components/providers/WagmiProvider'),
|
() => import('~/components/providers/WagmiProvider'),
|
||||||
@ -13,107 +12,13 @@ const WagmiProvider = dynamic(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Helper component to conditionally render auth providers
|
|
||||||
function AuthProviders({
|
|
||||||
children,
|
|
||||||
session,
|
|
||||||
shouldUseSession,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
session: any;
|
|
||||||
shouldUseSession: boolean;
|
|
||||||
}) {
|
|
||||||
const [authComponents, setAuthComponents] = useState<{
|
|
||||||
SessionProvider: React.ComponentType<any> | null;
|
|
||||||
AuthKitProvider: React.ComponentType<any> | null;
|
|
||||||
loaded: boolean;
|
|
||||||
}>({
|
|
||||||
SessionProvider: null,
|
|
||||||
AuthKitProvider: null,
|
|
||||||
loaded: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!shouldUseSession) {
|
|
||||||
setAuthComponents({
|
|
||||||
SessionProvider: null,
|
|
||||||
AuthKitProvider: null,
|
|
||||||
loaded: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadAuthComponents = async () => {
|
|
||||||
try {
|
|
||||||
// Dynamic imports for auth modules
|
|
||||||
let SessionProvider = null;
|
|
||||||
let AuthKitProvider = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const nextAuth = await import('next-auth/react');
|
|
||||||
SessionProvider = nextAuth.SessionProvider;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('NextAuth not available:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authKit = await import('@farcaster/auth-kit');
|
|
||||||
AuthKitProvider = authKit.AuthKitProvider;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Farcaster AuthKit not available:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAuthComponents({
|
|
||||||
SessionProvider,
|
|
||||||
AuthKitProvider,
|
|
||||||
loaded: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading auth components:', error);
|
|
||||||
setAuthComponents({
|
|
||||||
SessionProvider: null,
|
|
||||||
AuthKitProvider: null,
|
|
||||||
loaded: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadAuthComponents();
|
|
||||||
}, [shouldUseSession]);
|
|
||||||
|
|
||||||
if (!authComponents.loaded) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldUseSession || !authComponents.SessionProvider) {
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { SessionProvider, AuthKitProvider } = authComponents;
|
|
||||||
|
|
||||||
if (AuthKitProvider) {
|
|
||||||
return (
|
|
||||||
<SessionProvider session={session}>
|
|
||||||
<AuthKitProvider config={{}}>{children}</AuthKitProvider>
|
|
||||||
</SessionProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <SessionProvider session={session}>{children}</SessionProvider>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Providers({
|
export function Providers({
|
||||||
session,
|
|
||||||
children,
|
children,
|
||||||
shouldUseSession = false,
|
|
||||||
}: {
|
}: {
|
||||||
session: any | null;
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
shouldUseSession?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
const solanaEndpoint =
|
const solanaEndpoint =
|
||||||
process.env.SOLANA_RPC_ENDPOINT || 'https://solana-rpc.publicnode.com';
|
process.env.SOLANA_RPC_ENDPOINT || 'https://solana-rpc.publicnode.com';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WagmiProvider>
|
<WagmiProvider>
|
||||||
<MiniAppProvider
|
<MiniAppProvider
|
||||||
@ -121,9 +26,9 @@ export function Providers({
|
|||||||
backButtonEnabled={true}
|
backButtonEnabled={true}
|
||||||
>
|
>
|
||||||
<SafeFarcasterSolanaProvider endpoint={solanaEndpoint}>
|
<SafeFarcasterSolanaProvider endpoint={solanaEndpoint}>
|
||||||
<AuthProviders session={session} shouldUseSession={shouldUseSession}>
|
<AuthKitProvider config={{}}>
|
||||||
{children}
|
{children}
|
||||||
</AuthProviders>
|
</AuthKitProvider>
|
||||||
</SafeFarcasterSolanaProvider>
|
</SafeFarcasterSolanaProvider>
|
||||||
</MiniAppProvider>
|
</MiniAppProvider>
|
||||||
</WagmiProvider>
|
</WagmiProvider>
|
||||||
|
|||||||
208
src/components/ui/tabs/ActionsTab.NeynarAuth.tsx
Normal file
208
src/components/ui/tabs/ActionsTab.NeynarAuth.tsx
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
'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/miniapp-sdk';
|
||||||
|
import { APP_URL } from '~/lib/constants';
|
||||||
|
import { NeynarAuthButton } from '../NeynarAuthButton';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* - Send notifications to their account
|
||||||
|
* - 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 />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function ActionsTab() {
|
||||||
|
// --- Hooks ---
|
||||||
|
const { actions, added, notificationDetails, haptics, context } =
|
||||||
|
useMiniApp();
|
||||||
|
|
||||||
|
// --- State ---
|
||||||
|
const [notificationState, setNotificationState] = useState({
|
||||||
|
sendStatus: '',
|
||||||
|
shareUrlCopied: false,
|
||||||
|
});
|
||||||
|
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: '' }));
|
||||||
|
if (!notificationDetails || !context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
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' }));
|
||||||
|
return;
|
||||||
|
} else if (response.status === 429) {
|
||||||
|
setNotificationState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
sendStatus: 'Rate limited',
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const responseText = await response.text();
|
||||||
|
setNotificationState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
sendStatus: `Error: ${responseText}`,
|
||||||
|
}));
|
||||||
|
} catch (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.
|
||||||
|
*/
|
||||||
|
const copyUserShareUrl = useCallback(async () => {
|
||||||
|
if (context?.user?.fid) {
|
||||||
|
const userShareUrl = `${APP_URL}/share/${context.user.fid}`;
|
||||||
|
await navigator.clipboard.writeText(userShareUrl);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
const triggerHapticFeedback = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await haptics.impactOccurred(selectedHapticIntensity);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Haptic feedback failed:', error);
|
||||||
|
}
|
||||||
|
}, [haptics, selectedHapticIntensity]);
|
||||||
|
|
||||||
|
// --- Render ---
|
||||||
|
return (
|
||||||
|
<div className="space-y-3 px-6 w-full max-w-md mx-auto">
|
||||||
|
{/* Share functionality */}
|
||||||
|
<ShareButton
|
||||||
|
buttonText="Share Mini App"
|
||||||
|
cast={{
|
||||||
|
text: 'Check out this awesome frame @1 @2 @3! 🚀🪐',
|
||||||
|
bestFriends: true,
|
||||||
|
embeds: [`${APP_URL}/share/${context?.user?.fid || ''}`],
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Authentication */}
|
||||||
|
<SignIn />
|
||||||
|
|
||||||
|
{/* Neynar Authentication */}
|
||||||
|
<NeynarAuthButton />
|
||||||
|
|
||||||
|
{/* Mini app actions */}
|
||||||
|
<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
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Notification functionality */}
|
||||||
|
{notificationState.sendStatus && (
|
||||||
|
<div className="text-sm w-full">
|
||||||
|
Send notification result: {notificationState.sendStatus}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={sendFarcasterNotification}
|
||||||
|
disabled={!notificationDetails}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Send notification
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Share URL copying */}
|
||||||
|
<Button
|
||||||
|
onClick={copyUserShareUrl}
|
||||||
|
disabled={!context?.user?.fid}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{notificationState.shareUrlCopied ? 'Copied!' : 'Copy share URL'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Haptic feedback controls */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Haptic Intensity
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={selectedHapticIntensity}
|
||||||
|
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>
|
||||||
|
<option value={'medium'}>Medium</option>
|
||||||
|
<option value={'heavy'}>Heavy</option>
|
||||||
|
<option value={'soft'}>Soft</option>
|
||||||
|
<option value={'rigid'}>Rigid</option>
|
||||||
|
</select>
|
||||||
|
<Button onClick={triggerHapticFeedback} className="w-full">
|
||||||
|
Trigger Haptic Feedback
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useMiniApp } from '@neynar/react';
|
import { useMiniApp } from '@neynar/react';
|
||||||
import { ShareButton } from '../Share';
|
import { ShareButton } from '../Share';
|
||||||
@ -9,15 +8,6 @@ import { SignIn } from '../wallet/SignIn';
|
|||||||
import { type Haptics } from '@farcaster/miniapp-sdk';
|
import { type Haptics } from '@farcaster/miniapp-sdk';
|
||||||
import { APP_URL } from '~/lib/constants';
|
import { APP_URL } from '~/lib/constants';
|
||||||
|
|
||||||
// Import NeynarAuthButton
|
|
||||||
const NeynarAuthButton = dynamic(
|
|
||||||
() =>
|
|
||||||
import('../NeynarAuthButton').then((module) => ({
|
|
||||||
default: module.NeynarAuthButton,
|
|
||||||
})),
|
|
||||||
{ ssr: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActionsTab component handles mini app actions like sharing, notifications, and haptic feedback.
|
* ActionsTab component handles mini app actions like sharing, notifications, and haptic feedback.
|
||||||
*
|
*
|
||||||
@ -148,9 +138,6 @@ export function ActionsTab() {
|
|||||||
{/* Authentication */}
|
{/* Authentication */}
|
||||||
<SignIn />
|
<SignIn />
|
||||||
|
|
||||||
{/* Neynar Authentication */}
|
|
||||||
{NeynarAuthButton && <NeynarAuthButton />}
|
|
||||||
|
|
||||||
{/* Mini app actions */}
|
{/* Mini app actions */}
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user