feat: wallet UI — WalletConnect component + ranked fight flow in pages

Add WalletConnect.vue with NWC/LN Address connection states. Add "FIGHT
FOR SATS" button to JoinBoutPage with entry fee payment flow. Show ranked
pot and winner payout in FightPage. Add sats stats and wallet section to
BotProfilePage. Extend BotData interface with satsWon/satsWagered/hasWallet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 01:31:51 +00:00
co-authored by Claude Opus 4.6
parent 131d7e587e
commit 95b53e271f
5 changed files with 238 additions and 1 deletions
+49
View File
@@ -2,11 +2,14 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useNostr } from '../composables/useNostr'
import { useWallet } from '../composables/useWallet'
import SpritePreview from '../components/SpritePreview.vue'
import HumanPreview from '../components/HumanPreview.vue'
import WalletConnect from '../components/WalletConnect.vue'
const router = useRouter()
const { pubkey, bot, profilePicUrl, isLoggedIn, hasExtension, isLoading, login, registerBot, registerHuman, logout } = useNostr()
const { isWalletConnected, payEntryFee, paymentStatus } = useWallet()
// Steps: 'login' | 'choose-mode' | 'pick-character' | 'name-bot' | 'bot-setup' | 'add-webhook' |
// 'pick-human-avatar' | 'name-human' | 'human-guide' | 'ready'
@@ -15,6 +18,7 @@ const isHumanMode = ref(false)
const selectedHumanSeed = ref('baby_fighter_1')
const error = ref('')
const isJoining = ref(false)
const isJoiningRanked = ref(false)
const queueCount = ref(0)
let pollHandle: ReturnType<typeof setInterval> | null = null
@@ -243,6 +247,30 @@ async function fight() {
isJoining.value = false
}
async function fightRanked() {
if (!bot.value || isJoiningRanked.value) return
isJoiningRanked.value = true
error.value = ''
try {
const paymentId = await payEntryFee(bot.value.id)
const res = await fetch(`/api/queue/join-ranked/${bot.value.id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentId }),
})
if (res.ok) {
const data = await res.json()
router.push(`/arena/${data.fightId}`)
} else {
const data = await res.json()
error.value = data.error || 'Ranked match failed.'
}
} catch (err) {
error.value = err instanceof Error ? err.message : 'Ranked fight error.'
}
isJoiningRanked.value = false
}
function handleSignOut() {
logout()
step.value = 'login'
@@ -787,6 +815,27 @@ function handleSignOut() {
{{ (isHumanMode || bot.isHuman) ? 'Type your answers live against an AI' : 'Queue up against a real AI bot' }}
</p>
<!-- Ranked fight button -->
<button
v-if="!isHumanMode && !bot.isHuman"
class="w-full py-4 bg-neon-cyan/10 border-2 border-neon-cyan text-neon-cyan
font-display font-black text-xl tracking-[0.2em]
hover:bg-neon-cyan/20 transition-all
disabled:opacity-50 disabled:cursor-wait
flex flex-col items-center justify-center gap-1"
:disabled="!isWalletConnected || isJoiningRanked"
@click="fightRanked"
>
<span class="flex items-center gap-2">
<span v-if="isJoiningRanked" class="w-4 h-4 border-2 border-neon-cyan/30 border-t-neon-cyan rounded-full animate-spin" />
{{ isJoiningRanked ? 'PAYING...' : '⚡ FIGHT FOR SATS' }}
</span>
<span class="text-[9px] font-mono tracking-normal font-normal text-neon-cyan/70">21 SATS WINNER TAKES ALL</span>
</button>
<!-- Wallet connect (shown if no wallet) -->
<WalletConnect v-if="!isHumanMode && !bot.isHuman" />
</div>
<!-- Quick links -->