feat(login): create dev login bypass for testing, refactor main layout for consistency and more intuitive UI

This commit is contained in:
Nicolas Drolet 2025-08-07 15:40:19 -04:00
parent 7399232ed8
commit 5a4cba5588
29 changed files with 72 additions and 103 deletions

View File

@ -5,7 +5,7 @@ import messages from 'src/i18n';
export type MessageLanguages = keyof typeof messages; export type MessageLanguages = keyof typeof messages;
// Type-define 'en-US' as the master schema for the resource // Type-define 'en-US' as the master schema for the resource
export type MessageSchema = typeof messages['en-ca']; export type MessageSchema = typeof messages['en'];
// See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition // See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition
/* eslint-disable @typescript-eslint/no-empty-object-type */ /* eslint-disable @typescript-eslint/no-empty-object-type */
@ -23,7 +23,7 @@ declare module 'vue-i18n' {
export default defineBoot(({ app }) => { export default defineBoot(({ app }) => {
const i18n = createI18n<{ message: MessageSchema }, MessageLanguages>({ const i18n = createI18n<{ message: MessageSchema }, MessageLanguages>({
locale: 'fr-ca', locale: 'fr',
legacy: false, legacy: false,
messages, messages,
}); });

View File

@ -23,14 +23,13 @@ export default {
clearFilter: 'Clear filter', clearFilter: 'Clear filter',
}, },
navBar: { navBar: {
navItem_1: 'Users list', userMenuEmployeeList: 'Employee list',
navItem_2: 'Shift validations', userMenuShiftValidation: 'Timesheet Approval',
menuItem_1: 'Profile', userMenuProfile: 'Profile',
menuItem_2: 'Help', userMenuHelp: 'Help',
menuItem_3: 'Log Out', userMenuLogout: 'Log Out',
menuItem_4: 'Time Sheet', userMenuTimesheet: 'Timesheet',
menuItem_5: 'Annual calendar', userMenuCalendar: 'Calendar',
mobileIndexTitle: 'Hi',
}, },
notFoundPage: { notFoundPage: {
pageTitle: 'Oops. Nothing here...', pageTitle: 'Oops. Nothing here...',

View File

@ -162,14 +162,13 @@ export default {
clearFilter: 'Effacer le filtre', clearFilter: 'Effacer le filtre',
}, },
navBar: { navBar: {
navItem_1: 'Liste des utilisateurs', userMenuEmployeeList: 'Liste employés',
navItem_2: 'Validations des quarts de travail', userMenuShiftValidation: 'Valider les heures',
menuItem_1: 'Profil', userMenuProfile: 'Profil',
menuItem_2: 'Aide', userMenuHelp: 'Aide',
menuItem_3: 'Déconnexion', userMenuLogout: 'Déconnexion',
menuItem_4: 'Carte de temps', userMenuTimesheet: 'Carte de temps',
menuItem_5: 'Calendrier annuel', userMenuCalendar: 'Calendrier annuel',
mobileIndexTitle: 'Bonjour',
}, },
notFoundPage: { notFoundPage: {
pageTitle: 'Oops. Rien ici...', pageTitle: 'Oops. Rien ici...',

View File

@ -2,6 +2,6 @@ import enCA from './en-ca';
import frCA from './fr-ca'; import frCA from './fr-ca';
export default { export default {
'en-ca': enCA, 'en': enCA,
'fr-ca': frCA, 'fr': frCA,
}; };

View File

@ -2,11 +2,13 @@
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
import HeaderBar from 'src/modules/shared/components/navigation/header-bar.vue'; import HeaderBar from 'src/modules/shared/components/navigation/header-bar.vue';
import FooterBar from 'src/modules/shared/components/navigation/footer-bar.vue'; import FooterBar from 'src/modules/shared/components/navigation/footer-bar.vue';
import RightDrawer from 'src/modules/shared/components/navigation/right-drawer.vue';
</script> </script>
<template> <template>
<q-layout view="hHh lpR fFf"> <q-layout view="hHh lpR fFf">
<HeaderBar /> <HeaderBar />
<RightDrawer />
<q-page-container> <q-page-container>
<router-view class="q-pa-sm" /> <router-view class="q-pa-sm" />
</q-page-container> </q-page-container>

View File

@ -1,48 +0,0 @@
/* eslint-disable */
import { defineStore } from "pinia";
import { User } from "src/modules/users/types/user-interface";
import { AuthState } from "./types/auth-interface";
import { AuthService } from "./services/services-auth";
import { computed, ref } from "vue";
export const useAuthStore = defineStore('auth', () => {
const user = ref ({
firstName: 'Unknown',
lastName: 'Unknown',
email: 'guest@guest.com',
role: 'guest'
} as User);
const error = ref("");
const isAuthorizedUser = computed( () => user.value.role !== 'guest');
const login = () => {
//TODO: manage customer login process
};
const oidcLogin = () => {
const oidcPopup = AuthService.oidcLogin();
if (!oidcPopup) {
error.value = "You have popups blocked on this website!";
}
};
const logout = () => {
return "logout";
};
const setUser = (currentUser: User, isBypass: boolean = false, bypassRole?: string) => {
if (isBypass) {
user.value = {
firstName: "Testing",
lastName: "Tester",
email: "testingT@targointernet.com",
role: bypassRole || "guest"} as User;
} else {
user.value = currentUser;
}
};
return { login, oidcLogin, logout, setUser };
});

View File

@ -1,33 +1,36 @@
import { useAuthStore } from "../auth-store"; import { useAuthStore } from "../../../stores/auth-store";
import type { User } from "src/modules/users/types/user-interface";
export const useAuthApi = () => { export const useAuthApi = () => {
const authStore = useAuthStore(); const authStore = useAuthStore();
const login = () => { const login = () => {
const response = authStore.login(); authStore.login();
return response;
}; };
const oidcLogin = () => { const oidcLogin = () => {
return {status: 200, message: 'sent an openid connect login request'}; authStore.oidcLogin();
}; };
const logout = () => { const logout = () => {
return {status: 200, message: 'sent a logout request'}; authStore.logout();
}; };
const isLoggedIn = () => { const isAuthorizedUser = () => {
return {status: 200, message: 'sent a isLoggedIn request'}; return authStore.isAuthorizedUser;
}; };
const forgotPassword = (email: string) => { const setUser = (currentUser: User) => {
return {status: 200, message: 'sent a password reset request with email ' + email}; authStore.user = currentUser;
}; }
return { return {
login, login,
oidcLogin, oidcLogin,
logout, logout,
isLoggedIn, isAuthorizedUser,
forgotPassword, setUser,
}; };
}; };

View File

@ -1,12 +1,28 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { useAuthStore } from '../auth-store'; import { useAuthApi } from '../composables/use-auth-api';
import type { User } from 'src/modules/users/types/user-interface';
import { useRouter } from 'vue-router';
const authStore = useAuthStore(); const authApi = useAuthApi();
const email = ref(''); const email = ref('');
const isShowingEmployeeLoginButton = ref(false); const isShowingEmployeeLoginButton = ref(false);
const isRemembered = ref(false); const isRemembered = ref(false);
const router = useRouter();
const setBypassUser = (bypassRole: string) => {
authApi.setUser({
firstName: "Testing",
lastName: bypassRole,
email: "testingT@targointernet.com",
role: bypassRole || "guest"
} as User);
router.push({ name: 'dashboard' }).catch( err => {
console.error('Router navigation failed: ', err);
});
}
watch(email, (value) => { watch(email, (value) => {
isShowingEmployeeLoginButton.value = value.includes('@targ'); isShowingEmployeeLoginButton.value = value.includes('@targ');
@ -31,7 +47,7 @@
</div> </div>
</q-card-section> </q-card-section>
<q-form class="q-gutter-sm" @submit="authStore.login"> <q-form class="q-gutter-sm" @submit="authApi.login">
<q-input dense outlined label-color="primary" v-model="email" :label="$t('loginPage.email')" /> <q-input dense outlined label-color="primary" v-model="email" :label="$t('loginPage.email')" />
<q-card-section class="q-ma-none q-pa-none"> <q-card-section class="q-ma-none q-pa-none">
@ -61,7 +77,7 @@
<q-slide-transition> <q-slide-transition>
<div v-if="isShowingEmployeeLoginButton"> <div v-if="isShowingEmployeeLoginButton">
<transition slow enter-active-class="animated zoomIn" leave-active-class="animated zoomOut"> <transition slow enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
<q-btn rounded push color="primary" @click="authStore.oidcLogin" :label="$t('loginPage.employeeLoginButton')" class="full-width row" icon="img:src/assets/logo-targo-simple.svg" /> <q-btn rounded push color="primary" @click="authApi.oidcLogin" :label="$t('loginPage.employeeLoginButton')" class="full-width row" icon="img:src/assets/logo-targo-simple.svg" />
</transition> </transition>
</div> </div>
</q-slide-transition> </q-slide-transition>
@ -76,10 +92,10 @@
<q-separator color="primary" /> <q-separator color="primary" />
<q-card-section> <q-card-section>
<q-btn-group push rounded> <q-btn-group push rounded>
<q-btn push color="primary" text-color="white" label="ACCOUNTING" icon="attach_money" /> <q-btn push color="primary" text-color="white" label="ACCOUNTING" icon="attach_money" @click="setBypassUser('accounting')"/>
<q-btn push color="primary" text-color="white" label="SUPERVISOR" icon="supervisor_account"/> <q-btn push color="primary" text-color="white" label="SUPERVISOR" icon="supervisor_account" @click="setBypassUser('supervisor')"/>
<q-btn push color="primary" text-color="white" label="HR" icon="diversity_3"/> <q-btn push color="primary" text-color="white" label="HR" icon="diversity_3" @click="setBypassUser('human resources')"/>
<q-btn push color="primary" text-color="white" label="EMPLOYEE" icon="support_agent"/> <q-btn push color="primary" text-color="white" label="EMPLOYEE" icon="support_agent" @click="setBypassUser('employee')"/>
</q-btn-group> </q-btn-group>
</q-card-section> </q-card-section>

View File

@ -3,15 +3,18 @@ import { api } from 'src/boot/axios';
export const AuthService = { export const AuthService = {
// Will likely be deprecated and relegated to Authentik // Will likely be deprecated and relegated to Authentik
login: (credentials: { email: string; password: string }) => { login: () => {
// TODO: possibly add some kind of login logic, but will most likely be redirected //TODO: OIDC customer sign-in, eventually
// to Authentik as well.
api.post('/auth/login', credentials)
}, },
oidcLogin: () => { oidcLogin: (): Window | null => {
// TODO: OIDC login logic window.addEventListener('message', (event) => {
api.post('/auth/oidclogin'); if (event.data.type === 'authSuccess') {
//some kind of logic here to set user in store
}
})
return window.open('http://localhost:3000/auth/v1/login', 'authPopup', 'width=600,height=800');
}, },
logout: () => { logout: () => {

View File

@ -1,6 +0,0 @@
export interface User {
firstName: string;
lastName: string;
email: string;
role: string;
}

View File

@ -1,7 +1,7 @@
import { defineRouter } from '#q-app/wrappers'; import { defineRouter } from '#q-app/wrappers';
import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory, } from 'vue-router'; import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory, } from 'vue-router';
import routes from './routes'; import routes from './routes';
import { useAuthStore } from 'src/modules/auth/auth-store'; import { useAuthStore } from 'src/stores/auth-store';
/* /*
* If not building with SSR mode, you can * If not building with SSR mode, you can
@ -30,7 +30,7 @@ export default defineRouter(function (/* { store, ssrContext } */) {
Router.beforeEach((destinationPage) => { Router.beforeEach((destinationPage) => {
const authStore = useAuthStore(); const authStore = useAuthStore();
if (destinationPage.meta.requiresAuth && !authStore.isAuthorizedUser()) { if (destinationPage.meta.requiresAuth && !authStore.isAuthorizedUser) {
console.log("access denied!") console.log("access denied!")
return { name: 'login' }; return { name: 'login' };
} }

View File

@ -8,6 +8,7 @@ const routes: RouteRecordRaw[] = [
children: [ children: [
{ {
path: '', path: '',
name: 'dashboard',
component: () => import('src/pages/test-page.vue'), component: () => import('src/pages/test-page.vue'),
}, },
], ],