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 /pages | |
Initial commit
Diffstat (limited to 'pages')
| -rw-r--r-- | pages/agenda.vue | 57 | ||||
| -rw-r--r-- | pages/events.vue | 45 | ||||
| -rw-r--r-- | pages/index.vue | 6 | ||||
| -rw-r--r-- | pages/login.vue | 172 | ||||
| -rw-r--r-- | pages/register.vue | 172 | ||||
| -rw-r--r-- | pages/tracks/[slug].vue | 45 | ||||
| -rw-r--r-- | pages/tracks/index.vue | 49 |
7 files changed, 546 insertions, 0 deletions
diff --git a/pages/agenda.vue b/pages/agenda.vue new file mode 100644 index 0000000..3e4c1df --- /dev/null +++ b/pages/agenda.vue @@ -0,0 +1,57 @@ +<script setup lang="ts"> +import Panel from '~/components/Panel.vue'; + + +const favouritesStore = useFavouritesStore(); +const scheduleStore = useScheduleStore(); + +const favouriteEvents = computed(() => { + return scheduleStore.events.filter((event) => favouritesStore.isFavourite(event)); +}); + +console.log(favouriteEvents.value); + +</script> + +<template> + <Panel v-if="favouritesStore.status === 'pending'"> + <span>Updating favourites...</span> + </Panel> + <Panel v-else-if="favouriteEvents.length > 0"> + <h2 class="agenda-title">Agenda</h2> + <ul class="agenda-list"> + <li + v-for="event in favouriteEvents" + :key="event.id" + class="agenda-item" + > + <EventListing :event="event" /> + </li> + </ul> + </Panel> + <Panel v-else> + <span>You have not added any favourites yet.</span> + </Panel> +</template> + +<style scoped> +.agenda-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; +} + +.agenda-item { + border-bottom: 1px solid var(--color-background-muted); +} + +.agenda-title { + margin-bottom: 1rem; +} + +.agenda-item:last-child { + border-bottom: none; +} + +</style>
\ No newline at end of file diff --git a/pages/events.vue b/pages/events.vue new file mode 100644 index 0000000..33e6057 --- /dev/null +++ b/pages/events.vue @@ -0,0 +1,45 @@ +<script setup lang="ts"> +import { useScheduleStore } from '~/stores/schedule'; + +const scheduleStore = useScheduleStore(); + +</script> + +<template> + <Panel v-if="scheduleStore.schedule"> + <h2 class="events-title">Events</h2> + <div v-for="[day, events] of Object.entries(scheduleStore.eventsPerDay)" :key="day"> + <ul class="events-list"> + <li + v-for="event in events" + :key="event.id" + class="event-item" + > + <EventListing :event="event" /> + </li> + </ul> + </div> + </Panel> +</template> + +<style> +.events-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; +} + +.event-item { + border-bottom: 1px solid var(--color-background-muted); +} + +.events-title { + margin-bottom: 1rem; +} + +.event-item:last-child { + border-bottom: none; +} + +</style>
\ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue new file mode 100644 index 0000000..941a378 --- /dev/null +++ b/pages/index.vue @@ -0,0 +1,6 @@ +<script setup lang="ts"> +navigateTo('/events'); +</script> + +<template> +</template> diff --git a/pages/login.vue b/pages/login.vue new file mode 100644 index 0000000..2c38f61 --- /dev/null +++ b/pages/login.vue @@ -0,0 +1,172 @@ +<script setup lang="ts"> +import { ref } from 'vue' +import { FetchError } from 'ofetch' +import Input from '~/components/Input.vue' + +definePageMeta({ + layout: 'none' +}) + +const isLoading = ref(false) +const error = ref("") + +const config = useRuntimeConfig() +const headers = useRequestHeaders(['cookie']) + +const handleSubmit = async (e: Event) => { + const target = e.target as HTMLFormElement; + const formData = new FormData(target); + + isLoading.value = true + error.value = "" + + try { + await $fetch(config.public.baseURL + '/login', { + method: 'POST', + body: JSON.stringify(Object.fromEntries(formData)), + headers: headers, + server: false, + }); + + navigateTo("/events"); + } catch (e: any) { + if ((e as FetchError).data) { + error.value = e.data.message + } else { + error.value = "An unknown error occurred" + } + } + + isLoading.value = false +} + +</script> + +<template> + <div class="auth-container"> + <div class="auth-header"> + <h2 class="auth-title">Sign in</h2> + + <div v-if="error" class="auth-error"> + {{ error }} + </div> + </div> + + <div class="auth-body"> + <Panel> + <form class="auth-form" @submit.prevent="handleSubmit"> + <div class="form-group"> + <label for="username" class="form-label"> + Username + </label> + <div class="form-input-container"> + <Input id="username" name="username" required /> + </div> + </div> + + <div class="form-group"> + <label for="password" class="form-label"> + Password + </label> + <div class="form-input-container"> + <Input id="password" name="password" type="password" autocomplete="current-password" required /> + </div> + </div> + + + <div class="form-submit"> + <Button type="submit" :loading="isLoading"> + Sign in + </Button> + </div> + </form> + </Panel> + </div> + + <div class="form-footer"> + <NuxtLink to="/register" class="register-link"> + Register + </NuxtLink> + </div> + </div> +</template> + +<style scoped> +.auth-container { + min-height: 100vh; + background-color: var(--color-background-muted); + display: flex; + flex-direction: column; + justify-content: center; + gap: 1rem; +} + +.auth-header { + margin: 0 auto; + width: 100%; + max-width: 28rem; + display: flex; + gap: 1rem; + align-items: center; + flex-direction: column; +} + +.auth-title { + margin-top: 1.5rem; + font-size: 1.875rem; + font-weight: 800; + color: #1f2937; +} + +.auth-body { + margin-top: 2rem; + margin: 0 auto; + width: 100%; + max-width: 28rem; +} + +.auth-form { + display: grid; + gap: 1.5rem; +} + +.auth-error { + color: var(--color-error); +} + +.form-group { + display: flex; + flex-direction: column; +} + +.form-label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: #374151; +} + +.form-input-container { + margin-top: 0.25rem; +} + +.form-footer { + display: flex; + justify-content: flex-end; + margin: 0 auto; + max-width: 28rem; +} + +.register-link { + font-size: var(--text-small); + font-weight: 500; +} + +.form-submit { + display: flex; +} + +input[name="username"] { + text-transform: lowercase; +} +</style> diff --git a/pages/register.vue b/pages/register.vue new file mode 100644 index 0000000..ad9a041 --- /dev/null +++ b/pages/register.vue @@ -0,0 +1,172 @@ +<script setup lang="ts"> +import { ref } from 'vue' +import { FetchError } from 'ofetch' +import Input from '~/components/Input.vue' + +definePageMeta({ + layout: 'none' +}) + +const isLoading = ref(false) +const error = ref("") + +const config = useRuntimeConfig() +const headers = useRequestHeaders(['cookie']) + +const handleSubmit = async (e: Event) => { + const target = e.target as HTMLFormElement; + const formData = new FormData(target); + + isLoading.value = true + error.value = "" + + try { + await $fetch(config.public.baseURL + '/register', { + method: 'POST', + body: JSON.stringify(Object.fromEntries(formData)), + headers: headers, + server: false, + }); + + navigateTo("/login"); + } catch (e: any) { + if ((e as FetchError).data) { + error.value = e.data.message + } else { + error.value = "An unknown error occurred" + } + } + + isLoading.value = false +} + +</script> + +<template> + <div class="auth-container"> + <div class="auth-header"> + <h2 class="auth-title">Register</h2> + + <div v-if="error" class="auth-error"> + {{ error }} + </div> + </div> + + <div class="auth-body"> + <Panel> + <form class="auth-form" @submit.prevent="handleSubmit"> + <div class="form-group"> + <label for="username" class="form-label"> + Username + </label> + <div class="form-input-container"> + <Input id="username" name="username" required /> + </div> + </div> + + <div class="form-group"> + <label for="password" class="form-label"> + Password + </label> + <div class="form-input-container"> + <Input id="password" name="password" type="password" autocomplete="current-password" required /> + </div> + </div> + + + <div class="form-submit"> + <Button type="submit" :loading="isLoading"> + Register + </Button> + </div> + </form> + </Panel> + </div> + + <div class="form-footer"> + <NuxtLink to="/login" class="register-link"> + Sign in + </NuxtLink> + </div> + </div> +</template> + +<style scoped> +.auth-container { + min-height: 100vh; + background-color: var(--color-background-muted); + display: flex; + flex-direction: column; + justify-content: center; + gap: 1rem; +} + +.auth-header { + margin: 0 auto; + width: 100%; + max-width: 28rem; + display: flex; + gap: 1rem; + align-items: center; + flex-direction: column; +} + +.auth-title { + margin-top: 1.5rem; + font-size: 1.875rem; + font-weight: 800; + color: #1f2937; +} + +.auth-body { + margin-top: 2rem; + margin: 0 auto; + width: 100%; + max-width: 28rem; +} + +.auth-form { + display: grid; + gap: 1.5rem; +} + +.auth-error { + color: var(--color-error); +} + +.form-group { + display: flex; + flex-direction: column; +} + +.form-label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: #374151; +} + +.form-input-container { + margin-top: 0.25rem; +} + +.form-footer { + display: flex; + justify-content: flex-end; + margin: 0 auto; + max-width: 28rem; +} + +.register-link { + font-size: var(--text-small); + font-weight: 500; +} + +.form-submit { + display: flex; +} + +input[name="username"] { + text-transform: lowercase; +} +</style> diff --git a/pages/tracks/[slug].vue b/pages/tracks/[slug].vue new file mode 100644 index 0000000..38bce61 --- /dev/null +++ b/pages/tracks/[slug].vue @@ -0,0 +1,45 @@ +<script setup lang="ts"> +import { useScheduleStore } from '~/stores/schedule'; + +const route = useRoute(); +const scheduleStore = useScheduleStore(); + +const track = scheduleStore.schedule?.tracks.find((track) => track.slug === route.params.slug); +</script> + +<template> + <Panel v-if="track"> + <h2 class="events-title">{{ track.name }}</h2> + <ul class="events-list"> + <li + v-for="event in scheduleStore.eventsPerTrack[track.name]" + :key="event.id" + class="event-item" + > + <EventListing :event="event" /> + </li> + </ul> + </Panel> +</template> + +<style> +.events-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; +} + +.event-item { + border-bottom: 1px solid var(--color-background-muted); +} + +.events-title { + margin-bottom: 1rem; +} + +.event-item:last-child { + border-bottom: none; +} + +</style>
\ No newline at end of file diff --git a/pages/tracks/index.vue b/pages/tracks/index.vue new file mode 100644 index 0000000..35641ab --- /dev/null +++ b/pages/tracks/index.vue @@ -0,0 +1,49 @@ +<script setup lang="ts"> +import Panel from '~/components/Panel.vue'; + +const scheduleStore = useScheduleStore(); +</script> + +<template> + <Panel v-if="scheduleStore.schedule"> + <h2 class="tracks-title">Tracks</h2> + <ul class="tracks-list"> + <li + v-for="track in scheduleStore.schedule.tracks" + :key="track.name" + class="tracks-item" + > + <NuxtLink :to="'/tracks/' + track.slug" class="track-item"> + {{ track.name }} + </NuxtLink> + </li> + </ul> + </Panel> +</template> + +<style scoped> +.tracks-list { + list-style: none; + margin: 0.5rem 0 0 0; + padding: 0; + display: grid; +} + +.track-item { + position: relative; + border-bottom: 1px solid var(--color-background-muted); + padding: 0.5rem 1rem; + left: -1rem; + width: calc(100%); + display: block; + text-decoration: none; +} + +.track-item:last-child { + border-bottom: none; +} + +.track-item:hover { + background-color: var(--color-background-muted); +} +</style>
\ No newline at end of file |
