forked from mito-systems/sol-mem-gen
ok
This commit is contained in:
parent
9a2857bffd
commit
201a8510cc
35
package-lock.json
generated
35
package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "solana-meme-generator",
|
"name": "solana-meme-generator",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fal-ai/client": "^1.2.1",
|
||||||
"@solana/spl-token": "^0.3.8",
|
"@solana/spl-token": "^0.3.8",
|
||||||
"@solana/web3.js": "^1.78.4",
|
"@solana/web3.js": "^1.78.4",
|
||||||
"@types/node": "20.5.7",
|
"@types/node": "20.5.7",
|
||||||
@ -131,6 +132,19 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fal-ai/client": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fal-ai/client/-/client-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z/bztRqOeduuUYl74VPQRDS12vjXGGebROJM7NcJXCmkV4GSZllA68ES1GwY3Q+31puukBoOppC0iBmY6us5MQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@msgpack/msgpack": "^3.0.0-beta2",
|
||||||
|
"eventsource-parser": "^1.1.2",
|
||||||
|
"robot3": "^0.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.14",
|
"version": "0.11.14",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||||
@ -272,6 +286,14 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@msgpack/msgpack": {
|
||||||
|
"version": "3.0.0-beta2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz",
|
||||||
|
"integrity": "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "13.4.19",
|
"version": "13.4.19",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz",
|
||||||
@ -2609,6 +2631,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/eventsource-parser": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eyes": {
|
"node_modules/eyes": {
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
|
||||||
@ -4723,6 +4753,11 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/robot3": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/robot3/-/robot3-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-hzjy826lrxzx8eRgv80idkf8ua1JAepRc9Efdtj03N3KNJuznQCPlyCJ7gnUmDFwZCLQjxy567mQVKmdv2BsXQ=="
|
||||||
|
},
|
||||||
"node_modules/rpc-websockets": {
|
"node_modules/rpc-websockets": {
|
||||||
"version": "9.0.4",
|
"version": "9.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.0.4.tgz",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fal-ai/client": "^1.2.1",
|
||||||
"@solana/spl-token": "^0.3.8",
|
"@solana/spl-token": "^0.3.8",
|
||||||
"@solana/web3.js": "^1.78.4",
|
"@solana/web3.js": "^1.78.4",
|
||||||
"@types/node": "20.5.7",
|
"@types/node": "20.5.7",
|
||||||
|
52
src/app/api/generate/route.ts
Normal file
52
src/app/api/generate/route.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { fal } from "@fal-ai/client"
|
||||||
|
|
||||||
|
// Add debug logging
|
||||||
|
const FAL_KEY = process.env.FAL_AI_KEY
|
||||||
|
|
||||||
|
if (!FAL_KEY) {
|
||||||
|
throw new Error('FAL_AI_KEY is not configured in environment variables')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure fal client server-side only
|
||||||
|
fal.config({
|
||||||
|
credentials: FAL_KEY
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
|
try {
|
||||||
|
const { prompt } = await req.json()
|
||||||
|
|
||||||
|
if (!prompt) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Prompt is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Attempting Fal.ai request with prompt:', prompt)
|
||||||
|
|
||||||
|
const result = await fal.subscribe("fal-ai/flux-pro/v1.1", {
|
||||||
|
input: {
|
||||||
|
prompt
|
||||||
|
},
|
||||||
|
logs: true,
|
||||||
|
onQueueUpdate: (update) => {
|
||||||
|
if (update.status === "IN_PROGRESS") {
|
||||||
|
console.log('Generation progress:', update.logs.map((log) => log.message))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Fal.ai response received:', result ? 'Success' : 'Empty response')
|
||||||
|
return NextResponse.json(result)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Image generation error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : 'Failed to generate image' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic'
|
210
src/app/page.tsx
210
src/app/page.tsx
@ -2,21 +2,31 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
|
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
|
||||||
import { TOKEN_PROGRAM_ID, createTransferInstruction, getAssociatedTokenAddress } from '@solana/spl-token'
|
import {
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
createTransferInstruction,
|
||||||
|
getAssociatedTokenAddress,
|
||||||
|
createAssociatedTokenAccountInstruction as createATAInstruction,
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
} from '@solana/spl-token'
|
||||||
|
|
||||||
|
import { fal } from "@fal-ai/client"
|
||||||
|
|
||||||
interface WalletState {
|
interface WalletState {
|
||||||
connected: boolean
|
connected: boolean
|
||||||
publicKey: string | null
|
publicKey: string | null
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
interface PaymentStatus {
|
interface PaymentStatus {
|
||||||
paid: boolean
|
paid: boolean
|
||||||
processing: boolean
|
processing: boolean
|
||||||
error: string | null
|
error: string | null
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
interface GenerationState {
|
interface GenerationState {
|
||||||
loading: boolean
|
loading: boolean
|
||||||
|
processing: boolean
|
||||||
imageUrl: string | null
|
imageUrl: string | null
|
||||||
error: string | null
|
error: string | null
|
||||||
}
|
}
|
||||||
@ -41,7 +51,7 @@ declare global {
|
|||||||
|
|
||||||
// Replace these with your actual addresses
|
// Replace these with your actual addresses
|
||||||
const MTM_TOKEN_MINT: string = '97RggLo3zV5kFGYW4yoQTxr4Xkz4Vg2WPHzNYXXWpump'
|
const MTM_TOKEN_MINT: string = '97RggLo3zV5kFGYW4yoQTxr4Xkz4Vg2WPHzNYXXWpump'
|
||||||
const PAYMENT_RECEIVER_ADDRESS: string = 'JB8YCqKBKNtS4ZHcGPJXSJokcvdgdeeyWRxGqgyX5EwP'
|
const PAYMENT_RECEIVER_ADDRESS: string = '9B3mGyeJTUN7ZTqyLWHLL37zL92eif239hH2pYSkvq8J'
|
||||||
const REQUIRED_PAYMENT_AMOUNT: number = 2
|
const REQUIRED_PAYMENT_AMOUNT: number = 2
|
||||||
const SOLANA_NETWORK: string = 'mainnet'
|
const SOLANA_NETWORK: string = 'mainnet'
|
||||||
const SOLANA_RPC_URL: string = 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'
|
const SOLANA_RPC_URL: string = 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'
|
||||||
@ -60,14 +70,10 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
connected: false,
|
connected: false,
|
||||||
publicKey: null,
|
publicKey: null,
|
||||||
})
|
})
|
||||||
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>({
|
|
||||||
paid: false,
|
|
||||||
processing: false,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
const [inputText, setInputText] = useState<string>('')
|
const [inputText, setInputText] = useState<string>('')
|
||||||
const [generationState, setGenerationState] = useState<GenerationState>({
|
const [generationState, setGenerationState] = useState<GenerationState>({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
processing: false,
|
||||||
imageUrl: null,
|
imageUrl: null,
|
||||||
error: null,
|
error: null,
|
||||||
})
|
})
|
||||||
@ -98,20 +104,24 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const processPayment = async (): Promise<void> => {
|
const processPaymentAndGenerate = async (): Promise<void> => {
|
||||||
if (!walletState.connected || !walletState.publicKey || !window.solflare) {
|
if (!walletState.connected || !walletState.publicKey || !window.solflare || !inputText) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setPaymentStatus({ ...paymentStatus, processing: true, error: null })
|
setGenerationState({
|
||||||
|
...generationState,
|
||||||
|
processing: true,
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Convert string addresses to PublicKeys
|
// Process payment first
|
||||||
const senderPublicKey = new PublicKey(walletState.publicKey)
|
const senderPublicKey = new PublicKey(walletState.publicKey)
|
||||||
const mintPublicKey = new PublicKey(MTM_TOKEN_MINT)
|
const mintPublicKey = new PublicKey(MTM_TOKEN_MINT)
|
||||||
const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS)
|
const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS)
|
||||||
|
|
||||||
// Get the associated token accounts for sender and receiver
|
// Get the associated token accounts
|
||||||
const senderATA = await getAssociatedTokenAddress(
|
const senderATA = await getAssociatedTokenAddress(
|
||||||
mintPublicKey,
|
mintPublicKey,
|
||||||
senderPublicKey
|
senderPublicKey
|
||||||
@ -121,112 +131,113 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
receiverPublicKey
|
receiverPublicKey
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create transfer instruction
|
// Create transaction
|
||||||
|
const transaction = new Transaction()
|
||||||
|
|
||||||
|
// Check if receiver's ATA exists
|
||||||
|
const receiverATAInfo = await connection.getAccountInfo(receiverATA)
|
||||||
|
if (!receiverATAInfo) {
|
||||||
|
console.log('Creating receiver ATA...')
|
||||||
|
transaction.add(
|
||||||
|
createATAInstruction(
|
||||||
|
senderPublicKey, // payer
|
||||||
|
receiverATA, // ata
|
||||||
|
receiverPublicKey, // owner
|
||||||
|
mintPublicKey, // mint
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if sender's ATA exists
|
||||||
|
const senderATAInfo = await connection.getAccountInfo(senderATA)
|
||||||
|
if (!senderATAInfo) {
|
||||||
|
console.log('Creating sender ATA...')
|
||||||
|
transaction.add(
|
||||||
|
createATAInstruction(
|
||||||
|
senderPublicKey, // payer
|
||||||
|
senderATA, // ata
|
||||||
|
senderPublicKey, // owner
|
||||||
|
mintPublicKey, // mint
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add transfer instruction
|
||||||
const transferInstruction = createTransferInstruction(
|
const transferInstruction = createTransferInstruction(
|
||||||
senderATA,
|
senderATA,
|
||||||
receiverATA,
|
receiverATA,
|
||||||
senderPublicKey,
|
senderPublicKey,
|
||||||
BigInt(REQUIRED_PAYMENT_AMOUNT * (10 ** 6)) // Convert to proper format
|
BigInt(REQUIRED_PAYMENT_AMOUNT * (10 ** 9))
|
||||||
)
|
)
|
||||||
|
transaction.add(transferInstruction)
|
||||||
|
|
||||||
const latestBlockhash = await connection.getLatestBlockhash('confirmed')
|
const latestBlockhash = await connection.getLatestBlockhash('confirmed')
|
||||||
|
|
||||||
// Create transaction
|
|
||||||
const transaction = new Transaction()
|
|
||||||
transaction.add(transferInstruction)
|
|
||||||
transaction.recentBlockhash = latestBlockhash.blockhash
|
transaction.recentBlockhash = latestBlockhash.blockhash
|
||||||
transaction.feePayer = senderPublicKey
|
transaction.feePayer = senderPublicKey
|
||||||
|
|
||||||
try {
|
console.log('Sending transaction...')
|
||||||
// Sign and send transaction with preflight disabled
|
// Sign and send transaction
|
||||||
const { signature } = await window.solflare.signAndSendTransaction(transaction, {
|
const { signature } = await window.solflare.signAndSendTransaction(transaction, {
|
||||||
skipPreflight: true
|
skipPreflight: false // Enable preflight checks
|
||||||
})
|
|
||||||
|
|
||||||
console.log('Transaction sent:', signature)
|
|
||||||
|
|
||||||
// Wait for confirmation with WebSocket support
|
|
||||||
const confirmation = await connection.confirmTransaction({
|
|
||||||
signature,
|
|
||||||
blockhash: latestBlockhash.blockhash,
|
|
||||||
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
||||||
}, 'confirmed')
|
|
||||||
|
|
||||||
console.log('Transaction confirmed:', confirmation)
|
|
||||||
|
|
||||||
if (confirmation.value.err) {
|
|
||||||
throw new Error('Transaction failed to confirm')
|
|
||||||
}
|
|
||||||
|
|
||||||
setPaymentStatus({
|
|
||||||
paid: true,
|
|
||||||
processing: false,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Transaction error:', error)
|
|
||||||
throw new Error(`Failed to sign or send transaction: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Payment error:', error)
|
|
||||||
setPaymentStatus({
|
|
||||||
...paymentStatus,
|
|
||||||
processing: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Payment failed. Please try again.',
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateMeme = async (): Promise<void> => {
|
console.log('Transaction sent:', signature)
|
||||||
if (!inputText || !paymentStatus.paid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setGenerationState({
|
// Wait for confirmation
|
||||||
...generationState,
|
const confirmation = await connection.confirmTransaction({
|
||||||
loading: true,
|
signature,
|
||||||
error: null,
|
blockhash: latestBlockhash.blockhash,
|
||||||
})
|
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
||||||
|
}, 'confirmed')
|
||||||
|
|
||||||
try {
|
console.log('Transaction confirmed:', confirmation)
|
||||||
const response = await fetch('/api/proxy', {
|
|
||||||
|
if (confirmation.value.err) {
|
||||||
|
throw new Error('Payment failed to confirm')
|
||||||
|
}
|
||||||
|
|
||||||
|
// After payment is confirmed, generate the meme
|
||||||
|
setGenerationState(prev => ({ ...prev, loading: true }))
|
||||||
|
|
||||||
|
// Call our secure API endpoint instead of direct Fal.ai call
|
||||||
|
const response = await fetch('/api/generate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
protocol: 'https',
|
prompt: inputText,
|
||||||
origin: 'api.openai.com',
|
|
||||||
path: '/v1/images/generations',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
prompt: `Generate a meme with text: ${inputText}`,
|
|
||||||
n: 1,
|
|
||||||
size: '512x512',
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await response.json()
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to generate image')
|
||||||
|
}
|
||||||
|
|
||||||
if (data.data && data.data[0].url) {
|
const result = await response.json()
|
||||||
|
console.log("Generation Response:", result)
|
||||||
|
|
||||||
|
if (result.data && result.data.images && result.data.images[0]?.url) {
|
||||||
setGenerationState({
|
setGenerationState({
|
||||||
loading: false,
|
loading: false,
|
||||||
imageUrl: data.data[0].url,
|
processing: false,
|
||||||
|
imageUrl: result.data.images[0].url,
|
||||||
error: null,
|
error: null,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to generate image')
|
throw new Error('No image URL in response')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Generation error:', error)
|
||||||
setGenerationState({
|
setGenerationState({
|
||||||
...generationState,
|
...generationState,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: 'Failed to generate meme. Please try again.',
|
processing: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to process payment or generate meme.',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,7 +250,7 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
AI Meme Generator
|
AI Meme Generator
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg text-gray-600">
|
<p className="text-lg text-gray-600">
|
||||||
Connect your Solflare wallet and pay with MTM tokens to generate custom memes!
|
Connect your Solflare wallet and pay {REQUIRED_PAYMENT_AMOUNT} MTM token per meme generation!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -251,24 +262,11 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
>
|
>
|
||||||
Connect Solflare Wallet
|
Connect Solflare Wallet
|
||||||
</button>
|
</button>
|
||||||
) : !paymentStatus.paid ? (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-green-600">
|
<p className="text-green-600">
|
||||||
Wallet Connected: {walletState.publicKey?.slice(0, 8)}...
|
Wallet Connected: {walletState.publicKey?.slice(0, 8)}...
|
||||||
</p>
|
</p>
|
||||||
<button
|
|
||||||
onClick={processPayment}
|
|
||||||
disabled={paymentStatus.processing}
|
|
||||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors disabled:bg-blue-300"
|
|
||||||
>
|
|
||||||
{paymentStatus.processing ? 'Processing...' : `Pay ${REQUIRED_PAYMENT_AMOUNT} MTM Tokens`}
|
|
||||||
</button>
|
|
||||||
{paymentStatus.error && (
|
|
||||||
<p className="text-red-600">{paymentStatus.error}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<textarea
|
<textarea
|
||||||
value={inputText}
|
value={inputText}
|
||||||
onChange={(e) => setInputText(e.target.value)}
|
onChange={(e) => setInputText(e.target.value)}
|
||||||
@ -277,11 +275,13 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={generateMeme}
|
onClick={processPaymentAndGenerate}
|
||||||
disabled={generationState.loading || !inputText}
|
disabled={generationState.processing || generationState.loading || !inputText}
|
||||||
className="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 transition-colors disabled:bg-green-300"
|
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors disabled:bg-blue-300"
|
||||||
>
|
>
|
||||||
{generationState.loading ? 'Generating...' : 'Generate Meme'}
|
{generationState.processing ? 'Processing Payment...' :
|
||||||
|
generationState.loading ? 'Generating...' :
|
||||||
|
`Pay ${REQUIRED_PAYMENT_AMOUNT} MTM & Generate Meme`}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
Loading…
Reference in New Issue
Block a user