Skip to content

Commit

Permalink
Event Calendar (#346)
Browse files Browse the repository at this point in the history
* Create new Calendar tab - WIP

* Restructure files to better match the existing project

* Progress on styling the calendar

* styling for all meetups

* Starting EventList

* Update styles

* Remove EventList.vue - seperate feature

* Change to current event dates

* Implementing API - CORS Error

* Add Dialog Popup on click
rawData is a temporary solution until we resolve the CORS error

* Remove rawData - CORS error resolved

* Cleanup unused styling for icons/meetups
May re implement this in the future

* Add EventDetail component

* Cleanup and optimization

* Fix fetch url

* Add domain to fetch url

* WIP: switching to useLazyAsyncData instead of onMounted

* Remove improper calendar-events.ts - not a constant
Move Event class to calendar.vue page

* Remove improper calendar-events.ts - not a constant
Move Event class to calendar.vue page

* Change to useLazyFetch

* Add domain to fetch url
Appears to only work with the domain currently..

* Fix Calendar not loading initially
This should implement the fixes @arashsheyda mentioned

* Disable 'start-week-on-sunday' to fix off by one error

* Move calendar to Events page

* Add Calendar/List toggle
Also moved loading animation to Calendar component

* Change Modal to be more reusable
Having issues implementing useVModels

* Add footer to Modal

* Change Modal to use v-model

* Add clickable links and render markdown to description

* Final tweaks - Remove Calendar/List toggle
Added subtle border to events to help differentiate events that are
touching eachother.
Commented out Calendar/List toggle for now, until the list feature is
implemented.

* chore: apply automated updates

* Update package.json

* chore: apply automated updates

* Update package.json

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Mandy Meindersma <mandy.m.meindersma@gmail.com>
  • Loading branch information
3 people committed Jun 17, 2024
1 parent f52f12f commit 6970843
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 1 deletion.
191 changes: 191 additions & 0 deletions components/app/Calendar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<script setup lang="ts">
import VueCal from 'vue-cal'
import 'vue-cal/dist/vuecal.css'
defineProps<{
group: any
pending: boolean
}>()
const showEventModal = ref(false)
const selectedEvent = ref<any>({})
const onEventClick = (event: any, e: any) => {
selectedEvent.value = event
showEventModal.value = true
e.stopPropagation()
}
</script>

<template>
<section
v-if="!pending"
:id="slugify(group.name)"
:key="group.name"
class="max-w-7xl mx-auto lg:pt-20 pt-10 px-4 relative"
>
<ProseH1 class="mb-8 text-center">
{{ group.name }}
</ProseH1>
<vue-cal
class="rounded-lg bg-white dark:bg-neutral-800 overflow-hidden shadow"
today-button
small
:events-on-month-view="true"
:twelve-hour="true"
:events="group.items"
:start-week-on-sunday="false"
:disable-views="['years', 'year', 'day']"
:time-from="8 * 60"
:time-to="22 * 60"
:time-step="60"
:on-event-click="onEventClick"
>
<template #arrow-prev>
<Icon
class="w-8 h-8"
name="i-ph-arrow-left"
/>
</template>
<template #arrow-next>
<Icon
class="w-8 h-8"
name="i-ph-arrow-right"
/>
</template>
</vue-cal>

<!-- TODO: Implement the list view
<div
id="calendar-list-toggle"
class="w-[180px] bg-gray-400/20 rounded-lg absolute top-[158px] left-[22px]"
>
<div
class="w-1/2 inline-flex items-center"
>
<input
id="calendar-toggle"
name="calendar-list-toggle-radio"
type="radio"
class="hidden peer"
checked
>
<label
for="calendar-toggle"
class="w-full text-center px-3 py-1 cursor-pointer rounded-lg peer-checked:bg-primary peer-checked:text-white"
>
Calendar
</label>
</div>
<div
class="w-1/2 inline-flex items-center"
>
<input
id="list-toggle"
name="calendar-list-toggle-radio"
type="radio"
class="hidden peer"
>
<label
for="list-toggle"
class="w-full text-center px-3 py-1 cursor-pointer rounded-lg peer-checked:bg-primary peer-checked:text-white"
>
List
</label>
</div>
</div> -->

<AppModal
id="event-modal"
v-model="showEventModal"
>
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
{{ selectedEvent.title }}
</h3>
<button
type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
>
<Icon
class="w-6 h-6"
name="i-ph-x-light"
@click="showEventModal = false"
/>
<span class="sr-only">Close dialog</span>
</button>
</div>
<div class="p-4 space-y-4">
<p class="text-sm text-gray-500">
{{ selectedEvent.start.format('DD/MM/YYYY') }}
</p>
<p
class="content-full"
v-html="selectedEvent.description"
/>
<div>
<strong>Event details:</strong>
<ul>
<li>Event starts at: {{ selectedEvent.start.formatTime() }} MT</li>
<li>Event ends at: {{ selectedEvent.end.formatTime() }} MT</li>
</ul>
</div>
</div>
</AppModal>
</section>
<section
v-if="pending"
class="max-w-7xl mx-auto lg:py-20 py-10 px-4"
>
<ProseH1 class="mb-8 text-center">
{{ group.name }}
</ProseH1>
<div class="rounded-lg bg-white dark:bg-neutral-800 overflow-hidden shadow h-[691px]">
<div class="flex items-center justify-center h-96">
<Icon
class="w-12 h-12 animate-spin"
name="i-ph-spinner"
/>
</div>
</div>
</section>
</template>

<style>
.vuecal__event-title {
@apply text-xs sm:text-sm font-semibold;
}
.vuecal__event-time {
@apply text-[0.5rem] sm:text-xs;
}
.vuecal__event:hover {
@apply cursor-pointer;
}
.vuecal__event {
@apply flex flex-col justify-center p-2 bg-primary text-white border border-gray-400/25;
}
.vuecal__event-content {
@apply italic text-xs;
}
.vuecal__title-bar {
@apply relative;
}
.vuecal__today-btn {
@apply bg-gray-400/20 hover:bg-gray-400/25 py-1 px-3 me-4 rounded-lg dark:text-white text-black font-semibold absolute right-[-10px] top-[-39px];
}
div.vuecal__cell:nth-child(7)::before {
@apply rounded-ee-lg;
}
#event-modal .content-full a{
@apply hover:underline text-gray-600 dark:text-gray-400;
}
</style>
49 changes: 49 additions & 0 deletions components/app/Modal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script setup lang="ts">
const props = defineProps<{
modelValue: boolean
}>()
const modal = useVModel(props, 'modelValue')
const modalRef = ref(null)
const closeModal = () => {
modal.value = false
}
onClickOutside(modalRef, () => {
closeModal()
})
</script>

<template>
<div
v-if="modal"
id="modal"
class="flex items-center justify-center overflow-hidden fixed top-0 left-0 z-10 w-full h-full bg-slate-400 bg-opacity-30"
@click.stop
>
<div
ref="modalRef"
class="m-4 relative w-full max-w-2xl max-h-full bg-white border border-neutral-400/20 rounded-lg dark:bg-neutral-800 shadow z-50"
>
<slot />
<slot name="footer">
<div class="flex items-center p-4 border-t border-gray-200 rounded-b dark:border-gray-600">
<button
type="button"
class="duration-300 transition-all hover:bg-gray-200/30 dark:hover:bg-transparent border border-transparent rounded-lg bg-primary text-white px-3 py-1 hover:border-primary hover:text-primary flex items-center"
@click="closeModal"
>
Close
</button>
</div>
</slot>
</div>
</div>
</template>

<style>
#event-detail .content-full a{
@apply hover:underline text-gray-600 dark:text-gray-400;
}
</style>
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"typescript": "^5.4.3",
"vitest": "^1.4.0",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
"vue-router": "^4.3.0",
"vue-cal": "^4.8.1"
}
}
57 changes: 57 additions & 0 deletions pages/events.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,58 @@
<script setup lang="ts">
const group = { name: 'Community Events', items: events }
class Event {
start: Date
end: Date
title: string
organizer: string
content: string
description: string
class: string
eventUrl: string
constructor(start: string, end: string, summary: string, organizer: string, content: string, description: string, eventUrl: string) {
this.start = new Date(start)
this.end = new Date(end)
this.title = summary
this.organizer = organizer
this.content = content
this.description = description ? renderMarkdown(convertUrlsToLinks(description)) : description
this.class = this.organizer
this.eventUrl = eventUrl
}
}
const convertUrlsToLinks = (description: string) => {
const urlRegex = /(?<!["'>]|href=")\b((https?:\/\/)(([a-zA-Z0-9-]+\.)+)?[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?(\/[^\s"'<]*)?(\?[^\s"'<]*)?(:(\d{1,5}))?\/?)(?!["'<])/gm
return description.replace(urlRegex, '<a href="$&" target="_blank">$&</a>')
}
function renderMarkdown(description: string) {
return description.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>')
}
const createEventsList = (events: any) => {
return events.map((event: any) => new Event(
event.start.dateTime,
event.end.dateTime,
event.summary,
'',
'',
event.description,
event.htmlLink,
))
}
let groupCalendar = { name: 'Calendar', items: [] }
const { pending, data } = await useLazyFetch('https://devedmonton.com/api/events', {
transform: (data) => {
groupCalendar = { name: 'Calendar', items: createEventsList((data as any).events) }
},
})
const title = 'Events'
const description = 'List of all the organizations that have fun tech events in Edmonton.'
Expand All @@ -17,6 +69,11 @@ defineOgImage({

<template>
<main>
<AppCalendar
:group="groupCalendar"
:pending="pending"
/>

<AppSection :group="group" />
</main>
</template>

0 comments on commit 6970843

Please sign in to comment.