Skip to content

Commit

Permalink
Fix login username label and show error messages (#303)
Browse files Browse the repository at this point in the history
* 🔨 fix login label and show error messages

* 💚 give alert box some space to breathe

* 🔨 fix using i18n in stores
  • Loading branch information
devmount authored Mar 12, 2024
1 parent d5649da commit 3d9288e
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 41 deletions.
22 changes: 22 additions & 0 deletions frontend/src/composables/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// init localization
import { createI18n } from 'vue-i18n';

// language source files
import de from '@/locales/de.json';
import en from '@/locales/en.json';

const messages = {
de, // German
en, // English
};
const loc = localStorage?.getItem('locale') ?? (navigator.language || navigator.userLanguage);
const instance = createI18n({
legacy: false,
globalInjection: true,
locale: loc,
fallbackLocale: 'en',
messages,
});

export default instance;
export const i18n = instance.global;
4 changes: 3 additions & 1 deletion frontend/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
},
"error": {
"actionNeeded": "Aktion erforderlich!",
"credentialsIncomplete": "Bitte gib deine Zugangsdaten ein.",
"googleRefreshError": "Es gab ein Problem mit Google, bitte erneut verbinden.",
"loginMethodNotSupported": "Login-Methode wird nicht unterstützt. Bitte nochmal versuchen.",
"minimumValue": "{field} sollte wenigstens {value} sein.",
"noConnectedCalendars": "Kalendereinrichtung zum Fortfahren erforderlich. {0}.",
"noConnectedCalendarsLink": "Zu den Einstellungen wechseln.",
"unknownAppointmentError": "Es gab ein Problem beim Erstellen des Termins. Bitte versuche es noch einmal.",
"unknownAppointmentError": "Es gab ein Problem beim Erstellen des Termins. Bitte nochmal versuchen.",
"unknownScheduleError": "Es gab ein Problem beim Erstellen des Zeitplans.",
"usernameIsNotAvailable": "Dieser Benutzername ist nicht verfügbar."
},
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
},
"error": {
"actionNeeded": "Action needed",
"credentialsIncomplete": "Please provide login credentials.",
"googleRefreshError": "Error connecting with Google API, please re-connect.",
"loginMethodNotSupported": "Login method not supported. Please try again.",
"minimumValue": "{field} should be at least {value}.",
"noConnectedCalendars": "Calendar setup required. Connect a calendar to continue. {0}.",
"noConnectedCalendarsLink": "Go to settings",
Expand Down
22 changes: 3 additions & 19 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,9 @@ import { createPinia } from 'pinia';
// init router
import router from '@/router';

// init localization
import { createI18n } from 'vue-i18n';

// language source files
import de from '@/locales/de.json';
import en from '@/locales/en.json';

// init composables
import { useDayJS } from '@/composables/dayjs';
import i18ninstance from '@/composables/i18n';

// init basic css with tailwind imports
import '@/assets/main.css';
Expand Down Expand Up @@ -63,19 +58,8 @@ const apiUrl = `${protocol}://${import.meta.env.VITE_API_URL}${port}`;
app.provide('apiUrl', apiUrl);
app.provide('bookingUrl', `${protocol}://${import.meta.env.VITE_BASE_URL}/booking/`);

const messages = {
de, // German
en, // English
};
const loc = localStorage?.getItem('locale') ?? (navigator.language || navigator.userLanguage);
const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: loc,
fallbackLocale: 'en',
messages,
});
app.use(i18n);
app.use(i18ninstance);
useDayJS(app, loc);

// ready? let's go!
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/stores/user-store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { defineStore } from 'pinia';
import { useLocalStorage } from '@vueuse/core';
import { useI18n } from 'vue-i18n';
import { i18n } from '@/composables/i18n';

const initialUserObject = {
email: null,
Expand Down Expand Up @@ -29,13 +31,12 @@ export const useUserStore = defineStore('user', () => {
const { error, data: sigData } = await fetch('me/signature').get().json();

if (error.value || !sigData.value?.url) {
console.error(error.value, sigData.value);
return false;
return { error: sigData.value?.detail ?? error.value };
}

data.value.signedUrl = sigData.value.url;

return true;
return { error: false };
};

/**
Expand All @@ -49,7 +50,7 @@ export const useUserStore = defineStore('user', () => {
// Failed to get profile data, log this user out and return false
if (error.value || !userData.value) {
$reset();
return false;
return { error: userData.value?.detail ?? error.value };
}

data.value = {
Expand All @@ -73,11 +74,10 @@ export const useUserStore = defineStore('user', () => {
* @return {boolean}
*/
const changeSignedUrl = async (fetch) => {
const { error, data: sigData } = await fetch('me/signature').post().json();
const { error, data } = await fetch('me/signature').post().json();

if (error.value) {
console.error(error.value, sigData.value);
return false;
return { error: data.value?.detail ?? error.value };
}

return updateSignedUrl(fetch);
Expand All @@ -101,15 +101,15 @@ export const useUserStore = defineStore('user', () => {
const { error, data: tokenData } = await fetch('token').post(formData).json();

if (error.value || !tokenData.value.access_token) {
return false;
return { error: tokenData.value?.detail ?? error.value };
}

data.value.accessToken = tokenData.value.access_token;
} else if (import.meta.env.VITE_AUTH_SCHEME === 'fxa') {
// For FXA we re-use the username parameter as our access token
data.value.accessToken = username;
} else {
return false;
return { error: i18n.t('error.loginMethodNotSupported') };
}

return profile(fetch);
Expand Down
22 changes: 14 additions & 8 deletions frontend/src/views/LoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
<div class="my-auto flex w-1/2 max-w-lg flex-col items-center justify-center gap-2 bg-white px-4 py-12 shadow-lg dark:bg-gray-700">
<img class="mb-2 w-full max-w-[8rem]" src="/appointment_logo.svg" alt="Appointment Logo" />
<div class="text-center text-4xl font-light">{{ t('app.title') }}</div>
<alert-box v-if="loginError" @close="loginError = null" class="mt-4">
{{ loginError }}
</alert-box>
<div
class="my-8 grid w-full"
:class="{'gap-8': isPasswordAuth, 'grid-rows-2': isPasswordAuth, 'gap-4': isFxaAuth}"
>
<label class="mt-4 flex items-center pl-4">
<span class="w-full" :class="{'max-w-[4em]': isFxaAuth, 'max-w-[6rem]': isPasswordAuth}">
{{ t('label.email') }}
{{ isPasswordAuth ? t('label.username') : t('label.email') }}
</span>
<input
v-model="username"
Expand All @@ -37,13 +40,12 @@
</template>

<script setup>
import {
inject, ref,
} from 'vue';
import { inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user-store';
import PrimaryButton from '@/elements/PrimaryButton';
import AlertBox from '@/elements/AlertBox';
// component constants
const user = useUserStore();
Expand All @@ -56,13 +58,15 @@ const router = useRouter();
const isPasswordAuth = inject('isPasswordAuth');
const isFxaAuth = inject('isFxaAuth');
// list of pending appointments
// form input and error
const username = ref('');
const password = ref('');
const loginError = ref(null);
// do log out
const login = async () => {
if (!username.value) {
if (!username.value || (isPasswordAuth && !password.value)) {
loginError.value = t('error.credentialsIncomplete');
return;
}
Expand All @@ -75,18 +79,20 @@ const login = async () => {
const { url } = data.value;
if (error.value) {
loginError.value = error.value;
return;
}
window.location = url;
return;
}
if (!password.value) {
const { error } = await user.login(call, username.value, password.value);
if (error) {
loginError.value = error;
return;
}
await user.login(call, username.value, password.value);
await router.push('/calendar');
};
</script>
4 changes: 1 addition & 3 deletions frontend/src/views/PostLoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
</template>

<script setup>
import {
inject, computed, onMounted,
} from 'vue';
import { inject, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user-store';
Expand Down
2 changes: 1 addition & 1 deletion frontend/test/stores/user-store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe('User Store', () => {
},
}), TEST_USERNAME, `${TEST_PASSWORD}`);

expect(response).toBe(true);
expect(response.error).toBe(false);
expect(user.exists()).toBe(true);
expect(user.data.accessToken).toBeTruthy();
expect(user.data.username).toBeTruthy();
Expand Down

0 comments on commit 3d9288e

Please sign in to comment.