Skip to content

Commit

Permalink
feat(calendar-poc): validate using google calendar as our backend wil…
Browse files Browse the repository at this point in the history
…l work (#15)

* feat(calendar): ability to view the bookings

* feat(my-bookings): display a different icon if it's my booking

* fix(multi-day): fix for when the boat is booked over multiple days

* chore(cleanup): just some type cleanup
  • Loading branch information
hutchic authored Jul 19, 2024
1 parent e348e6b commit 49fbdbf
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 5 deletions.
5 changes: 5 additions & 0 deletions app/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export default defineNuxtConfig({
installPrompt: true
}
},
runtimeConfig: {
public: {
googleCalendarId: '7417fba20bb0d2c726dba1575d7d6421014047d0dbfe15875c8dd3588796f8c6@group.calendar.google.com'
}
},
vite: {
server: {
hmr: {
Expand Down
163 changes: 158 additions & 5 deletions app/pages/secure.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,161 @@
<template>
<UContainer>
<UCard class="mt-10">
<h1>Secure Page</h1>
<p>All your base belong to us.</p>
</UCard>
<UCard class="mt-10">
<h1>Boat Booking</h1>
<p>Boat Availability Calendar:</p>
<div v-if="loading">
<p>Loading calendar events...</p>
</div>
<div v-else>
<div class="calendar">
<div class="day" v-for="day in calendarDays" :key="day.date">
<span :class="{ booked: day.isBooked }">{{ day.date }}</span>
<template v-if="day.isBooked">
<UTooltip :text="day.tooltipText" :popper="{ placement: 'top', arrow: true }">
<template v-if="day.isUserBooking">
<UIcon name="i-heroicons-user-circle" dynamic class="icon-user-booked" />
</template>
<template v-else>
<UIcon name="i-heroicons-x-circle" dynamic class="icon-booked" />
</template>
</UTooltip>
</template>
<template v-else>
<UIcon name="i-heroicons-check-circle" dynamic class="icon-available" />
</template>
</div>
</div>
</div>
</UCard>
</UContainer>
</template>
</template>

<script setup lang="ts">
import { ref, onMounted, Ref } from 'vue';
import { useRuntimeConfig } from '#app';
interface CalendarEvent {
start: { date: string };
end: { date: string };
creator: { email: string };
}
interface CalendarDay {
date: string;
isBooked: boolean;
isUserBooking: boolean;
tooltipText: string;
}
const loading: Ref<boolean> = ref(true);
const events: Ref<CalendarEvent[]> = ref([]);
const calendarDays: Ref<CalendarDay[]> = ref([]);
const userEmail: Ref<string> = ref('');
const config = useRuntimeConfig();
const listCalendarEvents = async () => {
const accessToken = sessionStorage.getItem('googleAccessToken');
if (!accessToken) {
console.error('No access token found');
return;
}
try {
const response = await fetch(`https://www.googleapis.com/calendar/v3/calendars/${config.public.googleCalendarId}/events`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch calendar events');
}
const data = await response.json();
events.value = data.items;
generateCalendarDays();
} catch (error) {
console.error('Error fetching calendar events', error);
} finally {
loading.value = false;
}
};
const fetchUserProfile = async () => {
const accessToken = sessionStorage.getItem('googleAccessToken');
if (!accessToken) {
console.error('No access token found');
return;
}
try {
const response = await fetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch user profile');
}
const data = await response.json();
userEmail.value = data.email;
} catch (error) {
console.error('Error fetching user profile', error);
}
};
const generateCalendarDays = () => {
const today = new Date();
const daysInMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate();
calendarDays.value = Array.from({ length: daysInMonth }, (_, i) => {
const date = new Date(today.getFullYear(), today.getMonth(), i + 1).toISOString().split('T')[0];
return { date, isBooked: false, isUserBooking: false, tooltipText: '' };
});
events.value.forEach(event => {
const startDate = new Date(event.start.date);
const endDate = new Date(event.end.date);
for (let d = startDate; d < endDate; d.setDate(d.getDate() + 1)) {
const dayIndex = d.getDate() - 1;
if (calendarDays.value[dayIndex]) {
calendarDays.value[dayIndex].isBooked = true;
calendarDays.value[dayIndex].isUserBooking = event.creator.email === userEmail.value;
calendarDays.value[dayIndex].tooltipText = `Booked by: ${event.creator.email}`;
}
}
});
};
onMounted(async () => {
await fetchUserProfile();
await listCalendarEvents();
});
</script>

<style>
.calendar {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1rem;
}
.day {
display: flex;
flex-direction: column;
align-items: center;
}
.icon-booked {
color: red;
}
.icon-user-booked {
color: blue;
}
.icon-available {
color: green;
}
.booked {
font-weight: bold;
}
</style>

0 comments on commit 49fbdbf

Please sign in to comment.