2026-01-14 01:27:54 +01:00

206 lines
7.2 KiB
TypeScript

import { useState } from 'react'
import { nostrService } from '@/lib/nostr'
import { useNostrAuth } from '@/hooks/useNostrAuth'
import { Button, Card, Textarea, ErrorState } from './ui'
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 (
<Card variant="default">
<p className="text-cyber-accent mb-4">{t('sponsoring.form.connectRequired')}</p>
<Button variant="success" onClick={params.onConnect}>
{t('connect.connect')}
</Button>
</Card>
)
}
function SponsoringNoAddress(): React.ReactElement {
return (
<Card variant="default">
<p className="text-cyber-accent">{t('sponsoring.form.error.noAddress')}</p>
</Card>
)
}
function SponsoringInstructions(params: { instructions: SponsoringInstructionsState; onClose: () => void; onCancel: () => void }): React.ReactElement {
return (
<div role="dialog" aria-modal="true">
<Card variant="default" className="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.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" variant="success" onClick={params.onClose}>
{t('common.close')}
</Button>
<Button type="button" variant="ghost" onClick={params.onCancel}>
{t('common.cancel')}
</Button>
</div>
</Card>
</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 (
<Card variant="default" className="space-y-4">
<form onSubmit={params.onSubmit} className="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>
<Textarea
id="sponsoring-text"
label={`${t('sponsoring.form.text.label')} (${t('common.optional')})`}
value={params.text}
onChange={(e) => params.setText(e.target.value)}
placeholder={t('sponsoring.form.text.placeholder')}
rows={3}
helperText={t('sponsoring.form.text.help')}
/>
{params.error && <ErrorState message={params.error} />}
<div className="flex gap-2">
<Button type="submit" variant="success" disabled={params.loading} loading={params.loading}>
{params.loading ? t('common.loading') : t('sponsoring.form.submit')}
</Button>
{params.onCancel && (
<Button type="button" variant="ghost" onClick={params.onCancel}>
{t('common.cancel')}
</Button>
)}
</div>
</form>
</Card>
)
}