From 95416732da44ab70ce40ed2dfac06eef0e252028 Mon Sep 17 00:00:00 2001 From: Shreerang Kale Date: Thu, 6 Feb 2025 19:07:11 +0530 Subject: [PATCH 1/2] Add form to submit tweets for verification --- .env.example | 1 + README.md | 11 ++++-- src/app/api/tweet/route.ts | 11 ++++-- src/app/submit-tweets/[secret]/page.tsx | 16 +++++++++ src/components/AIServiceCard.tsx | 25 +++++++------ .../{TweetForm.tsx => TweetUrlForm.tsx} | 35 ++++++++++++------- 6 files changed, 72 insertions(+), 27 deletions(-) create mode 100644 src/app/submit-tweets/[secret]/page.tsx rename src/components/{TweetForm.tsx => TweetUrlForm.tsx} (56%) diff --git a/.env.example b/.env.example index 720dbca..1546e0a 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,7 @@ PINATA_GATEWAY= # Change to your website URL # For development: SITE_URL=http://localhost:3000 SITE_URL=https://memes.markto.market +VERIFY_TWEET_SECRET= NEXT_PUBLIC_TWITTER_HANDLE= WSOL_LOCKER_ACCOUNT_PK= diff --git a/README.md b/README.md index 0472fc5..271c6c2 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ This project is a Solana-based meme generator that allows users to connect their npm install ``` -- Copy the `.env.example` file to `.env.local` and add your API keys: +- Copy the `.env.example` file to `.env` and add your API keys: ```sh cp .env.example .env.local ``` -- Add your FAL AI key to the `.env.local` file: +- Add your FAL AI key to the `.env` file: ```env # Get key from https://fal.ai/ @@ -51,8 +51,13 @@ This project is a Solana-based meme generator that allows users to connect their # Get the gateway from https://app.pinata.cloud/gateway PINATA_GATEWAY= - # Add the account handle to be set in the tweet + # Mark to market twitter handle: @mark_2_market1 NEXT_PUBLIC_TWITTER_HANDLE= + + # Add a secret random string that will be used to access the page to submit tweets for verification + # This secret will be checked against the URL to either allow or deny access + # The URL should look something like this: http://localhost:3000/submit-tweets/ + VERIFY_TWEET_SECRET= ``` - Run the development server: diff --git a/src/app/api/tweet/route.ts b/src/app/api/tweet/route.ts index 26a0be3..5485880 100644 --- a/src/app/api/tweet/route.ts +++ b/src/app/api/tweet/route.ts @@ -5,7 +5,14 @@ import { extractData } from '../../../utils/tweetMessage'; export async function POST(req: NextRequest): Promise { try { - const { tweetUrl } = await req.json(); + const { tweetUrl, secret } = await req.json(); + + if(secret !== process.env.VERIFY_TWEET_SECRET) { + return NextResponse.json( + { error: 'Access denied' }, + { status: 401 } + ) + } const url = `https://publish.twitter.com/oembed?url=${tweetUrl}&maxwidth=600`; @@ -37,7 +44,7 @@ export async function POST(req: NextRequest): Promise { } catch (error) { console.error('Error while verifying tweet:', error) return NextResponse.json( - { error: 'Failed to verify tweet' }, + { error: error.message }, { status: 500 } ) } diff --git a/src/app/submit-tweets/[secret]/page.tsx b/src/app/submit-tweets/[secret]/page.tsx new file mode 100644 index 0000000..73c1f70 --- /dev/null +++ b/src/app/submit-tweets/[secret]/page.tsx @@ -0,0 +1,16 @@ +'use client' + +import TweetUrlForm from "../../../components/TweetUrlForm"; + +interface Props { + params: { secret: string }; +} + +export default function SubmitTweetPage({params}: Props) { + + return ( +
+ +
+ ); +} diff --git a/src/components/AIServiceCard.tsx b/src/components/AIServiceCard.tsx index 52d94dd..b2b1448 100644 --- a/src/components/AIServiceCard.tsx +++ b/src/components/AIServiceCard.tsx @@ -45,6 +45,7 @@ const AIServiceCard: React.FC = ({ transactionSignature: null, error: null, }) + const [isImageLoaded, setIsImageLoaded] = useState(false); const handleGenerate = async (): Promise => { if (!inputText || !isWalletConnected) return @@ -147,21 +148,25 @@ const AIServiceCard: React.FC = ({ {generationState.imageUrl && generationState.transactionSignature && (
+ {!isImageLoaded &&

Loading image...

} Generated content setIsImageLoaded(true)} /> - + {isImageLoaded && + + }
)} diff --git a/src/components/TweetForm.tsx b/src/components/TweetUrlForm.tsx similarity index 56% rename from src/components/TweetForm.tsx rename to src/components/TweetUrlForm.tsx index f020319..50c6623 100644 --- a/src/components/TweetForm.tsx +++ b/src/components/TweetUrlForm.tsx @@ -2,36 +2,47 @@ import React, { useState } from 'react' -const TweetUrlForm: React.FC = () => { +const TweetUrlForm: React.FC<{secret: string}> = ({secret}) => { const [inputText, setInputText] = useState('') - const handleVerify = async (): Promise => { + const [isSubmitted, setIsSubmitted] = useState(false); + const [loading, setLoading] = useState(false); + const [submitError, setSubmitError] = useState(null); + + const handleSubmit = async (): Promise => { try { + setLoading(true) const response = await fetch('/api/tweet', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ tweetUrl: inputText }), + body: JSON.stringify({ tweetUrl: inputText, secret }), }) + const parsedResponse = await response.json(); + if (!response.ok) { - throw new Error(`Failed to verify tweet: ${response.statusText}`); + setSubmitError(parsedResponse.error); + throw new Error(`Failed to submit tweet: ${response.statusText}`); } } catch (error) { - console.error('Failed to fetch price:', error); + setSubmitError(error.message); + console.error('Failed to submit tweet:', error); + } finally { + setIsSubmitted(true); + setLoading(false); + setInputText(''); } } return ( -
+
-
- -
+ {isSubmitted ? submitError ?

Submission failed: {submitError}

:

Tweet submitted!

: <>}