diff options
| author | Leonardo Bishop <me@leonardobishop.com> | 2025-01-17 13:42:21 +0000 |
|---|---|---|
| committer | Leonardo Bishop <me@leonardobishop.com> | 2025-01-17 13:42:21 +0000 |
| commit | 70ebc77f843207a1d4b46c8d960dafbff37e7e2e (patch) | |
| tree | 2d03f7a66b877bb6ffa2f92c0504ac90f26db55f /layouts | |
Initial commit
Diffstat (limited to 'layouts')
| -rw-r--r-- | layouts/default.vue | 201 | ||||
| -rw-r--r-- | layouts/none.vue | 6 |
2 files changed, 207 insertions, 0 deletions
diff --git a/layouts/default.vue b/layouts/default.vue new file mode 100644 index 0000000..f6d1af3 --- /dev/null +++ b/layouts/default.vue @@ -0,0 +1,201 @@ +<script setup lang="ts"> +import Dialog from "~/components/Dialog.vue"; +import EventDetail from "~/components/EventDetail.vue"; + +definePageMeta({ + middleware: ["logged-in"] +}) + +const scheduleStore = useScheduleStore(); +const config = useRuntimeConfig() +const selectedEventStore = useSelectedEventStore(); +const favouritesStore = useFavouritesStore(); +const errorStore = useErrorStore(); +const router = useRouter(); + +const { selectedEvent } = storeToRefs(selectedEventStore); +const { error } = storeToRefs(errorStore); + +const refSelectedDialog = ref<typeof Dialog>(); +const refErrorDialog = ref<typeof Dialog>(); + +useFetch(config.public.baseURL + '/schedule', { + method: 'GET', + server: false, + lazy: true, + onResponse: ({ response }) => { + if (!response.ok) { + if (response.status === 401) { + navigateTo('/login'); + } else { + errorStore.setError(response._data.message || 'An unknown error occurred'); + } + } + + if (response._data) { + scheduleStore.setSchedule((response._data as any).data.schedule); + } + }, +}); + +favouritesStore.setStatus('pending') + +await useFetch(config.public.baseURL + '/favourites', { + method: 'GET', + server: false, + lazy: true, + onResponseError: ({ response }) => { + favouritesStore.setStatus('idle') + errorStore.setError(response._data.message || 'An unknown error occurred'); + }, + onResponse: ({ response }) => { + if (response._data) { + favouritesStore.setFavourites((response._data as any).data); + } + favouritesStore.setStatus('idle') + }, +}); + + +watch(selectedEvent, () => { + if (selectedEvent.value != null) { + refSelectedDialog.value?.show(); + } +}); + +watch(error, () => { + if (error.value != null) { + refErrorDialog.value?.show(); + } +}); + +router.beforeEach((to, from) => { + refSelectedDialog.value?.close(); + refErrorDialog.value?.close(); +}); +</script> + +<template> + <div class="planner-container"> + <header class="planner-header"> + <h1 class="planner-title">Conference Planner</h1> + </header> + <div class="planner-layout"> + <template v-if="scheduleStore.schedule"> + <aside class="planner-sidebar"> + <Panel class="conference"> + <span class="conference-title">{{ scheduleStore.schedule?.conference.title }}</span> + <span class="conference-venue">{{ scheduleStore.schedule?.conference.venue }}</span> + <span class="conference-city">{{ scheduleStore.schedule?.conference.city }}</span> + </Panel> + + <Nav /> + + <div class="info"> + <span>Times listed are in local time ({{ scheduleStore.schedule?.conference.timeZoneName }})</span> + </div> + </aside> + + <main class="planner-content"> + <slot /> + </main> + </template> + <template v-else> + <div class="loading"> + <span class="loading-text"> + <Spinner color="var(--color-text-muted)" />Updating schedule... + </span> + </div> + </template> + </div> + </div> + + <Dialog ref="refSelectedDialog" @close="selectedEventStore.clearSelectedEvent" kind="normal"> + <template v-if="selectedEvent"> + <EventDetail :event="selectedEvent" /> + </template> + </Dialog> + + <Dialog ref="refErrorDialog" @close="errorStore.clearError" background-color="var(--color-background-error)" kind="error" :fit-contents="true"> + <template v-if="error"> + <span>{{ error }}</span> + </template> + </Dialog> +</template> + +<style> +.planner-container { + min-height: 100vh; + background-color: var(--color-background); + overflow-x: hidden; +} + +.planner-header { + background-color: var(--color-primary); + color: white; + padding: 1rem; +} + +.planner-title { + font-size: 1.5rem; + font-weight: 700; +} + +.planner-content { + max-width: 1000px; + width: 100%; +} + +.planner-layout { + display: flex; + flex-direction: row; + gap: 1rem; + padding: 1rem; + width: 100%; + justify-content: center; +} + +.planner-sidebar { + width: 100%; + max-width: 300px; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.conference { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.conference-title { + font-weight: 600; + font-size: var(--text-normal); +} + +.conference-venue, .conference-city { + font-size: var(--text-small); + color: var(--color-text-muted); +} + +.info { + font-size: var(--text-smaller); + color: var(--color-text-muted); + margin: 0 1rem; +} + +.loading-text { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + font-size: var(--text-normal); + color: var(--color-text-muted); +} + +.loading { + margin-top: 1rem; +} + +</style> diff --git a/layouts/none.vue b/layouts/none.vue new file mode 100644 index 0000000..c018025 --- /dev/null +++ b/layouts/none.vue @@ -0,0 +1,6 @@ +<template> + <slot /> +</template> + +<script setup> +</script>
\ No newline at end of file |
