Add frontend for market settlement

This commit is contained in:
ekzyis 2023-12-03 08:51:40 +01:00
parent c9bce83891
commit 51e204b64a
7 changed files with 110 additions and 4 deletions

View File

@ -28,7 +28,7 @@ func (db *DB) CreateMarket(tx *sql.Tx, ctx context.Context, market *Market) erro
}
func (db *DB) FetchMarket(marketId int, market *Market) error {
if err := db.QueryRow("SELECT id, description, end_date FROM markets WHERE id = $1", marketId).Scan(&market.Id, &market.Description, &market.EndDate); err != nil {
if err := db.QueryRow("SELECT id, description, end_date, pubkey FROM markets WHERE id = $1", marketId).Scan(&market.Id, &market.Description, &market.EndDate, &market.Pubkey); err != nil {
return err
}
return nil

View File

@ -29,7 +29,7 @@ type (
Id Serial `json:"id"`
Description string `json:"description"`
EndDate time.Time `json:"endDate"`
Pubkey string
Pubkey string `json:"pubkey"`
InvoiceId UUID
}
Share struct {

View File

@ -44,6 +44,7 @@ func HandleMarket(sc context.ServerContext) echo.HandlerFunc {
}
data = map[string]any{
"Id": market.Id,
"Pubkey": market.Pubkey,
"Description": market.Description,
"Shares": shares,
}

View File

@ -15,27 +15,32 @@
<StyledLink :to="'/market/' + marketId + '/form'">form</StyledLink>
<StyledLink :to="'/market/' + marketId + '/orders'">orders</StyledLink>
<StyledLink :to="'/market/' + marketId + '/stats'">stats</StyledLink>
<StyledLink v-if="mine" :to="'/market/' + marketId + '/settings'"><i>settings</i></StyledLink>
</nav>
</header>
<Suspense>
<router-view />
<router-view :market="market" />
</Suspense>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { useSession } from '@/stores/session'
import StyledLink from '@/components/StyledLink'
const session = useSession()
const route = useRoute()
const marketId = route.params.id
const market = ref(null)
const mine = ref(false)
const url = '/api/market/' + marketId
await fetch(url)
.then(r => r.json())
.then(body => {
market.value = body
mine.value = market.value.Pubkey === session.pubkey
})
.catch(console.error)

View File

@ -0,0 +1,95 @@
<template>
<div id="container2" class="mt-3 mx-3">
<div class="grid grid-cols-2 my-3 items-center">
<label>market settlement</label>
<div class="grid grid-cols-2 my-3">
<button :class="yesClass" class="label success font-mono mx-1" @click.prevent="() => click('YES')">YES</button>
<button :class="noClass" class="label error font-mono mx-1" @click.prevent="() => click('NO')">NO</button>
</div>
<div class="col-span-2 mb-3" v-if="selected">
<p><b>Are you sure you want to settle this market?</b></p>
<p>
This will cancel all pending orders and halt trading indefinitely.
Users with winning shares will receive 100 sats per winning share. Users with losing shares receive nothing.
</p>
<p class="red"><b>You cannot undo this action.</b></p>
</div>
<button class="col-span-2" v-if="selected" @click.prevent="confirm">confirm</button>
</div>
<div v-if="err" class="red text-center">{{ err }}</div>
<div v-if="success" class="green text-center">{{ success }}</div>
</div>
</template>
<script setup>
import { computed, defineProps, ref } from 'vue'
const props = defineProps(['market'])
const market = ref(props.market)
const err = ref(null)
const success = ref(null)
const selected = ref(null)
const yesClass = computed(() => selected.value === 'YES' ? ['active'] : [])
const noClass = computed(() => selected.value === 'NO' ? ['active'] : [])
const click = (sel) => {
selected.value = selected.value === sel ? null : sel
}
const confirm = async () => {
const sid = market.value.Shares.find(s => s.Description === selected.value).Id
const url = '/api/market/' + market.value.Id + '/settle'
const body = JSON.stringify({ sid })
try {
await fetch(url, { method: 'POST', headers: { 'Content-type': 'application/json' }, body })
} catch (err) {
console.error(err)
}
}
</script>
<style scoped>
textarea {
padding: 0 0.25em;
}
.label {
width: auto;
padding: 0.2em 1em
}
.success.active {
background-color: #35df8d;
color: white;
}
.error.active {
background-color: #ff7386;
color: white;
}
#container2 {
max-width: 33vw;
}
@media only screen and (max-width: 1024px) {
#container2 {
max-width: 80vw;
}
}
@media only screen and (max-width: 768px) {
#container2 {
max-width: 90vw;
}
}
@media only screen and (max-width: 640px) {
#container2 {
max-width: 100vw;
}
}
</style>

View File

@ -21,6 +21,9 @@ button:disabled {
input {
color: #000;
}
textarea {
color: #000;
}
a {
color: #8787a4;

View File

@ -17,6 +17,7 @@ import UserOrders from '@/components/UserOrders'
import OrderForm from '@/components/OrderForm'
import MarketOrders from '@/components/MarketOrders'
import MarketStats from '@/components/MarketStats'
import MarketSettings from '@/components/MarketSettings'
const routes = [
{
@ -43,7 +44,8 @@ const routes = [
children: [
{ path: 'form', name: 'form', component: OrderForm },
{ path: 'orders', name: 'market-orders', component: MarketOrders },
{ path: 'stats', name: 'market-stats', component: MarketStats }
{ path: 'stats', name: 'market-stats', component: MarketStats },
{ path: 'settings', name: 'market-settings', component: MarketSettings }
]
},
{