BetHistory component shows personal bet history with fight links, amounts, outcomes, and payouts. P&L summary shows total wagered, total won, and net sats. Added to BotProfilePage for the bot owner's view. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
4.4 KiB
Vue
125 lines
4.4 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
const props = defineProps<{ pubkey: string }>()
|
|
|
|
const router = useRouter()
|
|
|
|
interface Bet {
|
|
id: string
|
|
fightId: string
|
|
botId: string
|
|
amountSats: number
|
|
oddsAtPlacement: number
|
|
status: string
|
|
payoutSats: number | null
|
|
createdAt: string
|
|
settledAt: string | null
|
|
}
|
|
|
|
const bets = ref<Bet[]>([])
|
|
const isLoading = ref(true)
|
|
|
|
const totalWagered = computed(() => bets.value.reduce((sum, b) => sum + b.amountSats, 0))
|
|
const totalWon = computed(() => bets.value.reduce((sum, b) => sum + (b.payoutSats || 0), 0))
|
|
const netSats = computed(() => totalWon.value - totalWagered.value)
|
|
|
|
function statusColor(status: string): string {
|
|
if (status === 'won') return 'text-neon-green'
|
|
if (status === 'lost') return 'text-ko'
|
|
if (status === 'refunded') return 'text-neon-yellow'
|
|
return 'text-text-muted'
|
|
}
|
|
|
|
function statusLabel(status: string): string {
|
|
if (status === 'won') return 'WON'
|
|
if (status === 'lost') return 'LOST'
|
|
if (status === 'refunded') return 'REFUND'
|
|
if (status === 'locked') return 'ACTIVE'
|
|
return status.toUpperCase()
|
|
}
|
|
|
|
function timeAgo(dateStr: string): string {
|
|
const diffSec = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000)
|
|
if (diffSec < 60) return `${diffSec}s`
|
|
const diffMin = Math.floor(diffSec / 60)
|
|
if (diffMin < 60) return `${diffMin}m`
|
|
const diffHr = Math.floor(diffMin / 60)
|
|
if (diffHr < 24) return `${diffHr}h`
|
|
return `${Math.floor(diffHr / 24)}d`
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const res = await fetch(`/api/bets/history/${props.pubkey}`)
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
bets.value = data.bets || []
|
|
}
|
|
} catch { /* ignore */ }
|
|
isLoading.value = false
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<h3 class="font-display font-black text-xs tracking-widest text-neon-purple mb-2">BET HISTORY</h3>
|
|
|
|
<div v-if="isLoading" class="flex justify-center py-4">
|
|
<div class="w-6 h-6 border-2 border-neon-purple/30 border-t-neon-purple rounded-full animate-spin" />
|
|
</div>
|
|
|
|
<div v-else-if="bets.length === 0" class="py-4">
|
|
<p class="font-mono text-text-muted text-xs text-center">No bets placed yet.</p>
|
|
</div>
|
|
|
|
<template v-else>
|
|
<!-- P&L summary -->
|
|
<div class="flex gap-2 mb-3">
|
|
<div class="flex-1 px-2 py-1.5 bg-surface border border-border rounded-md text-center">
|
|
<p class="font-pixel text-[8px] text-text-muted tracking-wider">WAGERED</p>
|
|
<p class="font-mono text-xs text-text-primary">{{ totalWagered }}</p>
|
|
</div>
|
|
<div class="flex-1 px-2 py-1.5 bg-surface border border-border rounded-md text-center">
|
|
<p class="font-pixel text-[8px] text-text-muted tracking-wider">WON</p>
|
|
<p class="font-mono text-xs text-neon-green">{{ totalWon }}</p>
|
|
</div>
|
|
<div class="flex-1 px-2 py-1.5 bg-surface border border-border rounded-md text-center">
|
|
<p class="font-pixel text-[8px] text-text-muted tracking-wider">NET</p>
|
|
<p class="font-mono text-xs" :class="netSats >= 0 ? 'text-neon-green' : 'text-ko'">
|
|
{{ netSats >= 0 ? '+' : '' }}{{ netSats }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bet list -->
|
|
<div class="space-y-1.5 max-h-48 overflow-y-auto">
|
|
<button
|
|
v-for="bet in bets"
|
|
:key="bet.id"
|
|
class="w-full text-left px-2.5 py-1.5 bg-surface/50 border border-border rounded-md
|
|
hover:border-neon-cyan/20 transition-all text-xs"
|
|
@click="router.push(`/arena/${bet.fightId}`)"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-mono text-text-primary">{{ bet.amountSats }} sats</span>
|
|
<span class="font-display font-bold text-[10px] tracking-wider" :class="statusColor(bet.status)">
|
|
{{ statusLabel(bet.status) }}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center justify-between mt-0.5">
|
|
<span v-if="bet.payoutSats" class="font-mono text-[10px] text-neon-green">
|
|
+{{ bet.payoutSats }} sats
|
|
</span>
|
|
<span v-else class="font-mono text-[10px] text-text-muted">
|
|
{{ (bet.oddsAtPlacement * 0.95).toFixed(2) }}x odds
|
|
</span>
|
|
<span class="font-pixel text-[8px] text-text-muted">{{ timeAgo(bet.createdAt) }}</span>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|