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

Replace existing sign-out method with query mutation #835

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
104 changes: 54 additions & 50 deletions src/components/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
<div class="flex align-items-center justify-content-center w-full">
<PvMenubar :model="computedItems" class="w-full">
<template #start>
<router-link :to="{ name: 'Home' }">
<router-link :to="{ path: APP_ROUTES.HOME }">
<div class="navbar-logo mx-3">
<PvImage v-if="isLevante" src="/LEVANTE/Levante_Logo.png" alt="LEVANTE Logo" width="200" />
<ROARLogo v-else />
</div>
</router-link>
</template>

<template #menubuttonicon>
<PvButton
icon="pi pi-bars mr-2"
Expand All @@ -20,39 +21,40 @@
@click="toggleMenu"
/>
</template>

<template #end>
<div class="flex gap-2 align-items-center justify-content-center mr-3">
<div v-if="isWideScreen" class="nav-user-wrapper flex align-items-center gap-2 bg-gray-100">
<div class="text-lg font-bold text-gray-600" data-cy="user-display-name">
{{ userDisplayName }}
</div>
<router-link :to="{ name: 'SignOut' }" class="signout-button">
<PvButton
text
data-cy="button-sign-out"
class="no-underline h-2 p-1 m-0 text-primary border-none border-round h-2rem text-sm hover:bg-red-900 hover:text-white"
>{{ $t('navBar.signOut') }}
</PvButton>
</router-link>
</div>
<div v-else>
<router-link :to="{ name: 'SignOut' }" class="signout-button">
<PvButton
data-cy="button-sign-out"
class="no-underline m-0 bg-primary text-white border-none border-round h-2rem text-sm hover:bg-red-900"
>{{ $t('navBar.signOut') }}</PvButton
>
</router-link>
<PvButton
text
data-cy="button-sign-out"
class="no-underline h-2 p-1 m-0 text-primary border-none border-round h-2rem text-sm hover:bg-red-900 hover:text-white"
@click="signOut"
>{{ $t('navBar.signOut') }}
</PvButton>
</div>

<PvButton
v-else
data-cy="button-sign-out"
class="no-underline m-0 bg-primary text-white border-none border-round h-2rem text-sm hover:bg-red-900"
@click="signOut"
>{{ $t('navBar.signOut') }}
</PvButton>

<div v-if="authStore.isUserAdmin" class="nav-user-wrapper bg-gray-100">
<router-link :to="{ name: 'ProfileInfo' }"
><button
<router-link :to="{ path: APP_ROUTES.ACCOUNT_PROFILE }">
<button
data-cy="button-profile-info"
class="no-underline p-1 m-0 text-primary border-none border-round cursor-pointer h-2rem w-2rem text-sm hover:bg-red-900 hover:text-white"
>
<i class="pi pi-cog"></i></button
></router-link>
</div>

<div class="my-2">
<LanguageSelector />
</div>
Expand All @@ -69,22 +71,28 @@
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useAuthStore } from '@/store/auth';
import _isEmpty from 'lodash/isEmpty';
import _union from 'lodash/union';
import _get from 'lodash/get';
import { useAuthStore } from '@/store/auth';
import { getSidebarActions } from '@/router/sidebarActions';
import useUserClaimsQuery from '@/composables/queries/useUserClaimsQuery';
import ROARLogo from '@/assets/RoarLogo.vue';
import useSignOutMutation from '@/composables/mutations/useSignOutMutation';
import LanguageSelector from './LanguageSelector.vue';
import { APP_ROUTES } from '@/constants/routes';
import ROARLogo from '@/assets/RoarLogo.vue';

const router = useRouter();
const authStore = useAuthStore();
const { roarfirekit } = storeToRefs(authStore);

const initialized = ref(false);
const menu = ref();
const screenWidth = ref(window.innerWidth);

const isLevante = import.meta.env.MODE === 'LEVANTE';

const { mutate: signOut } = useSignOutMutation();

let unsubscribe;

const init = () => {
Expand All @@ -96,6 +104,12 @@ unsubscribe = authStore.$subscribe(async (mutation, state) => {
if (state.roarfirekit.restConfig) init();
});

// @TODO: Replace screen-size handlers with Tailwind/CSS media queries. Currently not possible due to an outdated
// PrimeVue and Tailwind version. If we cannot update PrimeVue/Tailwind, we should throttle the resize events.
const isWideScreen = computed(() => {
return screenWidth.value > 728;
});

const handleResize = () => {
screenWidth.value = window.innerWidth;
};
Expand All @@ -113,10 +127,6 @@ const { isLoading: isLoadingClaims, data: userClaims } = useUserClaimsQuery({
enabled: initialized,
});

const isWideScreen = computed(() => {
return screenWidth.value > 728;
});

const computedItems = computed(() => {
const items = [];
const headers = ['Administrations', 'Organizations', 'Users'];
Expand Down Expand Up @@ -145,20 +155,21 @@ const computedItems = computed(() => {
const userDisplayName = computed(() => {
if (!isLoadingClaims) {
return '';
} else {
let email = authStore?.userData?.email;
if (email && email.split('@')[1] === 'roar-auth.com') {
email = email.split('@')[0];
}
const displayName = authStore?.userData?.displayName;
const username = authStore?.userData?.username;
const firstName = authStore?.userData?.name?.first;
if (isAdmin.value === true) {
return 'Hi, ' + (displayName || username || email || 'Admin') + '!';
} else {
return 'Hi, ' + (firstName || displayName || username || email || 'User') + '! 👋';
}
}

let email = authStore?.userData?.email;
if (email && email.split('@')[1] === 'roar-auth.com') {
email = email.split('@')[0];
}
const displayName = authStore?.userData?.displayName;
const username = authStore?.userData?.username;
const firstName = authStore?.userData?.name?.first;

if (isAdmin.value) {
return 'Hi, ' + (displayName || username || email || 'Admin') + '!';
}

return 'Hi, ' + (firstName || displayName || username || email || 'User') + '! 👋';
});

const isAdmin = computed(() => {
Expand Down Expand Up @@ -186,19 +197,17 @@ let dropdownItems = ref([
label: authStore.isAuthenticated ? 'Home' : 'Log in',
icon: authStore.isAuthenticated ? 'pi pi-user' : 'pi pi-sign-in',
command: () => {
authStore.isAuthenticated ? router.push({ name: 'Home' }) : router.push({ name: 'SignIn' });
authStore.isAuthenticated ? router.push({ path: APP_ROUTES.HOME }) : router.push({ path: APP_ROUTES.SIGN_IN });
},
},
{
label: 'Sign Out',
icon: 'pi pi-sign-out',
command: () => {
router.push({ name: 'SignOut' });
},
command: () => signOut(),
},
]);

if (authStore.isAuthenticated && _get(roarfirekit.value, 'userData.userType') === 'admin') {
if (authStore.isAuthenticated && roarfirekit.value?.userData?.userType === 'admin') {
dropdownItems.value.splice(
1,
0,
Expand Down Expand Up @@ -236,11 +245,6 @@ nav {
min-width: 100%;
}

.signout-button {
font-size: 0.6rem !important;
font-weight: 500;
}

.nav-user-wrapper {
display: flex;
align-items: center;
Expand Down
45 changes: 45 additions & 0 deletions src/composables/mutations/useSignOutMutation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useMutation, useQueryClient } from '@tanstack/vue-query';
import { useRouter } from 'vue-router';
import * as Sentry from '@sentry/vue';
import { useAuthStore } from '@/store/auth';
import { SIGN_OUT_MUTATION_KEY } from '@/constants/mutationKeys';
import { APP_ROUTES } from '@/constants/routes';

/**
* Sign-Out mutation.
*
* @returns {Object} The mutation object returned by `useMutation`.
*/
const useSignOutMutation = () => {
const authStore = useAuthStore();
const router = useRouter();
const queryClient = useQueryClient();

return useMutation({
mutationKey: SIGN_OUT_MUTATION_KEY,
mutationFn: async () => {
await authStore.roarfirekit.signOut();
},
onSuccess: async () => {
// Cancel all actively fetching queries.
await queryClient.cancelQueries();

// Reset store and delete persisted data. Persisted data should be cleared via the $reset but to be safe, we also
// remove it manually from sessionStorage to prevent any issues.
authStore.$reset();
sessionStorage.removeItem('authStore');
sessionStorage.removeItem('gameStore');

// Clear the query client to remove all cached data.
queryClient.clear();

// Redirect to sign-in page.
router.push({ path: APP_ROUTES.SIGN_IN });
},
onError: (err) => {
Sentry.captureException(err);
},
});
};

export default useSignOutMutation;
4 changes: 2 additions & 2 deletions src/composables/queries/useUserClaimsQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
*/
const useUserClaimsQuery = (queryOptions = undefined) => {
const authStore = useAuthStore();
const { uid, userQueryKeyIndex } = storeToRefs(authStore);
const { uid } = storeToRefs(authStore);

const queryConditions = [() => !!uid.value];
const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);

return useQuery({
queryKey: [USER_CLAIMS_QUERY_KEY, uid, userQueryKeyIndex],
queryKey: [USER_CLAIMS_QUERY_KEY, uid],
queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USER_CLAIMS, uid),
enabled: isQueryEnabled,
...options,
Expand Down
16 changes: 4 additions & 12 deletions src/composables/queries/useUserClaimsQuery.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ describe('useUserClaimsQuery', () => {

it('should call query with correct parameters', () => {
const mockUserId = ref(nanoid());
const mockUserQueryKeyIndex = ref(1);

const authStore = useAuthStore(piniaInstance);
authStore.uid = mockUserId;
authStore.userQueryKeyIndex = mockUserQueryKeyIndex;

vi.spyOn(VueQuery, 'useQuery');

Expand All @@ -48,7 +46,7 @@ describe('useUserClaimsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryKey: ['user-claims', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
Expand All @@ -60,11 +58,9 @@ describe('useUserClaimsQuery', () => {

it('should correctly control the enabled state of the query', async () => {
const mockUserId = ref(nanoid());
const mockUserQueryKeyIndex = ref(5);

const authStore = useAuthStore(piniaInstance);
authStore.uid = mockUserId;
authStore.userQueryKeyIndex = mockUserQueryKeyIndex;

const enableQuery = ref(false);

Expand All @@ -77,7 +73,7 @@ describe('useUserClaimsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryKey: ['user-claims', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
Expand All @@ -95,11 +91,9 @@ describe('useUserClaimsQuery', () => {

it('should only fetch data if once uid is available', async () => {
const mockUserId = ref(null);
const mockUserQueryKeyIndex = ref(5);

const authStore = useAuthStore(piniaInstance);
authStore.uid = mockUserId;
authStore.userQueryKeyIndex = mockUserQueryKeyIndex;

const queryOptions = { enabled: true };

Expand All @@ -108,7 +102,7 @@ describe('useUserClaimsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryKey: ['user-claims', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
Expand All @@ -126,11 +120,9 @@ describe('useUserClaimsQuery', () => {

it('should not let queryOptions override the internally computed value', async () => {
const mockUserId = ref(null);
const mockUserQueryKeyIndex = ref(5);

const authStore = useAuthStore(piniaInstance);
authStore.uid = mockUserId;
authStore.userQueryKeyIndex = mockUserQueryKeyIndex;

const queryOptions = { enabled: true };

Expand All @@ -139,7 +131,7 @@ describe('useUserClaimsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryKey: ['user-claims', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
Expand Down
4 changes: 2 additions & 2 deletions src/composables/queries/useUserDataQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import { computed } from 'vue';
*/
const useUserDataQuery = (userId = undefined, queryOptions = undefined) => {
const authStore = useAuthStore();
const { roarUid, userQueryKeyIndex } = storeToRefs(authStore);
const { roarUid } = storeToRefs(authStore);

const uid = computed(() => userId || roarUid.value);
const queryConditions = [() => !!uid.value];
const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);

return useQuery({
queryKey: [USER_DATA_QUERY_KEY, uid, userQueryKeyIndex],
queryKey: [USER_DATA_QUERY_KEY, uid],
queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USERS, uid),
enabled: isQueryEnabled,
...options,
Expand Down
Loading
Loading