Skip to content

Commit

Permalink
Deploy to stage (#140)
Browse files Browse the repository at this point in the history
* Use localStorage to cache logged-in user

* Rename import to use existing variable name

* Per docs, register Router before Auth0 SDK

* Render based on stored user, not only on public vs private route

* Update frontend/src/App.vue

Co-authored-by: Andreas <mail@devmount.de>

* Add comment about registering router before Auth0 SDK

* Documentation and editorconfig (#134)

* 📜 update readme with latest restructuring

* ➕ provide editorconfig

* ❌ remove unused migration code

* Features/99 booking experience for logged in users (#136)

* Update to current state (#135)

* Schedule API endpoints (#114)

* ➕ add schedule API endpoints

* Get current backend tests (#122)

* General documentation (#116)

* ➕ add general documentation

* ➕ add general documentation

* 📜 update component chart

* Backend testing (#117)

* 🔨 updated pytests

* ➕ extend health and authentication tests

* ➕ add authentication for test user

* 📜 document testing in readme

* ➕ add subscriber related tests

* 👕 fix linter issues

* 🔨 prevent connecting calendars manually

* 🔨 check tier limit on connecting calendars

* ➕ add calendar related tests

* 🔨 only allow appointment creations on connected calendars

* 🔨 cascade delete attendees on slot deletion

* ➕ add appointment related tests

* 📜 add hint for smtp server for testing

* ➕ prepare google calendar tests

* ➕ add google test env vars

* 🔨 migrate data structure to current mockup

* ➕ endpoint for schedule creation

* ➕ add schedule test

* ❌ remove appointment type

* 🔨 improve model types and link verification

* ➕ time slots calculation from schedule config

* ➕ time slots comparison with remote events of assigned calendar

* ➕ compare schedule to all connected calendars

* ➕ check calendar connections first

* ➕ add test for invalid availability link

* ➕ check if actual booking slots exist

* Use localStorage to cache logged-in user (#124)

* Use localStorage to cache logged-in user

* Rename import to use existing variable name

* Update frontend/src/views/ProfileView.vue

Co-authored-by: Andreas <mail@devmount.de>

---------

Co-authored-by: Andreas <mail@devmount.de>

* Features/97 Schedules settings page (#128)

* Draft of GA Settings page (#126)

* Basic layout without form

* Finish styling header for general availability

* Finish fake step 1

* Add placeholders for forms, adds buttons

* Add placeholders for date and time inputs

* Add placeholder for step 3

* Change form inputs to correct types

* Additional styling on GA creation view

* Makes booking settings reactive, styles slot length

* Make sections toggle-able

* Set start/end time v-model refs

* Enable date picker for start/end date; add control to remove end date

* Sets default days; shows action buttons

* ➕ implement schedules page frontend

* ➕ implement schedule live preview

* 🔨 fix schedule preview on calendar navigation

* 🔨 fix calendar view tab navigation

* 🔨 fix names and calendar title in preview tooltip

* 🔨 handle unset schedule end date

* ➕ connect schedule page to actual schedule API endpoints

* ➕ add active flag for schedules

* ➕ extend toggle component

* ➕ add disabled state for tab bar

* ➕ implement schedule activation toggle

* 🔨 convert schedule times to utc

* 🔨 fix misaligned starting date for scheduled time slots

* 🔨 make list of weekdays aware to locale start day of week

* 🔨 fix calendar for different start of weeks

* 🔨 reload on locale change to update dayjs instance

* ➕ add option for event popup on right side

* ➕ add month navigation for booking page

* ➕ endpoint for availability booking

* 🔨 fix timezone back calculation on schedule config

* 🔨 fix remote event datetime format

* 🔨 show confirmation modal only on first schedule save

* ➕ implement ICS serving endpoint for schedule availabilities

---------

Co-authored-by: Chris Aquino <chris@thunderbird.net>

* Change how the backend and database migrations are structured (Fixes #119)
- BREAKING: Added `appointment` module folder in `src`.
- BREAKING: alembic.ini should now be placed in the backend root folder. You will need to grab a fresh copy as there's been additional changes.

- Added a super simple cli interface handled by main.py
- Adjusted main.py to bootstrap either a fast api server or a cli interface.
- Added a `update-db` command that is installed when pip install this module.
- The `update-db` command will initialize a fresh database or run migrations, resolving most of our database woes.
- New folder structure more closely matches the deployed folder structure for easier deployments.
- Local docker now only mounts the `src` folder
- Commented out some non-existent columns/constraints in a recent migration
- Added missing trailing slash to the frontend for `schedule/`
- Added sentry support to migrations
- Adjusted code-referenced folder locations as required

* Documentation and editorconfig (#134)

* 📜 update readme with latest restructuring

* ➕ provide editorconfig

* ❌ remove unused migration code

---------

Co-authored-by: Chris Aquino <chris@thunderbird.net>
Co-authored-by: Melissa Autumn <melissa@thunderbird.net>

* ➕ fill in booking modal with logged in users data

---------

Co-authored-by: Chris Aquino <chris@thunderbird.net>
Co-authored-by: Melissa Autumn <melissa@thunderbird.net>

* Add day view event popups (#137)

* Features/92 Pinia state management (#139)

* 📦 upgrade packages, add pinia

* ➕ implement user store with pinia

* 🔨 revamp page container markup

---------

Co-authored-by: Chris Aquino <chris@thunderbird.net>
Co-authored-by: Melissa Autumn <melissa@thunderbird.net>
  • Loading branch information
3 people authored Oct 5, 2023
1 parent 45ca66d commit 3779109
Show file tree
Hide file tree
Showing 19 changed files with 2,830 additions and 2,729 deletions.
103 changes: 52 additions & 51 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,51 +1,52 @@
{
"name": "thunderbird-appointment",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build-stage": "vue-cli-service build --mode staging",
"lint": "yarn run eslint --ext .js,.vue ./src"
},
"dependencies": {
"@auth0/auth0-vue": "^2.0.2",
"@sentry/vue": "^7.56.0",
"@sentry/webpack-plugin": "^2.3.0",
"@tabler/icons-vue": "^2.4.0",
"@vueuse/components": "^9.3.1",
"@vueuse/core": "^9.3.1",
"core-js": "^3.8.3",
"dayjs": "^1.11.5",
"vue": "^3.2.13",
"vue-i18n": "^9.2.2",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@tailwindcss/forms": "^0.5.3",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"autoprefixer": "^10.4.12",
"eslint": "^8.43.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-vue": "^9.12.0",
"postcss": "^8.4.17",
"tailwindcss": "^3.1.8"
},
"babel": {
"presets": [
"@vue/cli-plugin-babel/preset"
]
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
{
"name": "thunderbird-appointment",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build-stage": "vue-cli-service build --mode staging",
"lint": "yarn run eslint --ext .js,.vue ./src"
},
"dependencies": {
"@auth0/auth0-vue": "^2.0.2",
"@sentry/vue": "^7.56.0",
"@sentry/webpack-plugin": "^2.3.0",
"@tabler/icons-vue": "^2.4.0",
"@vueuse/components": "^10.4.1",
"@vueuse/core": "^10.4.1",
"core-js": "^3.8.3",
"dayjs": "^1.11.5",
"pinia": "^2.1.6",
"vue": "^3.2.13",
"vue-i18n": "^9.2.2",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@tailwindcss/forms": "^0.5.3",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"autoprefixer": "^10.4.12",
"eslint": "^8.43.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-vue": "^9.12.0",
"postcss": "^8.4.17",
"tailwindcss": "^3.1.8"
},
"babel": {
"presets": [
"@vue/cli-plugin-babel/preset"
]
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
62 changes: 31 additions & 31 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
<!DOCTYPE html>
<html lang="">
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>appointment_logo.svg">
<script>
// handle theme color scheme
if (
localStorage.getItem('theme') === 'dark'
|| (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
</head>
<body class="h-screen bg-white text-gray-900 dark:bg-gray-700 dark:text-gray-100">
<noscript>
<strong>
We're sorry but this application doesn't work properly without JavaScript enabled.
Please enable it to continue.
</strong>
</noscript>
<div id="app" class="h-full"></div>
<!-- built files will be auto injected -->
</body>
</html>
<!DOCTYPE html>
<html lang="">
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>appointment_logo.svg">
<script>
// handle theme color scheme
if (
localStorage.getItem('theme') === 'dark'
|| (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
</head>
<body class="h-screen bg-white text-gray-900 dark:bg-gray-700 dark:text-gray-100">
<noscript>
<strong>
We're sorry but this application doesn't work properly without JavaScript enabled.
Please enable it to continue.
</strong>
</noscript>
<div id="app" class="h-full"></div>
<!-- built files will be auto injected -->
</body>
</html>
41 changes: 20 additions & 21 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
<template>
<!-- authenticated subscriber content -->
<template v-if="currentUser">
<template v-if="isAuthenticated">
<site-notification
v-if="siteNotificationStore.display"
:title="siteNotificationStore.title"
:action-url="siteNotificationStore.actionUrl"
>
{{ siteNotificationStore.message }}
</site-notification>
<nav-bar :nav-items="navItems" :user="currentUser" />
<main class="mt-12 mx-4 lg:mx-8">
<div class="w-full max-w-[1740px] mx-auto">
<router-view
:calendars="calendars"
:appointments="appointments"
:user="currentUser"
/>
</div>
<nav-bar :nav-items="navItems" />
<main class="mx-4 pt-24 lg:mx-8 h-full">
<router-view
:calendars="calendars"
:appointments="appointments"
/>
</main>
</template>
<!-- for home page and booking page -->
<template v-else-if="routeIsPublic">
<title-bar />
<router-view />
<main class="mx-4 pt-24 lg:mx-8 h-full">
<router-view />
</main>
</template>
<template v-else>
<!-- TODO: handle wrong route -->
A authentication or routing error occured.
</template>
</template>

Expand All @@ -40,17 +40,18 @@ import TitleBar from "@/components/TitleBar";
import SiteNotification from "@/elements/SiteNotification";
import { siteNotificationStore } from "@/stores/alert-store";
// current user object
// structure: { username, email, name, level, timezone, id }
import { userStore as currentUser } from '@/stores/user-store';
// stores
import { useUserStore } from '@/stores/user-store';
// component constants
const currentUser = useUserStore(); // data: { username, email, name, level, timezone, id }
const apiUrl = inject("apiUrl");
const dj = inject("dayjs");
const route = useRoute();
// handle auth and fetch
const auth = useAuth0();
const isAuthenticated = computed(() => auth.isAuthenticated.value);
const call = createFetch({
baseUrl: apiUrl,
options: {
Expand Down Expand Up @@ -115,16 +116,14 @@ const appointments = ref([]);
// true if route can be accessed without authentication
const routeIsPublic = computed(
() =>
route.name === "booking" ||
route.name === "availability" ||
(route.name === "home" && !auth.isAuthenticated.value)
() => ["booking", "availability", "home"].includes(route.name)
);
// check login state of current user first
const checkLogin = async () => {
console.log(auth.user.value, currentUser.data);
if (auth.isAuthenticated.value) {
if (currentUser.value) {
if (currentUser.exists() && currentUser.data.email === auth.user.value.email) {
// avoid calling the backend unnecessarily
return;
}
Expand All @@ -134,7 +133,7 @@ const checkLogin = async () => {
if (!error.value && data.value) {
// data.value holds appointment subscriber structure
// auth.user.value holds auth0 user structure
currentUser.value = data.value;
currentUser.$patch({ data: data.value });
} else if (data.value && data.value.detail === "Missing bearer token") {
// Try logging in if we have an expired refresh token, but a valid authentication id.
await auth.loginWithRedirect();
Expand Down Expand Up @@ -188,7 +187,7 @@ const extendDbData = () => {
a.active = a.status !== appointmentState.past; // TODO
// convert start dates from UTC back to users timezone
a.slots.forEach((s) => {
s.start = dj.utc(s.start).tz(currentUser.value.timezone ?? dj.tz.guess());
s.start = dj.utc(s.start).tz(currentUser.data.timezone ?? dj.tz.guess());
});
});
};
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/AppointmentCreation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@
import { locationTypes } from "@/definitions";
import { ref, reactive, computed, inject, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useUserStore } from '@/stores/user-store';
import AppointmentCreatedModal from "@/components/AppointmentCreatedModal";
import CalendarMonth from "@/components/CalendarMonth";
import PrimaryButton from "@/elements/PrimaryButton";
Expand All @@ -258,6 +259,7 @@ import {
import AlertBox from "@/elements/AlertBox";
// component constants
const user = useUserStore();
const { t } = useI18n();
const dj = inject("dayjs");
const call = inject("call");
Expand All @@ -270,7 +272,6 @@ const emit = defineEmits(["start", "next", "create", "cancel"]);
const props = defineProps({
status: Number, // dialog creation progress [hidden: 0, details: 1, availability: 2, finished: 3]
calendars: Array, // list of user defined calendars
user: Object, // currently logged in user, null if not logged in
});
// calculate the current visible step by given status
Expand Down Expand Up @@ -313,7 +314,7 @@ const slotList = computed(() => {
list.push({
// save local time as UTC
start: start
.tz(props.user.timezone ?? dj.tz.guess(), true)
.tz(user.data.timezone ?? dj.tz.guess(), true)
.utc()
.format("YYYY-MM-DDTHH:mm:ss"),
// calculate duration as difference between start and end
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/BookingModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { inject, computed, reactive, ref, onMounted } from 'vue';
import { timeFormat } from '@/utils';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useUserStore } from '@/stores/user-store';
import ArtConfetti from '@/elements/arts/ArtConfetti';
import PrimaryButton from '@/elements/PrimaryButton';
import SecondaryButton from '@/elements/SecondaryButton';
Expand All @@ -85,6 +86,7 @@ import SecondaryButton from '@/elements/SecondaryButton';
import { IconX } from '@tabler/icons-vue';
// component constants
const user = useUserStore();
const { t } = useI18n();
const route = useRoute();
const dj = inject('dayjs');
Expand All @@ -109,9 +111,9 @@ const attendee = reactive({
email: '',
});
onMounted(() => {
if (props.user) {
attendee.name = props.user.name;
attendee.email = props.user.email;
if (user.exists()) {
attendee.name = user.data.name;
attendee.email = user.data.email;
}
});
const validAttendee = computed(() => attendee.email.length > 2);
Expand Down
14 changes: 10 additions & 4 deletions frontend/src/components/NavBar.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<template>
<header class="h-16 px-4 shadow-lg border-b flex justify-between border-gray-300 dark:border-gray-600">
<header
class="
fixed z-50 h-16 w-full px-4 shadow-lg border-b flex justify-between
bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600
"
>
<router-link
class="py-4 pl-4 pr-8 border-r border-gray-300 dark:border-gray-600 shrink-0"
:to="{ name: 'calendar' }"
Expand Down Expand Up @@ -38,11 +43,11 @@
</li>
</ul>
<router-link
v-if="user"
v-if="user.exists()"
:to="{ name: 'profile' }"
class="w-12 h-12 mr-4 self-center flex-center rounded-full bg-teal-500 text-lg font-normal text-white"
>
{{ initials(user.name) }}
{{ initials(user.data.name) }}
</router-link>
</nav>
</header>
Expand All @@ -52,17 +57,18 @@
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { initials } from '@/utils';
import { useUserStore } from '@/stores/user-store';
// icons
// import { IconSearch } from '@tabler/icons-vue';
// component constants
const user = useUserStore();
const route = useRoute();
const { t } = useI18n();
// component properties
defineProps({
navItems: Array, // list of route names that are also lang keys (format: label.<key>), used as nav items
user: Object, // currently logged in user, null if not logged in
});
</script>
Loading

0 comments on commit 3779109

Please sign in to comment.