Skip to content

Commit

Permalink
🔨 Use init call for whole user store
Browse files Browse the repository at this point in the history
  • Loading branch information
devmount committed Jan 17, 2025
1 parent e8f9b7e commit 49e3a2a
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 73 deletions.
30 changes: 15 additions & 15 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { useAppointmentStore } from '@/stores/appointment-store';
import { useScheduleStore } from '@/stores/schedule-store';
// component constants
const currentUser = useUserStore();
const user = useUserStore();
const apiUrl = inject(apiUrlKey);
const route = useRoute();
const routeName = typeof route.name === 'string' ? route.name : '';
Expand Down Expand Up @@ -58,13 +58,12 @@ const hasProfanity = (input: string) => profanity.exists(input);
provide(hasProfanityKey, hasProfanity);
// handle auth and fetch
const isAuthenticated = computed(() => currentUser?.authenticated);
const call = createFetch({
baseUrl: apiUrl,
options: {
beforeFetch({ options }) {
if (isAuthenticated.value) {
const token = currentUser.data.accessToken;
if (user?.authenticated) {
const token = user.data.accessToken;
// @ts-ignore
options.headers.Authorization = `Bearer ${token}`;
}
Expand Down Expand Up @@ -94,7 +93,7 @@ const call = createFetch({
);
} else if (response && response.status === 401 && data?.detail?.id === 'INVALID_TOKEN') {
// Clear current user data, and ship them to the login screen!
currentUser.$reset();
user.$reset();
await router.push('/login');
return context;
}
Expand All @@ -109,6 +108,9 @@ const call = createFetch({
},
});
// Initialize API calls for user store
user.init(call);
provide(callKey, call);
provide(isPasswordAuthKey, import.meta.env?.VITE_AUTH_SCHEME === 'password');
provide(isFxaAuthKey, import.meta.env?.VITE_AUTH_SCHEME === 'fxa');
Expand All @@ -125,7 +127,6 @@ const navItems = [
const calendarStore = useCalendarStore();
const appointmentStore = useAppointmentStore();
const scheduleStore = useScheduleStore();
const userStore = useUserStore();
// true if route can be accessed without authentication
const routeIsPublic = computed(
Expand All @@ -140,9 +141,9 @@ const routeHasModal = computed(
// retrieve calendars and appointments after checking login and persisting user to db
const getDbData = async () => {
if (currentUser?.authenticated) {
if (user?.authenticated) {
await Promise.all([
userStore.profile(call),
user.profile(),
calendarStore.fetch(call),
appointmentStore.fetch(call),
scheduleStore.fetch(call),
Expand Down Expand Up @@ -287,9 +288,8 @@ onMounted(async () => {
});
const id = await onPageLoad();
if (isAuthenticated.value) {
const profile = useUserStore();
posthog.identify(profile.data.uniqueHash);
if (user?.authenticated) {
posthog.identify(user.data.uniqueHash);
} else if (id) {
posthog.identify(id);
}
Expand All @@ -300,20 +300,20 @@ onMounted(async () => {

<template>
<!-- authenticated subscriber content -->
<template v-if="router.hasRoute(route.name) && (isAuthenticated || routeIsPublic)">
<template v-if="router.hasRoute(route.name) && (user?.authenticated || routeIsPublic)">
<site-notification
v-if="isAuthenticated && visibleNotification"
v-if="user?.authenticated && visibleNotification"
:title="notificationTitle"
:action-url="notificationActionUrl"
>
{{ notificationMessage }}
</site-notification>
<nav-bar v-if="isAuthenticated" :nav-items="navItems"/>
<nav-bar v-if="user?.authenticated" :nav-items="navItems"/>
<title-bar v-if="routeIsPublic"/>
<main
:class="{
'mx-4 min-h-full py-32 lg:mx-8': !routeIsHome && !routeIsPublic,
'!pt-24': routeIsHome || isAuthenticated,
'!pt-24': routeIsHome || user?.authenticated,
'min-h-full': routeIsPublic && !routeHasModal,
}"
>
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/FTUE/Finish.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const call = inject(callKey);
const scheduleStore = useScheduleStore();
const userStore = useUserStore();
userStore.init(call);
const ftueStore = useFTUEStore();
const { nextStep } = ftueStore;
Expand All @@ -25,7 +26,7 @@ const myLinkShow = ref(false);
onMounted(async () => {
await Promise.all([
scheduleStore.fetch(call),
userStore.profile(call),
userStore.profile(),
]);
myLink.value = userStore.myLink;
});
Expand All @@ -34,8 +35,8 @@ const onSubmit = async () => {
isLoading.value = true;
// Can't run async together!
await userStore.finishFTUE(call);
await userStore.profile(call);
await userStore.finishFTUE();
await userStore.profile();
// Clear the FTUE flow
window.localStorage?.removeItem('tba/ftue');
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/FTUE/SetupProfile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ftueStore = useFTUEStore();
const { hasNextStep } = storeToRefs(ftueStore);
const { nextStep } = ftueStore;
const user = useUserStore();
user.init(call);
// @ts-ignore
// See https://github.com/microsoft/TypeScript/issues/49231
Expand Down Expand Up @@ -57,7 +58,7 @@ const onSubmit = async () => {
return;
}
const response = await user.updateUser(call, {
const response = await user.updateUser({
name: fullName.value,
username: username.value,
timezone: timezone.value,
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/components/SettingsAccount.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ const { t } = useI18n({ useScope: 'global' });
const call = inject(callKey);
const hasProfanity = inject(hasProfanityKey);
const router = useRouter();
const user = useUserStore();
const schedule = useScheduleStore();
const externalConnectionsStore = useExternalConnectionsStore();
const user = useUserStore();
user.init(call);
const activeUsername = ref(user.data.username);
const activeDisplayName = ref(user.data.name);
Expand Down Expand Up @@ -65,7 +66,7 @@ const getAvailableEmails = async () => {
};
const refreshData = async () => Promise.all([
user.profile(call),
user.profile(),
schedule.fetch(call, true),
externalConnectionsStore.fetch(call),
getAvailableEmails(),
Expand All @@ -87,7 +88,7 @@ const updateUser = async () => {
if (!error.value) {
// update user in store
user.updateProfile(data.value);
await user.updateSignedUrl(call);
await user.updateSignedUrl();
errorUsername.value = null;
errorDisplayName.value = null;
// TODO show some confirmation
Expand Down Expand Up @@ -166,7 +167,7 @@ const refreshLink = async () => {
};
const refreshLinkConfirm = async () => {
await user.changeSignedUrl(call);
await user.changeSignedUrl();
await refreshData();
closeModals();
Expand Down
65 changes: 31 additions & 34 deletions frontend/src/stores/user-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const useUserStore = defineStore('user', () => {
/**
* Initialize store with data required at runtime
*
* @param fetch Function to perform API calls
* @param fetch preconfigured function to perform API calls
*/
const init = (fetch: Fetch) => {
call.value = fetch;
Expand All @@ -59,13 +59,6 @@ export const useUserStore = defineStore('user', () => {
};
}

// Make sure settings are saved directly when changed
watch(
() => data.value.settings,
() => updateSettings(),
{ deep: true }
);

/**
* Update user settings only
*/
Expand Down Expand Up @@ -95,7 +88,7 @@ export const useUserStore = defineStore('user', () => {
return scheduleLinks[0];
}

console.warn('Signed urls are deprecated here!');
// TODO: Signed urls are deprecated here!
return data.value.signedUrl;
});

Expand Down Expand Up @@ -159,10 +152,9 @@ export const useUserStore = defineStore('user', () => {

/**
* Retrieve the current signed url and update store
* @param call preconfigured API fetch function
*/
const updateSignedUrl = async (call: Fetch): Promise<Error> => {
const { error, data: sigData }: SignatureResponse = await call('me/signature').get().json();
const updateSignedUrl = async (): Promise<Error> => {
const { error, data: sigData }: SignatureResponse = await call.value('me/signature').get().json();

if (error.value || !sigData.value?.url) {
return { error: sigData.value ?? error.value };
Expand All @@ -175,16 +167,15 @@ export const useUserStore = defineStore('user', () => {

/**
* Retrieve the current signed url and update store
* @param call preconfigured API fetch function
* @param inputData Subscriber data to throw into the db
*/
const updateUser = async (call: Fetch, inputData: Subscriber) => {
const { error, data: userData }: SubscriberResponse = await call('me').put(inputData).json();
const updateUser = async (inputData: Subscriber) => {
const { error, data: userData }: SubscriberResponse = await call.value('me').put(inputData).json();

if (!error.value) {
// update user in store
updateProfile(userData.value);
await updateSignedUrl(call);
await updateSignedUrl();

return { error: false };
}
Expand All @@ -194,16 +185,14 @@ export const useUserStore = defineStore('user', () => {

/**
* Retrieve the current signed url and update store
* @param call preconfigured API fetch function
*/
const finishFTUE = async (call: Fetch) => call('subscriber/setup').post().json();
const finishFTUE = async () => call.value('subscriber/setup').post().json();

/**
* Update store with profile data from db
* @param call preconfigured API fetch function
*/
const profile = async (call: Fetch): Promise<Error> => {
const { error, data: userData }: SubscriberResponse = await call('me').get().json();
const profile = async (): Promise<Error> => {
const { error, data: userData }: SubscriberResponse = await call.value('me').get().json();

// Type error means they refreshed midway through the request. Don't log them out for this!
if (error.value instanceof TypeError) {
Expand All @@ -218,38 +207,36 @@ export const useUserStore = defineStore('user', () => {

updateProfile(userData.value);

return updateSignedUrl(call);
return updateSignedUrl();
};

/**
* Invalidate the current signed url and replace it with a new one
* @param call preconfigured API fetch function
*/
const changeSignedUrl = async (call: Fetch): Promise<Error> => {
const { error, data: sigData }: BooleanResponse = await call('me/signature').post().json();
const changeSignedUrl = async (): Promise<Error> => {
const { error, data: sigData }: BooleanResponse = await call.value('me/signature').post().json();

if (error.value) {
return { error: sigData.value ?? error.value };
}

return updateSignedUrl(call);
return updateSignedUrl();
};

/**
* Request subscriber login
* @param call preconfigured API fetch function
* @param username
* @param password or null if fxa authentication
*/
const login = async (call: Fetch, username: string, password: string|null): Promise<Error> => {
const login = async (username: string, password: string|null): Promise<Error> => {
$reset();

if (import.meta.env.VITE_AUTH_SCHEME === 'password') {
// fastapi wants us to send this as formdata :|
const formData = new FormData(document.createElement('form'));
formData.set('username', username);
formData.set('password', password);
const { error, data: tokenData }: TokenResponse = await call('token').post(formData).json();
const { error, data: tokenData }: TokenResponse = await call.value('token').post(formData).json();

Check failure on line 239 in frontend/src/stores/user-store.ts

View workflow job for this annotation

GitHub Actions / validate-frontend

test/stores/user-store.test.js > User Store > login fails

TypeError: call.value is not a function ❯ Proxy.login src/stores/user-store.ts:239:68 ❯ Proxy.wrappedAction node_modules/pinia/dist/pinia.mjs:1405:26 ❯ test/stores/user-store.test.js:134:33

Check failure on line 239 in frontend/src/stores/user-store.ts

View workflow job for this annotation

GitHub Actions / validate-frontend

test/stores/user-store.test.js > User Store > login successful

TypeError: call.value is not a function ❯ Proxy.login src/stores/user-store.ts:239:68 ❯ Proxy.wrappedAction node_modules/pinia/dist/pinia.mjs:1405:26 ❯ test/stores/user-store.test.js:143:33

if (error.value || !tokenData.value.access_token) {
return { error: tokenData.value ?? error.value };
Expand All @@ -259,7 +246,7 @@ export const useUserStore = defineStore('user', () => {
} else if (import.meta.env.VITE_AUTH_SCHEME === 'fxa') {
// We get a one-time token back from the api, use it to fetch the real access token
data.value.accessToken = username;
const { error, data: tokenData }: TokenResponse = await call('fxa-token').post().json();
const { error, data: tokenData }: TokenResponse = await call.value('fxa-token').post().json();

if (error.value || !tokenData.value.access_token) {
return { error: tokenData.value ?? error.value };
Expand All @@ -270,15 +257,14 @@ export const useUserStore = defineStore('user', () => {
return { error: i18n.t('error.loginMethodNotSupported') };
}

return profile(call);
return profile();
};

/**
* Do subscriber logout and reset store
* @param call preconfigured API fetch function
*/
const logout = async (call: Fetch) => {
const { error }: BooleanResponse = await call('logout').get().json();
const logout = async () => {
const { error }: BooleanResponse = await call.value('logout').get().json();

if (error.value) {
// TODO: show error message
Expand All @@ -288,6 +274,17 @@ export const useUserStore = defineStore('user', () => {
$reset();
};

// Make sure settings are saved directly when changed
watch(
() => data.value.settings,
() => {
if (authenticated.value) {
updateSettings();
}
},
{ deep: true }
);

return {
data,
init,
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/views/LoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ import PrimaryButton from '@/tbpro/elements/PrimaryButton.vue';
import WordMark from '@/elements/WordMark.vue';
import { handleFormError } from '@/utils';
// component constants
const user = useUserStore();
// component constants
const { t } = useI18n();
const call = inject(callKey);
Expand All @@ -33,6 +30,9 @@ const route = useRoute();
const router = useRouter();
const isPasswordAuth = inject(isPasswordAuthKey);
const isFxaAuth = inject(isFxaAuthKey);
const user = useUserStore();
user.init(call);
// Don't show the invite code field, only the "Join the waiting list" part
const hideInviteField = ref(false);
const isLoading = ref(false);
Expand Down Expand Up @@ -166,7 +166,7 @@ const login = async () => {
return;
}
const { error }: Error = await user.login(call, email.value, password.value);
const { error }: Error = await user.login(email.value, password.value);
if (error) {
let errObj = error as PydanticException;
if (typeof error === 'string') {
Expand Down
Loading

0 comments on commit 49e3a2a

Please sign in to comment.