diff options
| author | Leonardo Bishop <me@leonardobishop.com> | 2025-08-23 22:29:28 +0100 |
|---|---|---|
| committer | Leonardo Bishop <me@leonardobishop.com> | 2025-08-23 22:29:28 +0100 |
| commit | ecc6a55aba7bb35fc778e7a53848396b88214151 (patch) | |
| tree | 1b37a2dc5f4594155114da1ae0c4529d20a4c548 /web/components | |
| parent | 8f7dec8ba6b2f9bde01afd0a110596ebbd43e0ed (diff) | |
Add multiple conferences feature
Diffstat (limited to 'web/components')
| -rw-r--r-- | web/components/AddConference.vue | 54 | ||||
| -rw-r--r-- | web/components/Button.vue | 2 | ||||
| -rw-r--r-- | web/components/Dialog.vue | 2 | ||||
| -rw-r--r-- | web/components/EventDetail.vue | 2 | ||||
| -rw-r--r-- | web/components/EventListing.vue | 10 | ||||
| -rw-r--r-- | web/components/Input.vue | 2 | ||||
| -rw-r--r-- | web/components/Panel.vue | 15 | ||||
| -rw-r--r-- | web/components/Sidebar.vue | 45 |
8 files changed, 97 insertions, 35 deletions
diff --git a/web/components/AddConference.vue b/web/components/AddConference.vue new file mode 100644 index 0000000..95154d4 --- /dev/null +++ b/web/components/AddConference.vue @@ -0,0 +1,54 @@ +<script setup lang="ts"> +import { format } from 'date-fns'; +import { type Event as ScheduledEvent } from '~/stores/schedule'; + +const errorStore = useErrorStore(); +const config = useRuntimeConfig(); +const loading = ref(false) + +const emit = defineEmits(['update']); + +const addConference = async (e: Event) => { + const target = e.target as HTMLFormElement; + const formData = new FormData(target); + loading.value = true + + $api(config.public.baseURL + '/conference', { + method: 'POST', + body: JSON.stringify(Object.fromEntries(formData)), + onResponse: ({ response }) => { + loading.value = false + if (!response.ok) { + errorStore.setError(response._data?.message || 'An unknown error occurred'); + return + } + emit('update') + }, + }); +} + +</script> + +<template> + <div> + <form @submit.prevent="(e) => addConference(e)"> + <div class="form-group"> + <label for="url" class="form-label"> + Schedule data URL + </label> + <div class="form-input-container"> + <Input id="url" name="url" required /> + </div> + </div> + + <div class="form-submit"> + <Button type="submit" :loading="loading"> + Add + </Button> + </div> + </form> + </div> +</template> + +<style scoped> +</style>
\ No newline at end of file diff --git a/web/components/Button.vue b/web/components/Button.vue index ce9eefc..27cfa4e 100644 --- a/web/components/Button.vue +++ b/web/components/Button.vue @@ -48,7 +48,7 @@ button { gap: 0.4rem; padding: 0.5rem 1rem; border: 1px solid transparent; - border-radius: 0.375rem; + border-radius: 2px; font-size: var(--text-small); font-family: var(--font-family); font-weight: 500; diff --git a/web/components/Dialog.vue b/web/components/Dialog.vue index 7772f23..04e1461 100644 --- a/web/components/Dialog.vue +++ b/web/components/Dialog.vue @@ -72,7 +72,7 @@ const onDialogClick = (e: MouseEvent) => { <style scoped> dialog { outline: none; - border-radius: 0.5rem; + border-radius: 2px; padding: 1rem; width: 1000px; margin: 0; diff --git a/web/components/EventDetail.vue b/web/components/EventDetail.vue index b4f7bd9..8fd5b03 100644 --- a/web/components/EventDetail.vue +++ b/web/components/EventDetail.vue @@ -43,7 +43,7 @@ const getHostname = (url: string) => new URL(url).hostname; </div> <div> - <span class="event-track"><NuxtLink :to="'/tracks/' + event.track.slug">{{ event.track.name }}</NuxtLink> •</span> <a v-if="event.url" class="event-url" :href="event.url" target="_blank">view on {{ getHostname(event.url)}}</a> + <span v-if="event.track" class="event-track"><NuxtLink :to="'/tracks/' + event.track.slug">{{ event.track.name }}</NuxtLink> •</span> <a v-if="event.url" class="event-url" :href="event.url" target="_blank">view on {{ getHostname(event.url)}}</a> </div> </div> </template> diff --git a/web/components/EventListing.vue b/web/components/EventListing.vue index 5c04189..0cc546c 100644 --- a/web/components/EventListing.vue +++ b/web/components/EventListing.vue @@ -10,6 +10,7 @@ const { event, showRelativeTime } = defineProps<{ }>(); const selectedEventStore = useSelectedEventStore(); +const conferenceStore = useConferenceStore(); const favouritesStore = useFavouritesStore(); const errorStore = useErrorStore(); const config = useRuntimeConfig(); @@ -43,9 +44,10 @@ const addFavourite = async () => { addingToFavourite.value = true; try { - const res = await $fetch(config.public.baseURL + '/favourites', { + const res = await $api(config.public.baseURL + '/favourites', { method: 'POST', body: JSON.stringify({ + conferenceId: conferenceStore.id, eventGuid: event.guid, eventId: event.id, }), @@ -70,7 +72,7 @@ const removeFavourite = async () => { addingToFavourite.value = true; try { - await $fetch(config.public.baseURL + '/favourites', { + await $api(config.public.baseURL + '/favourites', { method: 'DELETE', body: JSON.stringify({ eventGuid: event.guid, @@ -102,9 +104,9 @@ const removeFavourite = async () => { </span> <span class="event-title">{{ event.title }}</span> <span class="event-speaker">{{ event.persons.map(p => p.name).join(", ") }}</span> - <span class="event-track">{{ event.track.name }}</span> + <span class="event-track">{{ event.track?.name }}</span> </div> - <template v-if="!addingToFavourite" class="event-button"> + <template v-if="!addingToFavourite && favouritesStore.status !== 'pending'" class="event-button"> <StarIcon v-if="favouritesStore.isFavourite(event)" color="var(--color-favourite)" fill="var(--color-favourite)" class="event-button" @click="removeFavourite" /> <StarIcon v-else color="var(--color-text-muted)" class="event-button" @click="addFavourite" /> </template> diff --git a/web/components/Input.vue b/web/components/Input.vue index b541566..aebece6 100644 --- a/web/components/Input.vue +++ b/web/components/Input.vue @@ -82,7 +82,7 @@ input { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; - border-radius: 0.375rem; + border-radius: 2px; box-sizing: border-box; /* box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); */ font-family: var(--font-family); diff --git a/web/components/Panel.vue b/web/components/Panel.vue index 1f2d22e..d417691 100644 --- a/web/components/Panel.vue +++ b/web/components/Panel.vue @@ -4,7 +4,7 @@ import { defineProps, type FunctionalComponent, type PropType } from 'vue'; defineProps({ kind: { - type: String as PropType<"normal" | "error" | "success" | "emphasis">, + type: String as PropType<"normal" | "top" | "error" | "success" | "emphasis">, required: false, default: 'normal', }, @@ -47,14 +47,11 @@ defineProps({ <style> div.card { padding: 1rem; - border-radius: 0.5rem; } div.card-header { padding: 1rem 1rem; margin: -1rem -1rem 0 -1rem; - border-top-left-radius: 0.5rem; - border-top-right-radius: 0.5rem;; display: flex; justify-content: space-between; align-items: center; @@ -73,7 +70,7 @@ div.header-left > svg { } span.card-title { - font-size: 1.5rem; + font-size: 1.3rem; font-weight: 700; } @@ -99,17 +96,21 @@ span.breadcrumb > a:hover { color: var(--color-primary); } -div.normal { +div.normal, div.top { background-color: white; border: 0.1rem solid var(--color-border); } -div.normal .card-header { +div.normal .card-header, div.top .card-header { background-color: var(--color-background-muted); border-bottom: 1px solid var(--color-border); margin: -1rem -1rem 1rem -1rem; } +div.top { + border-top: 3px solid var(--color-primary) +} + div.error { background-color: var(--color-error-light); border: 0.1rem solid var(--color-border-error-light); diff --git a/web/components/Sidebar.vue b/web/components/Sidebar.vue index 5fc42d3..fd64434 100644 --- a/web/components/Sidebar.vue +++ b/web/components/Sidebar.vue @@ -3,6 +3,7 @@ import { formatDistanceToNow } from "date-fns"; import { LucideClock, LucideRadio } from "lucide-vue-next"; const scheduleStore = useScheduleStore(); +const conferenceStore = useConferenceStore(); const errorStore = useErrorStore(); const timer = ref(); @@ -30,31 +31,35 @@ onBeforeUnmount(() => { <template> <div class="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 class="conference" kind="top"> + <template v-if="conferenceStore.id && conferenceStore.title"> + <span class="conference-title">{{ conferenceStore.title }}</span> + <span class="conference-venue">{{ conferenceStore.venue }}</span> + <span class="conference-city">{{ conferenceStore.city }}</span> + </template> - <Button kind="secondary" @click="errorStore.setError('This doesn\'t do anything yet :-)')">Change conference</Button> + <Button kind="secondary" @click="navigateTo('/conferences')">Change conference</Button> </Panel> - <Panel kind="success" class="ongoing" v-if="ongoing"> - <span>This conference is ongoing</span> - <Button kind="primary" :icon="LucideRadio" @click="navigateTo('/live')">View live</Button> - </Panel> + <template v-if="scheduleStore.schedule != null"> + <Panel kind="success" class="ongoing" v-if="ongoing"> + <span>This conference is ongoing</span> + <Button kind="primary" :icon="LucideRadio" @click="navigateTo('/live')">View live</Button> + </Panel> - <Panel kind="error" class="finished" v-else-if="finished"> - <span>This conference has finished</span> - </Panel> - - <Panel class="upcoming" v-else> - <span class="text-icon"><LucideClock /> <span>Starts in {{ startsIn }}</span></span> - </Panel> - - <Nav /> + <Panel kind="error" class="finished" v-else-if="finished"> + <span>This conference has finished</span> + </Panel> + + <Panel class="upcoming" v-else> + <span class="text-icon"><LucideClock /> <span>Starts in {{ startsIn }}</span></span> + </Panel> + + <Nav /> + </template> <div class="info"> - <span>Times listed are in local time ({{ scheduleStore.schedule?.conference.timeZoneName }})</span> + <span v-if="scheduleStore.schedule != null">Times listed are in local time ({{ scheduleStore.schedule?.conference.timeZoneName }})</span> <Version /> </div> @@ -74,7 +79,7 @@ onBeforeUnmount(() => { align-items: center; gap: 1rem; font-size: var(--text-small); - font-style: oblique; + font-style: italic; text-align: center; } |
