Skip to content

Commit

Permalink
Merge branch 'main' into stage
Browse files Browse the repository at this point in the history
  • Loading branch information
MelissaAutumn committed Dec 15, 2023
2 parents 20e199e + 5b905f9 commit 68bf719
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 190 deletions.
6 changes: 6 additions & 0 deletions backend/src/appointment/database/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ class Config:
from_attributes = True


class AppointmentWithCalendarOut(Appointment):
"""For /me/appointments"""
calendar_title: str
calendar_color: str


class AppointmentOut(AppointmentBase):
id: int | None = None
owner_name: str | None = None
Expand Down
7 changes: 6 additions & 1 deletion backend/src/appointment/routes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,17 @@ def read_my_calendars(
return [schemas.CalendarOut(id=c.id, title=c.title, color=c.color, connected=c.connected) for c in calendars]


@router.get("/me/appointments", response_model=list[schemas.Appointment])
@router.get("/me/appointments", response_model=list[schemas.AppointmentWithCalendarOut])
def read_my_appointments(db: Session = Depends(get_db), subscriber: Subscriber = Depends(get_subscriber)):
"""get all appointments of authenticated subscriber"""
if not subscriber:
raise HTTPException(status_code=401, detail="No valid authentication credentials provided")
appointments = repo.get_appointments_by_subscriber(db, subscriber_id=subscriber.id)
# Mix in calendar title and color.
# Note because we `__dict__` any relationship values won't be carried over, so don't forget to manually add those!
appointments = map(
lambda x: schemas.AppointmentWithCalendarOut(**x.__dict__, slots=x.slots, calendar_title=x.calendar.title,
calendar_color=x.calendar.color), appointments)
return appointments


Expand Down
117 changes: 29 additions & 88 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
</site-notification>
<nav-bar :nav-items="navItems" />
<main :class="{'mx-4 pt-24 lg:mx-8 min-h-full pb-24': !routeIsHome, 'pt-32': routeIsHome}">
<router-view
:calendars="calendars"
:appointments="appointments"
/>
<router-view />
</main>
<footer-bar />
</template>
Expand All @@ -32,23 +29,23 @@
</template>

<script setup>
import { appointmentState } from "@/definitions";
import { createFetch } from "@vueuse/core";
import { ref, inject, provide, onMounted, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import NavBar from "@/components/NavBar";
import TitleBar from "@/components/TitleBar";
import FooterBar from "@/components/FooterBar.vue";
import SiteNotification from "@/elements/SiteNotification";
import { useSiteNotificationStore } from "@/stores/alert-store";
import { createFetch } from '@vueuse/core';
import { inject, provide, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import NavBar from '@/components/NavBar';
import TitleBar from '@/components/TitleBar';
import FooterBar from '@/components/FooterBar.vue';
import SiteNotification from '@/elements/SiteNotification';
import { useSiteNotificationStore } from '@/stores/alert-store';
// stores
import { useUserStore } from '@/stores/user-store';
import { useCalendarStore } from '@/stores/calendar-store';
import { useAppointmentStore } from '@/stores/appointment-store';
// component constants
const currentUser = useUserStore(); // data: { username, email, name, level, timezone, id }
const apiUrl = inject("apiUrl");
const dj = inject("dayjs");
const apiUrl = inject('apiUrl');
const route = useRoute();
const router = useRouter();
const siteNotificationStore = useSiteNotificationStore();
Expand All @@ -68,8 +65,8 @@ const call = createFetch({
async onFetchError({ data, response, error }) {
// Catch any google refresh error that may occur
if (
data?.detail?.error === 'google_refresh_error' &&
!siteNotificationStore.isSameNotification('google_refresh_error')
data?.detail?.error === 'google_refresh_error'
&& !siteNotificationStore.isSameNotification('google_refresh_error')
) {
// Ensure other async calls don't reach here
siteNotificationStore.lock(data.detail.error);
Expand Down Expand Up @@ -97,26 +94,26 @@ const call = createFetch({
},
},
fetchOptions: {
mode: "cors",
credentials: "include",
mode: 'cors',
credentials: 'include',
},
});
provide("call", call);
provide('call', call);
provide('isPasswordAuth', import.meta.env?.VITE_AUTH_SCHEME === 'password');
provide('isFxaAuth', import.meta.env?.VITE_AUTH_SCHEME === 'fxa');
provide('fxaEditProfileUrl', import.meta.env?.VITE_FXA_EDIT_PROFILE);
// menu items for main navigation
const navItems = [
"calendar",
"schedule",
"appointments",
"settings",
'calendar',
'schedule',
'appointments',
'settings',
];
// db tables
const calendars = ref([]);
const appointments = ref([]);
const calendarStore = useCalendarStore();
const appointmentStore = useAppointmentStore();
// true if route can be accessed without authentication
const routeIsPublic = computed(
Expand All @@ -126,76 +123,20 @@ const routeIsHome = computed(
() => ['home'].includes(route.name),
);
// query db for all calendar data
const getDbCalendars = async (onlyConnected = true) => {
const { data, error } = await call(`me/calendars?only_connected=${onlyConnected}`).get().json();
if (!error.value) {
if (data.value === null || typeof data.value === "undefined") return;
calendars.value = data.value;
}
};
// query db for all appointments data
const getDbAppointments = async () => {
const { data, error } = await call("me/appointments").get().json();
if (!error.value) {
if (data.value === null || typeof data.value === "undefined") return;
appointments.value = data.value;
}
};
// check appointment status for current state (past|pending|booked)
const getAppointmentStatus = (a) => {
// check past events
if (a.slots.filter((s) => dj(s.start).isAfter(dj())).length === 0) {
return appointmentState.past;
}
// check booked events
if (a.slots.filter((s) => s.attendee_id != null).length > 0) {
return appointmentState.booked;
}
// else event is still wating to be booked
return appointmentState.pending;
};
// extend retrieved data
const extendDbData = () => {
// build { calendarId => calendarData } object for direct lookup
const calendarsById = {};
calendars.value.forEach((c) => {
calendarsById[c.id] = c;
});
// extend appointments data with active state and calendar title and color
appointments.value.forEach((a) => {
a.calendar_title = calendarsById[a.calendar_id]?.title;
a.calendar_color = calendarsById[a.calendar_id]?.color;
a.status = getAppointmentStatus(a);
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.data.timezone ?? dj.tz.guess());
});
});
};
const getAppointmentStatus = (a) => appointmentStore.status(a);
// retrieve calendars and appointments after checking login and persisting user to db
const getDbData = async (options = {}) => {
const { onlyConnectedCalendars = true } = options;
const getDbData = async () => {
if (currentUser?.exists()) {
await Promise.all([
getDbCalendars(onlyConnectedCalendars),
getDbAppointments(),
calendarStore.fetch(call),
appointmentStore.fetch(call),
]);
extendDbData();
}
};
// get the data initially
onMounted(async () => {
await getDbData();
});
// provide refresh functions for components
provide("refresh", getDbData);
provide("getAppointmentStatus", getAppointmentStatus);
provide('refresh', getDbData);
provide('getAppointmentStatus', getAppointmentStatus);
</script>
20 changes: 9 additions & 11 deletions frontend/src/components/SettingsAccount.vue
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,19 @@ import TextButton from '@/elements/TextButton.vue';
// icons
import { IconExternalLink } from '@tabler/icons-vue';
// stores
import { useExternalConnectionsStore } from '@/stores/external-connections-store';
// component constants
const { t } = useI18n({ useScope: 'global' });
const call = inject('call');
const refresh = inject('refresh');
const router = useRouter();
const user = useUserStore();
const externalConnectionsStore = useExternalConnectionsStore();
const externalConnections = ref({});
const hasZoomAccountConnected = computed(() => (externalConnections.value?.zoom?.length ?? []) > 0);
const zoomAccountName = computed(() => (externalConnections.value?.zoom[0].name ?? null));
// Currently we only support one zoom account being connected at once.
const hasZoomAccountConnected = computed(() => (externalConnectionsStore.zoom.length) > 0);
const zoomAccountName = computed(() => (externalConnectionsStore.zoom[0]?.name ?? null));
const activeUsername = ref(user.data.username);
const activeDisplayName = ref(user.data.name);
Expand Down Expand Up @@ -195,15 +198,9 @@ const getSignedUserUrl = async () => {
signedUserUrl.value = data.value.url;
};
const getExternalConnections = async () => {
const { data } = await call('account/external-connections').get().json();
externalConnections.value = data.value;
};
const refreshData = async () => Promise.all([
getSignedUserUrl(),
getExternalConnections(),
refresh(),
externalConnectionsStore.fetch(call),
]);
// save user data
Expand Down Expand Up @@ -251,6 +248,7 @@ const connectZoom = async () => {
};
const disconnectZoom = async () => {
await call('zoom/disconnect').post();
await useExternalConnectionsStore().reset();
await refreshData();
};
Expand Down
23 changes: 12 additions & 11 deletions frontend/src/components/SettingsCalendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<calendar-management
:title="t('heading.calendarsUnconnected')"
:type="calendarManagementType.connect"
:calendars="calendars"
:calendars="calendarStore.unconnectedCalendars"
:loading="loading"
@sync="syncCalendars"
@modify="connectCalendar"
Expand All @@ -18,7 +18,7 @@
<calendar-management
:title="t('heading.calendarsConnected')"
:type="calendarManagementType.edit"
:calendars="calendars"
:calendars="calendarStore.connectedCalendars"
:loading="loading"
@remove="deleteCalendar"
@modify="editCalendar"
Expand Down Expand Up @@ -199,7 +199,9 @@
<script setup>
import { calendarManagementType } from '@/definitions';
import { IconArrowRight } from '@tabler/icons-vue';
import { ref, reactive, inject, onMounted, computed } from 'vue';
import {
ref, reactive, inject, onMounted, computed,
} from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import AlertBox from '@/elements/AlertBox';
Expand All @@ -208,12 +210,14 @@ import GoogleSignInBtn from '@/assets/img/google/1x/btn_google_signin_light_norm
import GoogleSignInBtn2x from '@/assets/img/google/2x/[email protected]';
import PrimaryButton from '@/elements/PrimaryButton';
import SecondaryButton from '@/elements/SecondaryButton';
import ConfirmationModal from "@/components/ConfirmationModal.vue";
import ConfirmationModal from '@/components/ConfirmationModal.vue';
import { useCalendarStore } from '@/stores/calendar-store';
// component constants
const { t } = useI18n({ useScope: 'global' });
const call = inject('call');
const refresh = inject('refresh');
const calendarStore = useCalendarStore();
const calendarConnectError = ref('');
Expand All @@ -223,11 +227,6 @@ const deleteCalendarModalTarget = ref(null);
// Temp until we get a store solution rolling
const loading = ref(false);
// view properties
defineProps({
calendars: Array, // list of calendars from db
});
// handle calendar user input to add or edit calendar connections
const inputModes = {
hidden: 0,
Expand Down Expand Up @@ -264,7 +263,9 @@ const closeModals = async () => {
};
const refreshData = async () => {
await refresh({ onlyConnectedCalendars: false });
// Invalidate our calendar store
await calendarStore.reset();
await refresh();
loading.value = false;
};
Expand Down Expand Up @@ -392,6 +393,6 @@ onMounted(async () => {
await router.replace(route.path);
}
await refreshData();
await refresh();
});
</script>
4 changes: 2 additions & 2 deletions frontend/src/stores/alert-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const initialSiteNotificationObject = {
// eslint-disable-next-line import/prefer-default-export
export const useSiteNotificationStore = defineStore('siteNotification', {
state: () => ({
data: initialSiteNotificationObject,
data: structuredClone(initialSiteNotificationObject),
}),
getters: {
isVisible() {
Expand Down Expand Up @@ -50,7 +50,7 @@ export const useSiteNotificationStore = defineStore('siteNotification', {
});
},
reset() {
this.$patch({ data: initialSiteNotificationObject });
this.$patch({ data: structuredClone(initialSiteNotificationObject) });
},
},
});
Loading

0 comments on commit 68bf719

Please sign in to comment.