aboutsummaryrefslogtreecommitdiffstats
path: root/pages
diff options
context:
space:
mode:
authorLeonardo Bishop <me@leonardobishop.com>2025-01-17 13:42:21 +0000
committerLeonardo Bishop <me@leonardobishop.com>2025-01-17 13:42:21 +0000
commit70ebc77f843207a1d4b46c8d960dafbff37e7e2e (patch)
tree2d03f7a66b877bb6ffa2f92c0504ac90f26db55f /pages
Initial commit
Diffstat (limited to 'pages')
-rw-r--r--pages/agenda.vue57
-rw-r--r--pages/events.vue45
-rw-r--r--pages/index.vue6
-rw-r--r--pages/login.vue172
-rw-r--r--pages/register.vue172
-rw-r--r--pages/tracks/[slug].vue45
-rw-r--r--pages/tracks/index.vue49
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