feat(market): resolve for admin

This commit is contained in:
Loïc Gremaud 2026-05-23 20:33:21 +02:00
parent a264336bd8
commit 79e7cef3ba
Signed by: Legrems
GPG Key ID: D4620E6DF3E0121D

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
import { polylanSubmitterApiGetUserInfo, marketApiCreateBet, marketApiListUserBets } from "../api"; import { polylanSubmitterApiGetUserInfo, marketApiCreateBet, marketApiListUserBets, marketApiCloseMarket, marketApiResolveMarket } from "../api";
import type { Market, MarketOption } from "../types"; import type { Market, MarketOption } from "../types";
import type { UserInfoOut, UserBetSchema } from "../api/types.gen"; import type { UserInfoOut, UserBetSchema } from "../api/types.gen";
@ -19,6 +19,9 @@ const error = ref<string>("");
const userInfo = ref<UserInfoOut | undefined>(); const userInfo = ref<UserInfoOut | undefined>();
const userBets = ref<UserBetSchema[]>([]); const userBets = ref<UserBetSchema[]>([]);
const existingBet = ref<UserBetSchema | null>(null); const existingBet = ref<UserBetSchema | null>(null);
const showResolveModal = ref(false);
const selectedWinningOption = ref<string | null>(null);
const resolveLoading = ref(false);
const timeRemaining = computed(() => { const timeRemaining = computed(() => {
const endDate = new Date(props.market.end_date).getTime(); const endDate = new Date(props.market.end_date).getTime();
@ -68,6 +71,53 @@ const getPotentialGain = (option: MarketOption) => {
return Math.round(existingBet.value.amount * multiplier); return Math.round(existingBet.value.amount * multiplier);
}; };
const closeMarket = async () => {
loading.value = true;
error.value = "";
try {
await marketApiCloseMarket({
path: { market_uuid: props.market.uuid },
});
emit("refresh");
} catch (e) {
error.value = "Error closing market";
} finally {
loading.value = false;
}
};
const resolveMarket = async () => {
if (!selectedWinningOption.value) {
error.value = "Please select a winning option";
return;
}
resolveLoading.value = true;
error.value = "";
try {
await marketApiResolveMarket({
path: { market_uuid: props.market.uuid },
body: { winning_option_uuid: selectedWinningOption.value },
});
emit("refresh");
showResolveModal.value = false;
selectedWinningOption.value = null;
} catch (e) {
const err = e as any;
if (typeof err === 'object' && err?.detail) {
error.value = err.detail;
} else if (typeof err === 'string') {
error.value = err;
} else {
error.value = "Error resolving market";
}
} finally {
resolveLoading.value = false;
}
};
const placeBet = async () => { const placeBet = async () => {
if (!selectedOption.value || !betAmount.value) return; if (!selectedOption.value || !betAmount.value) return;
@ -147,7 +197,7 @@ onMounted(async () => {
<div :class="['badge', statusColor, 'text-white']"> <div :class="['badge', statusColor, 'text-white']">
{{ market.status }} {{ market.status }}
</div> </div>
<div class="text-sm text-base-content/60 text-right"> <div v-if="market.status === 'open'" class="text-sm text-base-content/60 text-right">
<div>{{ timeRemaining }}</div> <div>{{ timeRemaining }}</div>
<div class="text-xs">until close</div> <div class="text-xs">until close</div>
</div> </div>
@ -157,6 +207,51 @@ onMounted(async () => {
<div class="divider my-0"></div> <div class="divider my-0"></div>
<!-- Admin Actions -->
<div v-if="userInfo?.is_superuser" class="card-body py-4 bg-base-100">
<div class="flex gap-2">
<button v-if="market.status === 'open'" @click="closeMarket" :disabled="loading" class="btn btn-sm btn-warning">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<span v-else>Close Market</span>
</button>
<button v-if="market.status === 'closed'" @click="showResolveModal = true" class="btn btn-sm btn-success">
Resolve Market
</button>
</div>
</div>
<!-- Resolve Modal -->
<dialog v-if="showResolveModal" class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Resolve Market - Select Winner</h3>
<div class="space-y-2 mb-4">
<div v-for="option in market.options" :key="option.uuid" class="form-control">
<label class="label cursor-pointer border rounded-lg p-3 hover:bg-base-200 transition w-full flex justify-between">
<span class="label-text font-medium">{{ option.text }}</span>
<input type="radio" :value="option.uuid" v-model="selectedWinningOption" class="radio radio-primary" />
</label>
</div>
</div>
<div v-if="error" class="alert alert-error mb-4">
<i class="mdi mdi-alert-circle"></i>
<span>{{ error }}</span>
</div>
<div class="modal-action">
<button @click="showResolveModal = false" class="btn" :disabled="resolveLoading">
Cancel
</button>
<button @click="resolveMarket" :disabled="!selectedWinningOption || resolveLoading" class="btn btn-primary">
<span v-if="resolveLoading" class="loading loading-spinner loading-sm"></span>
<span v-else>Resolve & Distribute Points</span>
</button>
</div>
</div>
<div class="modal-backdrop" @click="showResolveModal = false"></div>
</dialog>
<!-- Options --> <!-- Options -->
<div class="card-body py-4"> <div class="card-body py-4">
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">