aboutsummaryrefslogtreecommitdiffstats
path: root/layouts/default.vue
diff options
context:
space:
mode:
Diffstat (limited to 'layouts/default.vue')
-rw-r--r--layouts/default.vue201
1 files changed, 201 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>