151 lines
4.9 KiB
Vue
151 lines
4.9 KiB
Vue
<template>
|
||
<IonPage>
|
||
<!-- Splash: unauthenticated -->
|
||
<template v-if="!user">
|
||
<IonContent class="splash-content">
|
||
<div class="splash-center">
|
||
<img src="/oysqn_logo.png" alt="OYS Borrow a Boat" class="splash-logo" />
|
||
<IonButton expand="block" router-link="/login" class="splash-btn">Log In</IonButton>
|
||
</div>
|
||
</IonContent>
|
||
</template>
|
||
|
||
<!-- Home: authenticated -->
|
||
<template v-else>
|
||
<IonHeader>
|
||
<IonToolbar color="primary">
|
||
<IonButtons slot="start">
|
||
<IonMenuButton />
|
||
</IonButtons>
|
||
<IonTitle>Home</IonTitle>
|
||
</IonToolbar>
|
||
</IonHeader>
|
||
<IonContent class="ion-padding">
|
||
<h2 class="welcome">Welcome, {{ auth.displayName }}</h2>
|
||
|
||
<IonCard>
|
||
<IonCardHeader>
|
||
<IonCardTitle>Upcoming Reservations</IonCardTitle>
|
||
</IonCardHeader>
|
||
<IonCardContent>
|
||
<div v-if="loadingReservations" class="ion-text-center ion-padding">
|
||
<IonSpinner name="crescent" />
|
||
</div>
|
||
<p v-else-if="upcomingReservations.length === 0" class="empty-text">
|
||
No upcoming reservations.
|
||
</p>
|
||
<IonList v-else lines="full">
|
||
<IonItem v-for="r in upcomingReservations" :key="r.id">
|
||
<IonLabel>
|
||
<h3>{{ boatName(r) }}</h3>
|
||
<p>{{ formatDateRange(r.start_time, r.end_time) }}</p>
|
||
</IonLabel>
|
||
<IonBadge slot="end" :color="statusColor(r.status)">{{ r.status }}</IonBadge>
|
||
</IonItem>
|
||
</IonList>
|
||
</IonCardContent>
|
||
</IonCard>
|
||
|
||
<IonButton expand="block" router-link="/reservations/create" class="ion-margin-bottom">
|
||
<IonIcon slot="start" :icon="addCircleOutline" />
|
||
Create Reservation
|
||
</IonButton>
|
||
|
||
<IonCard>
|
||
<IonCardHeader>
|
||
<IonCardTitle>Calendar</IonCardTitle>
|
||
</IonCardHeader>
|
||
<IonCardContent class="calendar-content">
|
||
<DatePicker inline :min-date="today" />
|
||
</IonCardContent>
|
||
</IonCard>
|
||
|
||
<WeatherWidget />
|
||
</IonContent>
|
||
</template>
|
||
</IonPage>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {
|
||
IonPage, IonHeader, IonToolbar, IonTitle, IonContent,
|
||
IonButtons, IonMenuButton, IonButton, IonCard, IonCardHeader,
|
||
IonCardTitle, IonCardContent, IonList, IonItem, IonLabel,
|
||
IonBadge, IonSpinner, IonIcon,
|
||
} from '@ionic/vue'
|
||
import { addCircleOutline } from 'ionicons/icons'
|
||
import { useAuthStore } from '~/stores/auth'
|
||
import type { Database, ReservationStatus } from '~/types/supabase'
|
||
|
||
type ReservationWithBoat = Database['public']['Tables']['reservations']['Row'] & {
|
||
boats: { name: string; display_name: string | null } | null
|
||
}
|
||
|
||
definePageMeta({ layout: false })
|
||
|
||
const user = useSupabaseUser()
|
||
const auth = useAuthStore()
|
||
const supabase = useSupabaseClient<Database>()
|
||
|
||
const today = new Date()
|
||
const loadingReservations = ref(true)
|
||
const upcomingReservations = ref<ReservationWithBoat[]>([])
|
||
|
||
async function fetchReservations() {
|
||
if (!user.value) return
|
||
loadingReservations.value = true
|
||
const { data } = await (supabase as ReturnType<typeof useSupabaseClient>)
|
||
.from('reservations')
|
||
.select('*, boats(name, display_name)')
|
||
.eq('user_id', user.value.id)
|
||
.gte('start_time', new Date().toISOString())
|
||
.order('start_time', { ascending: true })
|
||
.limit(3)
|
||
upcomingReservations.value = (data as ReservationWithBoat[]) ?? []
|
||
loadingReservations.value = false
|
||
}
|
||
|
||
watch(user, (val) => { if (val) fetchReservations() }, { immediate: true })
|
||
|
||
function boatName(r: ReservationWithBoat) {
|
||
return r.boats?.display_name || r.boats?.name || 'Unknown boat'
|
||
}
|
||
|
||
function formatDateRange(start: string, end: string) {
|
||
const s = new Date(start)
|
||
const e = new Date(end)
|
||
const date = s.toLocaleDateString('en-CA', { weekday: 'short', month: 'short', day: 'numeric' })
|
||
const startTime = s.toLocaleTimeString('en-CA', { hour: '2-digit', minute: '2-digit' })
|
||
const endTime = e.toLocaleTimeString('en-CA', { hour: '2-digit', minute: '2-digit' })
|
||
return `${date} · ${startTime}–${endTime}`
|
||
}
|
||
|
||
function statusColor(status: ReservationStatus): string {
|
||
const colors: Record<ReservationStatus, string> = {
|
||
pending: 'warning',
|
||
tentative: 'medium',
|
||
confirmed: 'success',
|
||
}
|
||
return colors[status]
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.splash-content { --background: var(--ion-color-light); }
|
||
.splash-center {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
padding: 2rem;
|
||
gap: 2rem;
|
||
}
|
||
.splash-logo { max-width: 280px; width: 100%; }
|
||
.splash-btn { width: 100%; max-width: 280px; }
|
||
|
||
.welcome { font-size: 1.25rem; font-weight: 600; margin: 0 0 1rem; }
|
||
.empty-text { color: var(--ion-color-medium); margin: 0; }
|
||
.calendar-content { display: flex; justify-content: center; padding: 0.5rem 0; }
|
||
</style>
|