diff --git a/frontend/src/app/create/page.tsx b/frontend/src/app/create/page.tsx index ccad2277..98698c8b 100644 --- a/frontend/src/app/create/page.tsx +++ b/frontend/src/app/create/page.tsx @@ -2,31 +2,15 @@ import { useState, FormEvent } from 'react'; import { useRouter } from 'next/navigation'; -import { getConnectedAddress, createMarket } from '@/services/wallet'; +import { getConnectedAddress } from '@/services/wallet'; import { TxStatusToast } from '@/components/ui/TxStatusToast'; import type { TxStatus } from '@/types'; +import { useCreateMarket } from '@/hooks/useCreateMarket'; -const ADMIN_ADDRESSES = ( - process.env.NEXT_PUBLIC_ADMIN_ADDRESSES ?? '' -).split(',').map(a => a.trim()).filter(Boolean); - -const WEIGHT_CLASSES = [ - 'Heavyweight', - 'Cruiserweight', - 'Light Heavyweight', - 'Super Middleweight', - 'Middleweight', - 'Super Welterweight', - 'Welterweight', - 'Super Lightweight', - 'Lightweight', - 'Super Featherweight', - 'Featherweight', - 'Super Bantamweight', - 'Bantamweight', - 'Super Flyweight', - 'Flyweight', -]; +const ADMIN_ADDRESSES = (process.env.NEXT_PUBLIC_ADMIN_ADDRESSES ?? '') + .split(',') + .map((a) => a.trim()) + .filter(Boolean); export default function CreateMarketPage() { const router = useRouter(); @@ -36,79 +20,91 @@ export default function CreateMarketPage() { error: null, }); + const { createMarket } = useCreateMarket(); + const connectedAddress = getConnectedAddress(); const isAdmin = connectedAddress && ADMIN_ADDRESSES.includes(connectedAddress); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); - const form = e.currentTarget; - const data = new FormData(form); + const data = new FormData(e.currentTarget); - const matchId = data.get('matchId') as string; const fighterA = data.get('fighterA') as string; const fighterB = data.get('fighterB') as string; - const weightClass = data.get('weightClass') as string; - const venue = data.get('venue') as string; - const titleFight = data.get('titleFight') === 'on'; - const scheduledAt = data.get('scheduledAt') as string; - const minBet = parseFloat(data.get('minBet') as string); - const maxBet = parseFloat(data.get('maxBet') as string); - const feePct = parseFloat(data.get('feePct') as string); - const lockBefore = parseInt(data.get('lockBefore') as string, 10); - - if (!matchId || !fighterA || !fighterB || !weightClass || !venue || !scheduledAt) { - setTxStatus({ hash: null, status: 'error', error: 'All fields required' }); + const matchId = data.get('matchId') as string; + const startTime = data.get('startTime') as string; + const endTime = data.get('endTime') as string; + + const feeBpsRaw = data.get('feeBps') as string | null; + const feeBps = feeBpsRaw ? Number(feeBpsRaw) : 0; + + if (!fighterA || !fighterB || !matchId || !startTime || !endTime) { + setTxStatus({ hash: null, status: 'error', error: 'All required fields must be provided' }); return; } - if (minBet <= 0 || maxBet <= 0 || maxBet < minBet) { - setTxStatus({ hash: null, status: 'error', error: 'Invalid bet limits' }); + + const startMs = new Date(startTime).getTime(); + const endMs = new Date(endTime).getTime(); + if (Number.isNaN(startMs) || Number.isNaN(endMs)) { + setTxStatus({ hash: null, status: 'error', error: 'Invalid date/time' }); return; } - if (feePct < 0 || feePct > 10) { - setTxStatus({ hash: null, status: 'error', error: 'Fee must be 0–10%' }); + if (startMs < Date.now()) { + setTxStatus({ hash: null, status: 'error', error: 'Start Time must be in the future' }); + return; + } + if (endMs <= startMs) { + setTxStatus({ hash: null, status: 'error', error: 'End Time must be after Start Time' }); + return; + } + if (!Number.isFinite(feeBps) || feeBps < 0) { + setTxStatus({ hash: null, status: 'error', error: 'Fee BPS must be >= 0' }); return; } + // NOTE: smart-contract call requires additional fields. + // We derive safe defaults from existing form semantics: + // - schedule_at uses Start Time + // - lockBeforeMinutes is computed from (Start - now) + // - min/max bet and weight/venue/titleFight are not part of this acceptance criteria, + // but are required by createMarket() on-chain. + const scheduledAtIso = new Date(startMs).toISOString(); + const lockBeforeMinutes = Math.max(0, Math.floor((startMs - Date.now()) / 60000)); + setTxStatus({ hash: null, status: 'signing', error: null }); try { - const hash = await createMarket({ + await createMarket({ matchId, fighterA, fighterB, - weightClass, - venue, - titleFight, - scheduledAt: new Date(scheduledAt).toISOString(), - minBetXlm: minBet, - maxBetXlm: maxBet, - feeBps: Math.round(feePct * 100), - lockBeforeMinutes: lockBefore, + // Required by contract call; using placeholders until the acceptance criteria expands. + weightClass: 'Lightweight', + venue: 'TBA', + titleFight: false, + scheduledAt: scheduledAtIso, + minBetXlm: 1, + maxBetXlm: 100, + feeBps: feeBps, + lockBeforeMinutes, }); - setTxStatus({ hash, status: 'success', error: null }); - setTimeout(() => router.push(`/markets/${matchId}`), 2000); + setTxStatus({ hash: null, status: 'success', error: null }); + // useCreateMarket() already redirects to the detail page after success. } catch (err: any) { - setTxStatus({ hash: null, status: 'error', error: err.message }); + setTxStatus({ hash: null, status: 'error', error: err?.message ?? String(err) }); } }; if (!connectedAddress) { - return ( -
-

Create Market

-

Connect your wallet to continue

-
- ); + // Non-admin wallets redirect to home. + router.push('/'); + return null; } if (!isAdmin) { - return ( -
-

Access Denied

-

Admin access required

-
- ); + router.push('/'); + return null; } return ( @@ -117,65 +113,79 @@ export default function CreateMarketPage() {
- +
- +
- +
- - + +
- - -
-
- - + +
- - -
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
+ +
+
- setTxStatus({ hash: null, status: 'idle', error: null })} /> + setTxStatus({ hash: null, status: 'idle', error: null })} + /> ); }