diff options
| -rw-r--r-- | app.vue | 4 | ||||
| -rw-r--r-- | assets/css/main.css | 11 | ||||
| -rw-r--r-- | components/Button.vue | 71 | ||||
| -rw-r--r-- | components/Nav.vue | 4 | ||||
| -rw-r--r-- | components/Panel.vue | 29 | ||||
| -rw-r--r-- | components/Sidebar.vue | 43 | ||||
| -rw-r--r-- | layouts/default.vue | 12 | ||||
| -rw-r--r-- | pages/agenda.vue | 2 | ||||
| -rw-r--r-- | pages/live.vue | 13 | ||||
| -rw-r--r-- | stores/error.ts | 6 | ||||
| -rw-r--r-- | stores/schedule.ts | 33 |
11 files changed, 173 insertions, 55 deletions
@@ -3,9 +3,9 @@ </script> <template> + <NuxtPwaManifest /> + <NuxtLayout> <NuxtPage /> </NuxtLayout> - - <NuxtPwaManifest /> </template> diff --git a/assets/css/main.css b/assets/css/main.css index 88b8e3e..1ece5c0 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -5,7 +5,8 @@ --color-accent: #6366f1; --color-primary: #4f46e5; --color-primary-dark: #4338ca; - --color-error: #ff4136; + --color-error: #f8d7da; + --color-success: #d1e7dd; --color-text: #000; --color-text-muted: #4b5563; --color-background-muted: #f3f4f6; @@ -13,6 +14,8 @@ --color-background-error: #cf2f27; --color-background-error-dark: #c12820; --color-border: #d1d9e0; + --color-border-error: #f1aeb5; + --color-border-success: #a3cfbb; --color-favourite: #f6e05e; --text-smaller: 0.75rem; @@ -77,4 +80,10 @@ ul { list-style-position: inside; margin: 0.5rem 0 1rem 1rem; line-height: 1.3; +} + +span.text-icon { + display: flex; + align-items: centerg; + gap: 0.4em; }
\ No newline at end of file diff --git a/components/Button.vue b/components/Button.vue index 00cc1e7..97ff928 100644 --- a/components/Button.vue +++ b/components/Button.vue @@ -1,9 +1,33 @@ -<script setup> +<script setup lang="ts"> import { Loader2Icon } from 'lucide-vue-next' +import { defineProps } from 'vue'; + +defineProps({ + isLoading: { + type: Boolean, + default: false, + }, + disabled: { + type: Boolean, + default: false + }, + loading: { + type: Boolean, + default: false + }, + type: { + type: String as PropType<"button" | "submit" | "reset">, + default: "", + }, + kind: { + type: String as PropType<"primary" | "secondary">, + default: "primary", + }, +}); </script> <template> - <button :type="type" :disabled="disabled || loading"> + <button :type="type" :disabled="disabled || loading" :class="kind"> <Loader2Icon v-if="loading" class="icon-loader" /> <span> <slot /> @@ -11,30 +35,6 @@ import { Loader2Icon } from 'lucide-vue-next' </button> </template> -<script> -export default { - name: "Button", - props: { - isLoading: { - type: Boolean, - default: false, - }, - disabled: { - type: Boolean, - default: false - }, - loading: { - type: Boolean, - default: false - }, - type: { - type: String, - default: "", - }, - }, -}; -</script> - <style scoped> button { width: 100%; @@ -43,12 +43,10 @@ button { padding: 0.5rem 1rem; border: 1px solid transparent; border-radius: 0.375rem; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); font-size: var(--text-small); font-family: var(--font-family); font-weight: 500; color: white; - background-color: var(--color-primary); cursor: pointer; transition: background-color 0.2s ease; } @@ -60,7 +58,6 @@ button:hover { button:focus { outline: none; border-color: var(--color-accent); - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.4); } button:disabled { @@ -76,6 +73,22 @@ button:disabled { width: 1.25rem; color: white; } + +button.primary { + background-color: var(--color-primary); +} + +button.secondary { + background-color: var(--color-background); + border: 1px solid var(--color-primary); + color: var(--color-primary); +} + +button.secondary:hover { + background-color: var(--color-background-muted); + border: 1px solid var(--color-primary-dark); + color: var(--color-primary-dark); +} @keyframes spin { 0% { diff --git a/components/Nav.vue b/components/Nav.vue index 6ab4c9f..06a0295 100644 --- a/components/Nav.vue +++ b/components/Nav.vue @@ -48,7 +48,7 @@ route.afterEach(() => { display: flex; align-items: center; gap: 0.5rem; - color: var(--color-text-muted); + color: var(--color-accent); padding: 0.4rem 1rem; left: -1rem; width: calc(100%); @@ -56,7 +56,7 @@ route.afterEach(() => { } .nav-list > li.active > a { - color: var(--color-text); + color: var(--color-primary); font-weight: 600; background-color: var(--color-background-muted); } diff --git a/components/Panel.vue b/components/Panel.vue index 007d2b6..8e72414 100644 --- a/components/Panel.vue +++ b/components/Panel.vue @@ -1,15 +1,38 @@ +<script setup lang="ts"> +import { defineProps, type PropType } from 'vue'; + +defineProps({ + kind: { + type: String as PropType<"normal" | "error" | "success">, + required: false, + default: 'normal', + }, +}); +</script> <template> - <div class="card"> + <div class="card" :class="kind"> <slot /> </div> </template> <style> .card { - background-color: white; padding: 1rem; border-radius: 0.5rem; - /* box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); */ +} + +.normal { + background-color: white; border: 0.1rem solid var(--color-border); } + +.error { + background-color: var(--color-error); + border: 0.1rem solid var(--color-border-error); +} + +.success { + background-color: var(--color-success); + border: 0.1rem solid var(--color-border-success); +} </style>
\ No newline at end of file diff --git a/components/Sidebar.vue b/components/Sidebar.vue index 755f200..9366696 100644 --- a/components/Sidebar.vue +++ b/components/Sidebar.vue @@ -1,5 +1,21 @@ <script setup lang="ts"> +import { formatDistanceToNow } from "date-fns"; +import { LucideClock, LucideRadio } from "lucide-vue-next"; + const scheduleStore = useScheduleStore(); +const errorStore = useErrorStore(); + +const timeUntilConferenceStart = computed(() => { + if (!scheduleStore.schedule) { + return 0; + } + + const now = new Date(); + const conferenceStart = new Date(scheduleStore.schedule.conference.start); + const diff = conferenceStart.getTime() - now.getTime(); + + return diff; +}); </script> <template> @@ -8,6 +24,21 @@ const scheduleStore = useScheduleStore(); <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> + + <Button kind="secondary" @click="errorStore.setError('This doesn\'t do anything yet :-)')">Change conference</Button> + </Panel> + + <Panel kind="success" class="ongoing" v-if="scheduleStore.isConferenceOngoing()"> + <span class="text-icon"><LucideRadio size="var(--text-small)"/> <span>This conference is ongoing</span></span> + <Button kind="primary" @click="navigateTo('/live')">View live</Button> + </Panel> + + <Panel kind="error" class="finished" v-else-if="scheduleStore.isConferenceFinished()"> + <span>This conference has finished</span> + </Panel> + + <Panel class="upcoming" v-else> + <span class="text-icon"><LucideClock size="var(--text-small)" /> <span>Starts in {{ formatDistanceToNow(scheduleStore.getStartDate()) }}</span></span> </Panel> <Nav /> @@ -18,13 +49,23 @@ const scheduleStore = useScheduleStore(); </div> </template> -<style> +<style scoped> .sidebar { display: flex; flex-direction: column; gap: 1rem; } +.finished, .ongoing, .upcoming { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + font-size: var(--text-small); + font-style: oblique; + text-align: center; +} + .conference { display: flex; flex-direction: column; diff --git a/layouts/default.vue b/layouts/default.vue index 1f291ef..cd9fa09 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import { LucideCross, LucideMenu, LucideX } from "lucide-vue-next"; +import { LucideMenu, LucideX } from "lucide-vue-next"; import Dialog from "~/components/Dialog.vue"; import EventDetail from "~/components/EventDetail.vue"; import Sidebar from "~/components/Sidebar.vue"; @@ -78,6 +78,10 @@ router.beforeEach((to, from) => { refErrorDialog.value?.close(); }); +router.afterEach(() => { + showHamburger.value = false; +}); + </script> <template> @@ -155,6 +159,9 @@ header { .planner-content { max-width: 1000px; width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; } .planner-layout { @@ -211,7 +218,8 @@ header { .hamburger-content { background-color: var(--color-background-muted); padding: 1rem; - border-bottom: 2px solid var(--color-border) + border-bottom: 2px solid var(--color-border); + position: fixed; } .planner-sidebar { diff --git a/pages/agenda.vue b/pages/agenda.vue index 316c823..97fdbad 100644 --- a/pages/agenda.vue +++ b/pages/agenda.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import Panel from '~/components/Panel.vue'; - +import { LucideRadio } from "lucide-vue-next"; const favouritesStore = useFavouritesStore(); const scheduleStore = useScheduleStore(); diff --git a/pages/live.vue b/pages/live.vue new file mode 100644 index 0000000..b181ff0 --- /dev/null +++ b/pages/live.vue @@ -0,0 +1,13 @@ +<script setup lang="ts"> +import Panel from '~/components/Panel.vue'; + +</script> + +<template> + <Panel> + </Panel> + +</template> + +<style scoped> +</style>
\ No newline at end of file diff --git a/stores/error.ts b/stores/error.ts index 19ff289..dfb5850 100644 --- a/stores/error.ts +++ b/stores/error.ts @@ -2,9 +2,9 @@ import { type Event } from "./schedule"; import { defineStore } from "pinia"; export const useErrorStore = defineStore('error', () => { - const error = ref(null as Event | null) - const setError = (event: Event) => { - error.value = event + const error = ref(null as string | null) + const setError = (newError: string) => { + error.value = newError } const clearError = () => { error.value = null diff --git a/stores/schedule.ts b/stores/schedule.ts index b1bf536..df2c1ec 100644 --- a/stores/schedule.ts +++ b/stores/schedule.ts @@ -81,20 +81,14 @@ export const useScheduleStore = defineStore('schedule', () => { const tracks = ref({} as { [key: string]: Track }) const setSchedule = (newSchedule: Schedule) => { - schedule.value = newSchedule - - console.log(newSchedule) - tracks.value = {} - schedule.value.tracks.forEach(track => { + newSchedule.tracks.forEach(track => { track.slug = convertToSlug(track.name) tracks.value[track.name] = track }); - console.log("hi") - events.value = [] - schedule.value.days.forEach(day => { + newSchedule.days.forEach(day => { day.start = new TZDate(day.start, newSchedule.conference.timeZoneName) day.end = new TZDate(day.end, newSchedule.conference.timeZoneName) day.rooms.forEach(room => { @@ -111,8 +105,7 @@ export const useScheduleStore = defineStore('schedule', () => { return a.start.getTime() - b.start.getTime() }) - console.log("hi2") - console.log(newSchedule) + schedule.value = newSchedule eventsPerDay.value = {} events.value.forEach(event => { @@ -132,7 +125,25 @@ export const useScheduleStore = defineStore('schedule', () => { }); } - return {schedule, events, eventsPerDay, eventsPerTrack, setSchedule} + const isConferenceOngoing = () => { + if (!schedule.value) { + return false + } + return new Date() >= schedule.value.conference.start && new Date() < schedule.value.conference.end + } + + const isConferenceFinished = () => { + if (!schedule.value) { + return false + } + return new Date() > schedule.value.conference.end + } + + const getStartDate = () => { + return schedule.value?.conference.start || 0 + } + + return {schedule, events, eventsPerDay, eventsPerTrack, setSchedule, isConferenceOngoing, isConferenceFinished, getStartDate} }) function normalizeDates(event: Event, timeZone: string) { |
