Files
oysqn.app/app/pages/index.vue
2026-04-20 07:11:56 -04:00

151 lines
4.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>