From 95b53e271f5f20ba53a025112442d91c116a6cc8 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 01:31:51 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20wallet=20UI=20=E2=80=94=20WalletConnect?= =?UTF-8?q?=20component=20+=20ranked=20fight=20flow=20in=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/components/WalletConnect.vue | 147 ++++++++++++++++++++++ frontend/src/composables/useNostr.ts | 9 ++ frontend/src/pages/BotProfilePage.vue | 21 ++++ frontend/src/pages/FightPage.vue | 13 +- frontend/src/pages/JoinBoutPage.vue | 49 ++++++++ 5 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/WalletConnect.vue diff --git a/frontend/src/components/WalletConnect.vue b/frontend/src/components/WalletConnect.vue new file mode 100644 index 0000000..2729855 --- /dev/null +++ b/frontend/src/components/WalletConnect.vue @@ -0,0 +1,147 @@ + + + diff --git a/frontend/src/composables/useNostr.ts b/frontend/src/composables/useNostr.ts index ef52b38..cd09d31 100644 --- a/frontend/src/composables/useNostr.ts +++ b/frontend/src/composables/useNostr.ts @@ -23,6 +23,9 @@ interface BotData { winStreak: number bestStreak: number tier: number + satsWon: number + satsWagered: number + hasWallet: boolean } interface NostrWindow { @@ -140,6 +143,9 @@ export function useNostr() { winStreak: 0, bestStreak: 0, tier: 0, + satsWon: 0, + satsWagered: 0, + hasWallet: false, } store('bf_bot', bot.value) @@ -201,6 +207,9 @@ export function useNostr() { winStreak: 0, bestStreak: 0, tier: 0, + satsWon: 0, + satsWagered: 0, + hasWallet: false, } store('bf_bot', bot.value) diff --git a/frontend/src/pages/BotProfilePage.vue b/frontend/src/pages/BotProfilePage.vue index e0dc295..6c58fdc 100644 --- a/frontend/src/pages/BotProfilePage.vue +++ b/frontend/src/pages/BotProfilePage.vue @@ -4,6 +4,7 @@ import { useRoute, useRouter, RouterLink } from 'vue-router' import { useNostr } from '../composables/useNostr' import SpritePreview from '../components/SpritePreview.vue' import HumanPreview from '../components/HumanPreview.vue' +import WalletConnect from '../components/WalletConnect.vue' import type { SpriteCustomization } from '../game/sprites' const route = useRoute() @@ -39,6 +40,9 @@ interface BotStats { totalFights: number rank: number totalBots: number + satsWon: number + satsWagered: number + hasWallet: boolean createdAt: string recentFights: { id: string @@ -421,6 +425,23 @@ const tierClass = (t: number) => `tier-${t}` + +
+
+

{{ stats.satsWon || 0 }}

+

SATS WON

+
+
+

{{ stats.satsWagered || 0 }}

+

WAGERED

+
+
+ + +
+ +
+
diff --git a/frontend/src/pages/JoinBoutPage.vue b/frontend/src/pages/JoinBoutPage.vue index 41b899e..237fc46 100644 --- a/frontend/src/pages/JoinBoutPage.vue +++ b/frontend/src/pages/JoinBoutPage.vue @@ -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 | 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' }}

+ + + + + +