Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/app/_components/token-metadata-viewer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use client'

import { useEffect, useState } from 'react'

interface Props {
metadataUrl: string
}

export default function TokenMetadataViewer({ metadataUrl }: Props) {
const [error, setError] = useState<string | null>(null)
const [imagePreview, setImagePreview] = useState<string | null>(null)
const [json, setJson] = useState<any | null>(null)
const [imageError, setImageError] = useState(false)

const normalizeIpfsUrl = (url: string) => {
if (url.startsWith('ipfs://')) {
return url.replace('ipfs://', 'https://ipfs.io/ipfs/')
}
return url
}

useEffect(() => {
if (!metadataUrl) return

const url = normalizeIpfsUrl(metadataUrl)

fetch(url)
.then(async (res) => {
const contentType = res.headers.get('Content-Type') || ''
if (contentType.includes('application/json')) {
const data = await res.json()
setJson(data)
} else if (contentType.includes('image/')) {
setImagePreview(url)
setError('Received image instead of JSON metadata')
} else {
throw new Error(`Unexpected content type: ${contentType}`)
}
})
.catch((err) => {
console.error('Metadata fetch error:', err)
setError(err.message)
})
}, [metadataUrl])

return (
<div className="mt-6 bg-white border border-gray-200 rounded-lg p-4">
<h3 className="text-lg font-semibold mb-2">Metadata</h3>
<p className="text-sm text-gray-600 break-all">
<a
href={normalizeIpfsUrl(metadataUrl)}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline"
>
{metadataUrl}
</a>
</p>

{error && (
<p className="text-red-600 bg-red-50 border border-red-200 rounded p-3 mt-3 text-sm">
Failed to load metadata: {error}
</p>
)}

{imagePreview && !imageError && (
<div className="mt-4">
<p className="text-sm text-gray-700 mb-1">Image found instead of JSON:</p>
<img
src={imagePreview}
alt="Token metadata image"
className="w-48 h-auto border rounded shadow"
onError={() => setImageError(true)}
/>
</div>
)}

{imageError && (
<p className="text-gray-500 italic mt-2">Preview not available</p>
)}

{json && (
<pre className="mt-4 text-sm bg-gray-50 p-3 rounded border border-gray-100 overflow-auto max-h-96">
{JSON.stringify(json, null, 2)}
</pre>
)}
</div>
)
}
250 changes: 250 additions & 0 deletions src/app/_components/token-verification-form/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
'use client'

import { useState } from 'react'
import TokenLoader from '@/app/token-verify/TokenLoader'

export default function TokenVerificationForm() {
const [formData, setFormData] = useState({
tokenId: '',
ticker: '',
projectName: '',
description: '',
website: '',
logoUrl: '',
contactInfo: '',
email: '',
links: '',
submitterName: '',
agreed: false,
})

const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value, type, checked } = e.target as any
setFormData((prev) => ({
...prev,
[name]: type === 'checkbox' ? checked : value,
}))
}

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()

const { tokenId, ticker, projectName, description, email, agreed } = formData

if (!tokenId || !ticker || !projectName || !description || !email || !agreed) {
alert('Please fill in all required fields and agree to the terms.')
return
}

const TELEGRAM_BOT_TOKEN = process.env.NEXT_PUBLIC_TELEGRAM_BOT_TOKEN!
const TELEGRAM_CHAT_ID = process.env.NEXT_PUBLIC_TELEGRAM_CHAT_ID!

const message = `
🟦 *New token verification request*
🔹 *Token ID:* ${formData.tokenId}
🔹 *Ticker:* ${formData.ticker}
🔹 *Project Name:* ${formData.projectName}
🔹 *Description:* ${formData.description}
🔹 *Website:* ${formData.website || '-'}
🔹 *Logo:* ${formData.logoUrl || '-'}
🔹 *Contact Info:* ${formData.contactInfo || '-'}
🔹 *Additional Links:* ${formData.links || '-'}
🔹 *Submitter:* ${formData.submitterName || '-'}
📧 *Email:* ${formData.email}
`

try {
const res = await fetch(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
chat_id: TELEGRAM_CHAT_ID,
text: message,
parse_mode: 'Markdown',
}),
})

if (res.ok) {
alert('Request submitted successfully!')
// Reset form after successful submission
setFormData({
tokenId: '',
ticker: '',
projectName: '',
description: '',
website: '',
logoUrl: '',
contactInfo: '',
email: '',
links: '',
submitterName: '',
agreed: false,
})
} else {
alert('Error sending to Telegram.')
}
} catch (error) {
console.error('Telegram error:', error)
alert('Connection error with Telegram.')
}
}

return (
<div className="max-w-3xl mx-auto bg-white p-8 rounded-xl shadow-md mt-10">
<h2 className="text-3xl font-bold mb-6 text-gray-800">
Token Verification Request
</h2>

<TokenLoader setFormData={setFormData} />

<form onSubmit={handleSubmit} className="space-y-5 mt-6">
<div>
<label className="block font-medium mb-1">Token ID *</label>
<input
type="text"
name="tokenId"
value={formData.tokenId}
onChange={handleChange}
className="w-full p-2 border border-gray-300 rounded-md"
required
/>
</div>

<div>
<label className="block font-medium mb-1">Token Symbol (Ticker) *</label>
<input
type="text"
name="ticker"
value={formData.ticker}
onChange={handleChange}
className="w-full p-2 border border-gray-300 rounded-md"
required
/>
</div>

<div>
<label className="block font-medium mb-1">Project Name *</label>
<input
type="text"
name="projectName"
value={formData.projectName}
onChange={handleChange}
className="w-full p-2 border border-gray-300 rounded-md"
required
/>
</div>

<div>
<label className="block font-medium mb-1">Project Description *</label>
<textarea
name="description"
value={formData.description}
onChange={handleChange}
rows={4}
className="w-full p-2 border border-gray-300 rounded-md"
required
/>
</div>

<div>
<label className="block font-medium mb-1">Website (if available)</label>
<input
type="url"
name="website"
value={formData.website}
onChange={handleChange}
placeholder="https://example.com"
className="w-full p-2 border border-gray-300 rounded-md"
/>
</div>

<div>
<label className="block font-medium mb-1">Token Logo (URL)</label>
<input
type="url"
name="logoUrl"
value={formData.logoUrl}
onChange={handleChange}
placeholder="https://example.com/logo.png"
className="w-full p-2 border border-gray-300 rounded-md"
/>
</div>

<div>
<label className="block font-medium mb-1">
Contact Information (Telegram, Discord, X, etc.)
</label>
<textarea
name="contactInfo"
value={formData.contactInfo}
onChange={handleChange}
rows={2}
className="w-full p-2 border border-gray-300 rounded-md"
/>
</div>

<div>
<label className="block font-medium mb-1">
Additional Links (GitHub, Docs, Whitepaper)
</label>
<textarea
name="links"
value={formData.links}
onChange={handleChange}
rows={2}
placeholder="https://github.com/project&#10;https://docs.project.com"
className="w-full p-2 border border-gray-300 rounded-md"
/>
</div>

<div>
<label className="block font-medium mb-1">Your Name (if different from project)</label>
<input
type="text"
name="submitterName"
value={formData.submitterName}
onChange={handleChange}
className="w-full p-2 border border-gray-300 rounded-md"
/>
</div>

<div>
<label className="block font-medium mb-1">Contact Email *</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full p-2 border border-gray-300 rounded-md"
required
/>
</div>

<div className="flex items-start gap-2">
<input
type="checkbox"
name="agreed"
checked={formData.agreed}
onChange={handleChange}
className="mt-1 accent-primary-100"
required
/>
<label className="text-sm text-gray-700">
I agree to the <a href="/terms" className="text-primary-100 underline">terms and conditions</a>
</label>
</div>

<button
type="submit"
className="w-full bg-primary-100 hover:bg-primary-110 text-white font-semibold py-2 px-4 rounded-lg transition-colors"
>
Submit Request
</button>
</form>
</div>
)
}
Loading