First crack at the app. Still lots of bugs to squash.
This commit is contained in:
93
src/routes/hunter/HuntList.svelte
Normal file
93
src/routes/hunter/HuntList.svelte
Normal file
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import {push} from 'svelte-spa-router'
|
||||
import {auth} from '../../lib/stores/auth.svelte'
|
||||
import {apiGetOngoingHunts, apiGetUnstartedHunts} from '../../lib/api/index'
|
||||
import type {HuntResponse} from '../../lib/api/types'
|
||||
import StatusBadge from '../../lib/components/StatusBadge.svelte'
|
||||
import LoadingSpinner from '../../lib/components/LoadingSpinner.svelte'
|
||||
|
||||
$effect(() => {
|
||||
if (!auth.isLoggedIn) push('/login')
|
||||
else if (auth.isAdmin) push('/admin')
|
||||
})
|
||||
|
||||
let ongoing = $state<HuntResponse[]>([])
|
||||
let upcoming = $state<HuntResponse[]>([])
|
||||
let loading = $state(true)
|
||||
let error = $state('')
|
||||
let tab = $state<'active' | 'upcoming'>('active')
|
||||
|
||||
$effect(() => {
|
||||
Promise.all([
|
||||
apiGetOngoingHunts(),
|
||||
apiGetUnstartedHunts(),
|
||||
]).then(([o, u]) => {
|
||||
ongoing = o
|
||||
upcoming = u
|
||||
}).catch(e => {
|
||||
error = e instanceof Error ? e.message : 'Failed to load hunts'
|
||||
}).finally(() => {
|
||||
loading = false
|
||||
})
|
||||
})
|
||||
|
||||
function huntStatus(hunt: HuntResponse): 'ONGOING' | 'UNSTARTED' | 'CLOSED' {
|
||||
if (hunt.isTerminated) return 'CLOSED'
|
||||
const now = Date.now()
|
||||
const start = new Date(hunt.startDateTime).getTime()
|
||||
const end = new Date(hunt.endDateTime).getTime()
|
||||
if (now < start) return 'UNSTARTED'
|
||||
if (now > end) return 'CLOSED'
|
||||
return 'ONGOING'
|
||||
}
|
||||
|
||||
function formatDate(dt: string) {
|
||||
return new Date(dt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="p-4 pb-20">
|
||||
<div class="tabs tabs-boxed bg-base-200 mb-4">
|
||||
<button class="tab flex-1" class:tab-active={tab === 'active'} onclick={() => tab = 'active'}>
|
||||
Active
|
||||
{#if ongoing.length > 0}<span class="badge badge-primary badge-sm ml-1">{ongoing.length}</span>{/if}
|
||||
</button>
|
||||
<button class="tab flex-1" class:tab-active={tab === 'upcoming'} onclick={() => tab = 'upcoming'}>
|
||||
Upcoming
|
||||
{#if upcoming.length > 0}<span class="badge badge-info badge-sm ml-1">{upcoming.length}</span>{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<LoadingSpinner />
|
||||
{:else if error}
|
||||
<div class="alert alert-error">{error}</div>
|
||||
{:else}
|
||||
{@const hunts = tab === 'active' ? ongoing : upcoming}
|
||||
{#if hunts.length === 0}
|
||||
<div class="text-center py-16 text-base-content/50">
|
||||
<p class="text-4xl mb-3">🔍</p>
|
||||
<p class="font-medium">{tab === 'active' ? 'No active hunts right now' : 'No upcoming hunts'}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-3">
|
||||
{#each hunts as hunt}
|
||||
<button
|
||||
class="card bg-base-100 shadow-sm border border-base-200 text-left w-full hover:border-primary hover:shadow-md transition-all"
|
||||
onclick={() => push(`/hunt/${hunt.id}`)}
|
||||
>
|
||||
<div class="card-body p-4 gap-2">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<h2 class="card-title text-base">{hunt.title}</h2>
|
||||
<StatusBadge status={huntStatus(hunt)} />
|
||||
</div>
|
||||
<p class="text-xs text-base-content/50">
|
||||
{formatDate(hunt.startDateTime)} – {formatDate(hunt.endDateTime)}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user