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,6 +1,6 @@
import NextAuth from "next-auth"
import { authOptions } from "~/auth"
import NextAuth from 'next-auth';
import { authOptions } from '~/auth';
const handler = NextAuth(authOptions)
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST }
export { handler as GET, handler as POST };

View File

@@ -4,10 +4,13 @@ export async function GET(request: Request) {
const apiKey = process.env.NEYNAR_API_KEY;
const { searchParams } = new URL(request.url);
const fid = searchParams.get('fid');
if (!apiKey) {
return NextResponse.json(
{ error: 'Neynar API key is not configured. Please add NEYNAR_API_KEY to your environment variables.' },
{
error:
'Neynar API key is not configured. Please add NEYNAR_API_KEY to your environment variables.',
},
{ status: 500 }
);
}
@@ -24,7 +27,7 @@ export async function GET(request: Request) {
`https://api.neynar.com/v2/farcaster/user/best_friends?fid=${fid}&limit=3`,
{
headers: {
"x-api-key": apiKey,
'x-api-key': apiKey,
},
}
);
@@ -33,14 +36,19 @@ export async function GET(request: Request) {
throw new Error(`Neynar API error: ${response.statusText}`);
}
const { users } = await response.json() as { users: { user: { fid: number; username: string } }[] };
const { users } = (await response.json()) as {
users: { user: { fid: number; username: string } }[];
};
return NextResponse.json({ bestFriends: users });
} catch (error) {
console.error('Failed to fetch best friends:', error);
return NextResponse.json(
{ error: 'Failed to fetch best friends. Please check your Neynar API key and try again.' },
{
error:
'Failed to fetch best friends. Please check your Neynar API key and try again.',
},
{ status: 500 }
);
}
}
}

View File

@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
import { NextRequest } from "next/server";
import { getNeynarUser } from "~/lib/neynar";
import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';
import { getNeynarUser } from '~/lib/neynar';
export const dynamic = 'force-dynamic';
@@ -15,10 +15,18 @@ export async function GET(request: NextRequest) {
<div tw="flex h-full w-full flex-col justify-center items-center relative bg-primary">
{user?.pfp_url && (
<div tw="flex w-96 h-96 rounded-full overflow-hidden mb-8 border-8 border-white">
<img src={user.pfp_url} alt="Profile" tw="w-full h-full object-cover" />
<img
src={user.pfp_url}
alt="Profile"
tw="w-full h-full object-cover"
/>
</div>
)}
<h1 tw="text-8xl text-white">{user?.display_name ? `Hello from ${user.display_name ?? user.username}!` : 'Hello!'}</h1>
<h1 tw="text-8xl text-white">
{user?.display_name
? `Hello from ${user.display_name ?? user.username}!`
: 'Hello!'}
</h1>
<p tw="text-5xl mt-4 text-white opacity-80">Powered by Neynar 🪐</p>
</div>
),
@@ -27,4 +35,4 @@ export async function GET(request: NextRequest) {
height: 800,
}
);
}
}

View File

@@ -1,9 +1,9 @@
import { notificationDetailsSchema } from "@farcaster/frame-sdk";
import { NextRequest } from "next/server";
import { z } from "zod";
import { setUserNotificationDetails } from "~/lib/kv";
import { sendMiniAppNotification } from "~/lib/notifs";
import { sendNeynarMiniAppNotification } from "~/lib/neynar";
import { notificationDetailsSchema } from '@farcaster/frame-sdk';
import { NextRequest } from 'next/server';
import { z } from 'zod';
import { setUserNotificationDetails } from '~/lib/kv';
import { sendMiniAppNotification } from '~/lib/notifs';
import { sendNeynarMiniAppNotification } from '~/lib/neynar';
const requestSchema = z.object({
fid: z.number(),
@@ -13,7 +13,8 @@ const requestSchema = z.object({
export async function POST(request: NextRequest) {
// If Neynar is enabled, we don't need to store notification details
// as they will be managed by Neynar's system
const neynarEnabled = process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID;
const neynarEnabled =
process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID;
const requestJson = await request.json();
const requestBody = requestSchema.safeParse(requestJson);
@@ -34,21 +35,23 @@ export async function POST(request: NextRequest) {
}
// Use appropriate notification function based on Neynar status
const sendNotification = neynarEnabled ? sendNeynarMiniAppNotification : sendMiniAppNotification;
const sendNotification = neynarEnabled
? sendNeynarMiniAppNotification
: sendMiniAppNotification;
const sendResult = await sendNotification({
fid: Number(requestBody.data.fid),
title: "Test notification",
body: "Sent at " + new Date().toISOString(),
title: 'Test notification',
body: 'Sent at ' + new Date().toISOString(),
});
if (sendResult.state === "error") {
if (sendResult.state === 'error') {
return Response.json(
{ success: false, error: sendResult.error },
{ status: 500 }
);
} else if (sendResult.state === "rate_limit") {
} else if (sendResult.state === 'rate_limit') {
return Response.json(
{ success: false, error: "Rate limited" },
{ success: false, error: 'Rate limited' },
{ status: 429 }
);
}

View File

@@ -5,10 +5,13 @@ export async function GET(request: Request) {
const apiKey = process.env.NEYNAR_API_KEY;
const { searchParams } = new URL(request.url);
const fids = searchParams.get('fids');
if (!apiKey) {
return NextResponse.json(
{ error: 'Neynar API key is not configured. Please add NEYNAR_API_KEY to your environment variables.' },
{
error:
'Neynar API key is not configured. Please add NEYNAR_API_KEY to your environment variables.',
},
{ status: 500 }
);
}
@@ -23,7 +26,7 @@ export async function GET(request: Request) {
try {
const neynar = new NeynarAPIClient({ apiKey });
const fidsArray = fids.split(',').map(fid => parseInt(fid.trim()));
const { users } = await neynar.fetchBulkUsers({
fids: fidsArray,
});
@@ -32,7 +35,10 @@ export async function GET(request: Request) {
} catch (error) {
console.error('Failed to fetch users:', error);
return NextResponse.json(
{ error: 'Failed to fetch users. Please check your Neynar API key and try again.' },
{
error:
'Failed to fetch users. Please check your Neynar API key and try again.',
},
{ status: 500 }
);
}

View File

@@ -2,19 +2,20 @@ import {
ParseWebhookEvent,
parseWebhookEvent,
verifyAppKeyWithNeynar,
} from "@farcaster/frame-node";
import { NextRequest } from "next/server";
import { APP_NAME } from "~/lib/constants";
} from '@farcaster/frame-node';
import { NextRequest } from 'next/server';
import { APP_NAME } from '~/lib/constants';
import {
deleteUserNotificationDetails,
setUserNotificationDetails,
} from "~/lib/kv";
import { sendMiniAppNotification } from "~/lib/notifs";
} from '~/lib/kv';
import { sendMiniAppNotification } from '~/lib/notifs';
export async function POST(request: NextRequest) {
// If Neynar is enabled, we don't need to handle webhooks here
// as they will be handled by Neynar's webhook endpoint
const neynarEnabled = process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID;
const neynarEnabled =
process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID;
if (neynarEnabled) {
return Response.json({ success: true });
}
@@ -28,20 +29,20 @@ export async function POST(request: NextRequest) {
const error = e as ParseWebhookEvent.ErrorType;
switch (error.name) {
case "VerifyJsonFarcasterSignature.InvalidDataError":
case "VerifyJsonFarcasterSignature.InvalidEventDataError":
case 'VerifyJsonFarcasterSignature.InvalidDataError':
case 'VerifyJsonFarcasterSignature.InvalidEventDataError':
// The request data is invalid
return Response.json(
{ success: false, error: error.message },
{ status: 400 }
);
case "VerifyJsonFarcasterSignature.InvalidAppKeyError":
case 'VerifyJsonFarcasterSignature.InvalidAppKeyError':
// The app key is invalid
return Response.json(
{ success: false, error: error.message },
{ status: 401 }
);
case "VerifyJsonFarcasterSignature.VerifyAppKeyError":
case 'VerifyJsonFarcasterSignature.VerifyAppKeyError':
// Internal error verifying the app key (caller may want to try again)
return Response.json(
{ success: false, error: error.message },
@@ -56,33 +57,33 @@ export async function POST(request: NextRequest) {
// Only handle notifications if Neynar is not enabled
// When Neynar is enabled, notifications are handled through their webhook
switch (event.event) {
case "frame_added":
case 'frame_added':
if (event.notificationDetails) {
await setUserNotificationDetails(fid, event.notificationDetails);
await sendMiniAppNotification({
fid,
title: `Welcome to ${APP_NAME}`,
body: "Mini app is now added to your client",
body: 'Mini app is now added to your client',
});
} else {
await deleteUserNotificationDetails(fid);
}
break;
case "frame_removed":
case 'frame_removed':
await deleteUserNotificationDetails(fid);
break;
case "notifications_enabled":
case 'notifications_enabled':
await setUserNotificationDetails(fid, event.notificationDetails);
await sendMiniAppNotification({
fid,
title: `Welcome to ${APP_NAME}`,
body: "Notifications are now enabled",
body: 'Notifications are now enabled',
});
break;
case "notifications_disabled":
case 'notifications_disabled':
await deleteUserNotificationDetails(fid);
break;
}

View File

@@ -1,10 +1,10 @@
"use client";
'use client';
import dynamic from "next/dynamic";
import { APP_NAME } from "~/lib/constants";
import dynamic from 'next/dynamic';
import { APP_NAME } from '~/lib/constants';
// note: dynamic import is required for components that use the Frame SDK
const AppComponent = dynamic(() => import("~/components/App"), {
const AppComponent = dynamic(() => import('~/components/App'), {
ssr: false,
});

View File

@@ -62,11 +62,11 @@ body {
.container {
@apply mx-auto max-w-md px-4;
}
.container-wide {
@apply mx-auto max-w-lg px-4;
}
.container-narrow {
@apply mx-auto max-w-sm px-4;
}
@@ -75,7 +75,7 @@ body {
.card {
@apply bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm;
}
.card-primary {
@apply bg-primary/10 border-primary/20;
}
@@ -84,15 +84,15 @@ body {
.btn {
@apply inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none;
}
.btn-primary {
@apply bg-primary text-white hover:bg-primary-dark focus:ring-primary;
}
.btn-secondary {
@apply bg-secondary text-gray-900 hover:bg-gray-200 focus:ring-gray-500 dark:bg-secondary-dark dark:text-gray-100 dark:hover:bg-gray-600;
}
.btn-outline {
@apply border border-gray-300 bg-transparent hover:bg-gray-50 focus:ring-gray-500 dark:border-gray-600 dark:hover:bg-gray-800;
}
@@ -106,7 +106,7 @@ body {
.spinner {
@apply animate-spin rounded-full border-2 border-gray-300 border-t-primary;
}
.spinner-primary {
@apply animate-spin rounded-full border-2 border-white border-t-transparent;
}

View File

@@ -1,9 +1,9 @@
import type { Metadata } from "next";
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";
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,
@@ -14,8 +14,8 @@ export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getSession()
}>) {
const session = await getSession();
return (
<html lang="en">

View File

@@ -1,7 +1,7 @@
import { Metadata } from "next";
import App from "./app";
import { APP_NAME, APP_DESCRIPTION, APP_OG_IMAGE_URL } from "~/lib/constants";
import { getMiniAppEmbedMetadata } from "~/lib/utils";
import { Metadata } from 'next';
import App from './app';
import { APP_NAME, APP_DESCRIPTION, APP_OG_IMAGE_URL } from '~/lib/constants';
import { getMiniAppEmbedMetadata } from '~/lib/utils';
export const revalidate = 300;
@@ -14,11 +14,11 @@ export async function generateMetadata(): Promise<Metadata> {
images: [APP_OG_IMAGE_URL],
},
other: {
"fc:frame": JSON.stringify(getMiniAppEmbedMetadata()),
'fc:frame': JSON.stringify(getMiniAppEmbedMetadata()),
},
};
}
export default function Home() {
return (<App />);
return <App />;
}

View File

@@ -1,25 +1,35 @@
"use client";
'use client';
import dynamic from "next/dynamic";
import type { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
import { MiniAppProvider } from "@neynar/react";
import { SafeFarcasterSolanaProvider } from "~/components/providers/SafeFarcasterSolanaProvider";
import { ANALYTICS_ENABLED } from "~/lib/constants";
import dynamic from 'next/dynamic';
import type { Session } from 'next-auth';
import { SessionProvider } from 'next-auth/react';
import { MiniAppProvider } from '@neynar/react';
import { SafeFarcasterSolanaProvider } from '~/components/providers/SafeFarcasterSolanaProvider';
import { ANALYTICS_ENABLED } from '~/lib/constants';
const WagmiProvider = dynamic(
() => import("~/components/providers/WagmiProvider"),
() => 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";
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}>
<MiniAppProvider
analyticsEnabled={ANALYTICS_ENABLED}
backButtonEnabled={true}
>
<SafeFarcasterSolanaProvider endpoint={solanaEndpoint}>
{children}
</SafeFarcasterSolanaProvider>

View File

@@ -1,7 +1,7 @@
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { APP_URL, APP_NAME, APP_DESCRIPTION } from "~/lib/constants";
import { getMiniAppEmbedMetadata } from "~/lib/utils";
import type { Metadata } from 'next';
import { redirect } from 'next/navigation';
import { APP_URL, APP_NAME, APP_DESCRIPTION } from '~/lib/constants';
import { getMiniAppEmbedMetadata } from '~/lib/utils';
export const revalidate = 300;
// This is an example of how to generate a dynamically generated share page based on fid:
@@ -23,12 +23,12 @@ export async function generateMetadata({
images: [imageUrl],
},
other: {
"fc:frame": JSON.stringify(getMiniAppEmbedMetadata(imageUrl)),
'fc:frame': JSON.stringify(getMiniAppEmbedMetadata(imageUrl)),
},
};
}
export default function SharePage() {
// redirect to home page
redirect("/");
redirect('/');
}