Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event Calendar #346

Merged
merged 38 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9ea38be
Create new Calendar tab - WIP
keifererikson Apr 30, 2024
4dfab43
Restructure files to better match the existing project
keifererikson May 1, 2024
091307a
Progress on styling the calendar
keifererikson May 3, 2024
b6c29b9
styling for all meetups
keifererikson May 3, 2024
18262f7
Starting EventList
keifererikson May 4, 2024
875b151
Update styles
keifererikson May 21, 2024
baa618f
Merge branch 'main' into calendar
keifererikson May 21, 2024
a48cd0f
Remove EventList.vue - seperate feature
keifererikson May 21, 2024
b4c5eb8
Change to current event dates
keifererikson May 21, 2024
6de2d79
Implementing API - CORS Error
keifererikson May 21, 2024
fcba8b9
Add Dialog Popup on click
keifererikson May 22, 2024
f221678
Remove rawData - CORS error resolved
keifererikson May 23, 2024
bc85fe7
Cleanup unused styling for icons/meetups
keifererikson May 23, 2024
6c28057
Merge branch 'main' into calendar
keifererikson May 23, 2024
1e82084
Add EventDetail component
keifererikson May 24, 2024
e4fb77a
Cleanup and optimization
keifererikson May 24, 2024
000f688
Fix fetch url
keifererikson May 24, 2024
473cd7c
Add domain to fetch url
keifererikson May 24, 2024
953fd07
WIP: switching to useLazyAsyncData instead of onMounted
keifererikson May 24, 2024
b3897e4
Remove improper calendar-events.ts - not a constant
keifererikson May 25, 2024
d6d28d7
Remove improper calendar-events.ts - not a constant
keifererikson May 25, 2024
6328c42
Merge branch 'calendar' of https://github.com/keifererikson/DES-Websi…
keifererikson May 25, 2024
82839b5
Change to useLazyFetch
keifererikson May 25, 2024
6f477a7
Add domain to fetch url
keifererikson May 25, 2024
f11fc33
Fix Calendar not loading initially
keifererikson May 27, 2024
6d5c015
Disable 'start-week-on-sunday' to fix off by one error
keifererikson May 28, 2024
e4fe284
Move calendar to Events page
keifererikson May 28, 2024
8530ee0
Add Calendar/List toggle
keifererikson May 28, 2024
89d0f70
Change Modal to be more reusable
keifererikson May 28, 2024
3085d79
Add footer to Modal
keifererikson May 28, 2024
370e30d
Change Modal to use v-model
keifererikson May 28, 2024
100c60e
Add clickable links and render markdown to description
keifererikson May 31, 2024
1b2432e
Final tweaks - Remove Calendar/List toggle
keifererikson Jun 5, 2024
0a91234
chore: apply automated updates
autofix-ci[bot] Jun 7, 2024
5acf3a2
Update package.json
MandyMeindersma Jun 17, 2024
5335b53
chore: apply automated updates
autofix-ci[bot] Jun 17, 2024
2ba1103
Update package.json
MandyMeindersma Jun 17, 2024
8947c07
Merge branch 'main' into calendar
MandyMeindersma Jun 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>