206 lines
8.3 KiB
TypeScript
206 lines
8.3 KiB
TypeScript
import { useState } from 'react'
|
|
import { nostrService } from '@/lib/nostr'
|
|
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
|
import { t } from '@/lib/i18n'
|
|
import { sponsoringPaymentService } from '@/lib/sponsoringPayment'
|
|
import type { AuthorPresentationArticle } from '@/types/nostr'
|
|
|
|
interface SponsoringFormProps {
|
|
author: AuthorPresentationArticle
|
|
onSuccess?: () => void
|
|
onCancel?: () => void
|
|
}
|
|
|
|
export function SponsoringForm({ author, onSuccess, onCancel }: SponsoringFormProps): React.ReactElement {
|
|
const { pubkey, connect } = useNostrAuth()
|
|
const state = useSponsoringFormState()
|
|
const onSubmit = (e: React.FormEvent): void => {
|
|
e.preventDefault()
|
|
void handleSubmit({ pubkey, author, setInstructions: state.setInstructions, setError: state.setError, setLoading: state.setLoading })
|
|
}
|
|
|
|
if (!pubkey) {
|
|
return <SponsoringConnectRequired onConnect={() => { void connect() }} />
|
|
}
|
|
|
|
if (!author.mainnetAddress) {
|
|
return <SponsoringNoAddress />
|
|
}
|
|
|
|
if (state.instructions) {
|
|
return (
|
|
<SponsoringInstructions
|
|
instructions={state.instructions}
|
|
onClose={() => {
|
|
state.setInstructions(null)
|
|
onSuccess?.()
|
|
}}
|
|
onCancel={() => {
|
|
state.setInstructions(null)
|
|
onCancel?.()
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<SponsoringFormView
|
|
text={state.text}
|
|
setText={state.setText}
|
|
loading={state.loading}
|
|
error={state.error}
|
|
onSubmit={onSubmit}
|
|
{...(onCancel ? { onCancel } : {})}
|
|
/>
|
|
)
|
|
}
|
|
|
|
async function handleSubmit(params: {
|
|
pubkey: string | null
|
|
author: AuthorPresentationArticle
|
|
setInstructions: (value: SponsoringInstructionsState) => void
|
|
setError: (value: string | null) => void
|
|
setLoading: (value: boolean) => void
|
|
}): Promise<void> {
|
|
if (!params.pubkey || !params.author.mainnetAddress) {
|
|
return
|
|
}
|
|
await submitSponsoring({
|
|
pubkey: params.pubkey,
|
|
author: params.author,
|
|
setInstructions: params.setInstructions,
|
|
setError: params.setError,
|
|
setLoading: params.setLoading,
|
|
})
|
|
}
|
|
|
|
type SponsoringInstructionsState = {
|
|
authorAddress: string
|
|
platformAddress: string
|
|
authorBtc: string
|
|
platformBtc: string
|
|
}
|
|
|
|
function useSponsoringFormState(): {
|
|
text: string
|
|
setText: (value: string) => void
|
|
loading: boolean
|
|
setLoading: (value: boolean) => void
|
|
error: string | null
|
|
setError: (value: string | null) => void
|
|
instructions: SponsoringInstructionsState | null
|
|
setInstructions: (value: SponsoringInstructionsState | null) => void
|
|
} {
|
|
const [text, setText] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [instructions, setInstructions] = useState<SponsoringInstructionsState | null>(null)
|
|
return { text, setText, loading, setLoading, error, setError, instructions, setInstructions }
|
|
}
|
|
|
|
async function submitSponsoring(params: {
|
|
pubkey: string
|
|
author: AuthorPresentationArticle
|
|
setInstructions: (value: SponsoringInstructionsState) => void
|
|
setError: (value: string | null) => void
|
|
setLoading: (value: boolean) => void
|
|
}): Promise<void> {
|
|
params.setLoading(true)
|
|
params.setError(null)
|
|
try {
|
|
const privateKey = nostrService.getPrivateKey()
|
|
if (!privateKey) {
|
|
params.setError(t('sponsoring.form.error.noPrivateKey'))
|
|
return
|
|
}
|
|
const result = await sponsoringPaymentService.createSponsoringPayment({ authorPubkey: params.author.pubkey, authorMainnetAddress: params.author.mainnetAddress ?? '', amount: 0.046 })
|
|
if (!result.success) {
|
|
params.setError(result.error ?? t('sponsoring.form.error.paymentFailed'))
|
|
return
|
|
}
|
|
console.warn('Sponsoring payment info:', { authorAddress: result.authorAddress, platformAddress: result.platformAddress, authorAmount: result.split.authorSats, platformAmount: result.split.platformSats, totalAmount: result.split.totalSats })
|
|
params.setInstructions({ authorAddress: result.authorAddress, platformAddress: result.platformAddress, authorBtc: (result.split.authorSats / 100_000_000).toFixed(8), platformBtc: (result.split.platformSats / 100_000_000).toFixed(8) })
|
|
} catch (submitError) {
|
|
params.setError(submitError instanceof Error ? submitError.message : t('sponsoring.form.error.paymentFailed'))
|
|
} finally {
|
|
params.setLoading(false)
|
|
}
|
|
}
|
|
|
|
function SponsoringConnectRequired(params: { onConnect: () => void }): React.ReactElement {
|
|
return (
|
|
<div className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark">
|
|
<p className="text-cyber-accent mb-4">{t('sponsoring.form.connectRequired')}</p>
|
|
<button onClick={params.onConnect} className="px-4 py-2 bg-neon-green/20 hover:bg-neon-green/30 text-neon-green rounded-lg font-medium transition-all border border-neon-green/50">
|
|
{t('connect.connect')}
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SponsoringNoAddress(): React.ReactElement {
|
|
return (
|
|
<div className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark">
|
|
<p className="text-cyber-accent">{t('sponsoring.form.error.noAddress')}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SponsoringInstructions(params: { instructions: SponsoringInstructionsState; onClose: () => void; onCancel: () => void }): React.ReactElement {
|
|
return (
|
|
<div className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark space-y-4" role="dialog" aria-modal="true">
|
|
<h3 className="text-lg font-semibold text-neon-cyan">{t('sponsoring.form.title')}</h3>
|
|
<p className="text-sm text-cyber-accent/70">{t('sponsoring.form.instructions', { authorAddress: params.instructions.authorAddress, platformAddress: params.instructions.platformAddress, authorAmount: params.instructions.authorBtc, platformAmount: params.instructions.platformBtc })}</p>
|
|
<div className="flex gap-3">
|
|
<button type="button" onClick={params.onClose} className="px-4 py-2 bg-neon-green/20 hover:bg-neon-green/30 text-neon-green rounded-lg font-medium transition-all border border-neon-green/50">
|
|
{t('common.close')}
|
|
</button>
|
|
<button type="button" onClick={params.onCancel} className="px-4 py-2 bg-neon-cyan/10 hover:bg-neon-cyan/20 text-neon-cyan rounded-lg font-medium transition-all border border-neon-cyan/30">
|
|
{t('common.cancel')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SponsoringFormView(params: {
|
|
text: string
|
|
setText: (value: string) => void
|
|
loading: boolean
|
|
error: string | null
|
|
onSubmit: (e: React.FormEvent) => void
|
|
onCancel?: () => void
|
|
}): React.ReactElement {
|
|
return (
|
|
<form onSubmit={params.onSubmit} className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark space-y-4">
|
|
<h3 className="text-lg font-semibold text-neon-cyan">{t('sponsoring.form.title')}</h3>
|
|
<p className="text-sm text-cyber-accent/70">{t('sponsoring.form.description', { amount: '0.046' })}</p>
|
|
<div>
|
|
<label htmlFor="sponsoring-text" className="block text-sm font-medium text-cyber-accent mb-1">
|
|
{t('sponsoring.form.text.label')} <span className="text-cyber-accent/50">({t('common.optional')})</span>
|
|
</label>
|
|
<textarea
|
|
id="sponsoring-text"
|
|
value={params.text}
|
|
onChange={(e) => params.setText(e.target.value)}
|
|
placeholder={t('sponsoring.form.text.placeholder')}
|
|
rows={3}
|
|
className="w-full px-3 py-2 bg-cyber-darker border border-neon-cyan/30 rounded text-cyber-accent focus:border-neon-cyan focus:outline-none"
|
|
/>
|
|
<p className="text-xs text-cyber-accent/70 mt-1">{t('sponsoring.form.text.help')}</p>
|
|
</div>
|
|
{params.error && <div className="p-3 bg-red-900/20 border border-red-500/50 rounded text-red-400 text-sm">{params.error}</div>}
|
|
<div className="flex gap-2">
|
|
<button type="submit" disabled={params.loading} className="px-4 py-2 bg-neon-green/20 hover:bg-neon-green/30 text-neon-green rounded-lg font-medium transition-all border border-neon-green/50 hover:shadow-glow-green disabled:opacity-50">
|
|
{params.loading ? t('common.loading') : t('sponsoring.form.submit')}
|
|
</button>
|
|
{params.onCancel && (
|
|
<button type="button" onClick={params.onCancel} className="px-4 py-2 bg-cyber-darker hover:bg-cyber-dark text-cyber-accent rounded-lg font-medium transition-all border border-neon-cyan/30">
|
|
{t('common.cancel')}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</form>
|
|
)
|
|
}
|