Clean up toasts.

This commit is contained in:
Andrew Lalis 2023-03-27 18:57:31 +02:00
parent 2e1f09c5a6
commit e275c5a044
10 changed files with 90 additions and 62 deletions

View File

@ -10,8 +10,6 @@
const { configure } = require('quasar/wrappers'); const { configure } = require('quasar/wrappers');
const path = require('path'); const path = require('path');
const { withCtx } = require('vue');
// Load environment variables from different files depending on if we're in development. // Load environment variables from different files depending on if we're in development.
let envPath = '.env.production'; let envPath = '.env.production';
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
@ -22,7 +20,7 @@ if (result.error) {
throw result.error; throw result.error;
} }
module.exports = configure(function (ctx) { module.exports = configure(function () {
return { return {
eslint: { eslint: {
// fix: true, // fix: true,
@ -123,7 +121,7 @@ module.exports = configure(function (ctx) {
// directives: [], // directives: [],
// Quasar plugins // Quasar plugins
plugins: ['Notify'], plugins: ['Notify', 'Dialog'],
}, },
// animations: 'all', // --- includes all animations // animations: 'all', // --- includes all animations

View File

@ -1,3 +1,7 @@
<!--
An item that's meant for the main UI menu, and features a dropdown with some
account-related actions.
-->
<template> <template>
<div class="q-mx-sm"> <div class="q-mx-sm">
<q-btn-dropdown <q-btn-dropdown

View File

@ -52,6 +52,7 @@ export default {
}, },
accountPrivate: 'This account is private.', accountPrivate: 'This account is private.',
recentLifts: 'Recent Lifts', recentLifts: 'Recent Lifts',
requestedToFollow: 'Requested to follow this user.',
}, },
userSearchPage: { userSearchPage: {
searchHint: 'Search for a user' searchHint: 'Search for a user'
@ -83,6 +84,11 @@ export default {
save: 'Save', save: 'Save',
undo: 'Undo' undo: 'Undo'
}, },
submissionPage: {
confirmDeletion: 'Confirm Deletion',
confirmDeletionMsg: 'Are you sure you want to delete this submission? It will be removed permanently.',
deletionSuccessful: 'Submission deleted successfully.'
},
accountMenuItem: { accountMenuItem: {
logIn: 'Login', logIn: 'Login',
profile: 'Profile', profile: 'Profile',

View File

@ -47,6 +47,7 @@ export default {
}, },
accountPrivate: 'Dit account is privaat.', accountPrivate: 'Dit account is privaat.',
recentLifts: 'Recente liften', recentLifts: 'Recente liften',
requestedToFollow: 'Gevraagd om deze gebruiker te volgen.',
}, },
userSearchPage: { userSearchPage: {
searchHint: 'Zoek een gebruiker' searchHint: 'Zoek een gebruiker'
@ -78,6 +79,11 @@ export default {
save: 'Opslaan', save: 'Opslaan',
undo: 'Terugzetten' undo: 'Terugzetten'
}, },
submissionPage: {
confirmDeletion: 'Bevestig verwijderen',
confirmDeletionMsg: 'Ben je zeker dat je dit submissie willen verwijderen? Het zal permanent verwijderd worden.',
deletionSuccessful: 'Submissie succesvol verwijderd.'
},
accountMenuItem: { accountMenuItem: {
logIn: 'Inloggen', logIn: 'Inloggen',
profile: 'Profile', profile: 'Profile',

View File

@ -63,16 +63,30 @@ onMounted(async () => {
} }
}); });
/**
* Shows a confirmation dialog asking the user if they really want to delete
* their submission, and if they say okay, go ahead and delete it, and bring
* the user back to their home page that shows all their lifts.
*/
async function deleteSubmission() { async function deleteSubmission() {
// TODO: Confirm via a dialog or something before deleting. quasar.dialog({
if (!submission.value) return; title: i18n.t('submissionPage.confirmDeletion'),
try { message: i18n.t('submissionPage.confirmDeletionMsg'),
await api.gyms.submissions.deleteSubmission(submission.value.id, authStore); cancel: true
await router.push('/'); }).onOk(async () => {
} catch (error) { if (!submission.value) return;
console.error(error); try {
showApiErrorToast(i18n, quasar); await api.gyms.submissions.deleteSubmission(submission.value.id, authStore);
} await router.replace(`/users/${submission.value.user.id}`);
quasar.notify({
message: i18n.t('submissionPage.deletionSuccessful'),
position: 'top',
color: 'secondary'
})
} catch (error) {
showApiErrorToast(error);
}
});
} }
</script> </script>
<style scoped> <style scoped>

View File

@ -101,11 +101,10 @@ import EditablePropertyRow from 'components/EditablePropertyRow.vue';
import {WeightUnit} from 'src/api/main/submission'; import {WeightUnit} from 'src/api/main/submission';
import {resolveLocale, supportedLocales} from 'src/i18n'; import {resolveLocale, supportedLocales} from 'src/i18n';
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
import {useQuasar} from 'quasar'; import {showApiErrorToast, showSuccessToast, showWarningToast} from 'src/utils';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const quasar = useQuasar();
const authStore = useAuthStore(); const authStore = useAuthStore();
const i18n = useI18n({useScope: 'global'}); const i18n = useI18n({useScope: 'global'});
@ -121,7 +120,7 @@ onMounted(async () => {
// Redirect away from the page if the user isn't viewing their own settings. // Redirect away from the page if the user isn't viewing their own settings.
const userId = route.params.userId as string; const userId = route.params.userId as string;
if (!authStore.user || authStore.user.id !== userId) { if (!authStore.user || authStore.user.id !== userId) {
await router.push(`/users/${userId}`); await router.replace(`/users/${userId}`);
} }
personalDetails.value = await api.auth.getMyPersonalDetails(authStore); personalDetails.value = await api.auth.getMyPersonalDetails(authStore);
@ -157,7 +156,7 @@ async function savePersonalDetails() {
if (error.response && error.response.status === 400) { if (error.response && error.response.status === 400) {
console.warn('bad request'); console.warn('bad request');
} else { } else {
console.error(error); showApiErrorToast(error);
} }
} }
} }
@ -189,25 +188,13 @@ async function updatePassword() {
try { try {
await api.auth.updatePassword(newPassword.value, authStore); await api.auth.updatePassword(newPassword.value, authStore);
newPassword.value = ''; newPassword.value = '';
quasar.notify({ showSuccessToast('userSettingsPage.passwordUpdated');
message: i18n.t('userSettingsPage.passwordUpdated'),
type: 'positive',
position: 'top'
});
} catch (error: any) { } catch (error: any) {
if (error.response && error.response.status === 400) { if (error.response && error.response.status === 400) {
newPassword.value = ''; newPassword.value = '';
quasar.notify({ showWarningToast('userSettingsPage.passwordInvalid');
message: i18n.t('userSettingsPage.passwordInvalid'),
type: 'warning',
position: 'top'
});
} else { } else {
quasar.notify({ showApiErrorToast(error);
message: i18n.t('generalErrors.apiError'),
type: 'danger',
position: 'top'
});
} }
} }
} }

View File

@ -210,7 +210,7 @@ async function onSubmitted() {
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitFailed'); submitButtonLabel.value = i18n.t('gymPage.submitPage.submitFailed');
await sleep(3000); await sleep(3000);
} else { } else {
showApiErrorToast(i18n, quasar); showApiErrorToast(error);
} }
} }
// Otherwise, report the failed submission and give up. // Otherwise, report the failed submission and give up.
@ -219,7 +219,7 @@ async function onSubmitted() {
await sleep(3000); await sleep(3000);
} }
} catch (error: any) { } catch (error: any) {
showApiErrorToast(i18n, quasar); showApiErrorToast(error);
} finally { } finally {
submitting.value = false; submitting.value = false;
submitButtonLabel.value = i18n.t('gymPage.submitPage.submit'); submitButtonLabel.value = i18n.t('gymPage.submitPage.submit');

View File

@ -63,8 +63,6 @@ import {UserFollowResponse, UserProfile} from 'src/api/main/auth';
import api from 'src/api/main'; import api from 'src/api/main';
import {useRoute} from 'vue-router'; import {useRoute} from 'vue-router';
import {useAuthStore} from 'stores/auth-store'; import {useAuthStore} from 'stores/auth-store';
import {useI18n} from 'vue-i18n';
import {useQuasar} from 'quasar';
import {showApiErrorToast, showInfoToast} from 'src/utils'; import {showApiErrorToast, showInfoToast} from 'src/utils';
import PageMenu from 'components/PageMenu.vue'; import PageMenu from 'components/PageMenu.vue';
import UserSubmissionsPage from 'pages/user/UserSubmissionsPage.vue'; import UserSubmissionsPage from 'pages/user/UserSubmissionsPage.vue';
@ -75,8 +73,6 @@ import {useDevStore} from 'stores/dev-store';
const route = useRoute(); const route = useRoute();
const authStore = useAuthStore(); const authStore = useAuthStore();
const devStore = useDevStore(); const devStore = useDevStore();
const i18n = useI18n();
const quasar = useQuasar();
const profile: Ref<UserProfile | undefined> = ref(); const profile: Ref<UserProfile | undefined> = ref();
const isOwnUser = ref(false); const isOwnUser = ref(false);
@ -102,7 +98,7 @@ async function loadUser(id: string) {
isOwnUser.value = authStore.loggedIn && profile.value.id === authStore.user?.id; isOwnUser.value = authStore.loggedIn && profile.value.id === authStore.user?.id;
} catch (error: any) { } catch (error: any) {
if (!(error.response && error.response.status === 404)) { if (!(error.response && error.response.status === 404)) {
showApiErrorToast(i18n, quasar); showApiErrorToast(error);
} }
} }
} }
@ -114,10 +110,10 @@ async function followUser() {
if (result === UserFollowResponse.FOLLOWED) { if (result === UserFollowResponse.FOLLOWED) {
await loadUser(profile.value.id); await loadUser(profile.value.id);
} else if (result === UserFollowResponse.REQUESTED) { } else if (result === UserFollowResponse.REQUESTED) {
showInfoToast(quasar, 'Requested to follow this user!'); showInfoToast('userPage.requestedToFollow');
} }
} catch (error) { } catch (error) {
showApiErrorToast(i18n, quasar); showApiErrorToast(error);
} }
} }
} }
@ -128,7 +124,7 @@ async function unfollowUser() {
await api.auth.unfollowUser(profile.value.id, authStore); await api.auth.unfollowUser(profile.value.id, authStore);
await loadUser(profile.value.id); await loadUser(profile.value.id);
} catch (error) { } catch (error) {
showApiErrorToast(i18n, quasar); showApiErrorToast(error);
} }
} }
} }

View File

@ -14,8 +14,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {useI18n} from 'vue-i18n';
import {useQuasar} from 'quasar';
import {useAuthStore} from 'stores/auth-store'; import {useAuthStore} from 'stores/auth-store';
import {onMounted, ref, Ref} from 'vue'; import {onMounted, ref, Ref} from 'vue';
import api from 'src/api/main'; import api from 'src/api/main';
@ -30,20 +28,14 @@ interface Props {
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
const i18n = useI18n();
const quasar = useQuasar();
const authStore = useAuthStore(); const authStore = useAuthStore();
const submissions: Ref<ExerciseSubmission[]> = ref([]); const submissions: Ref<ExerciseSubmission[]> = ref([]);
const loader = new InfinitePageLoader(submissions, async paginationOptions => { const loader = new InfinitePageLoader(submissions, async paginationOptions => {
try { try {
return await api.users.getSubmissions(props.userId, authStore, paginationOptions); return await api.users.getSubmissions(props.userId, authStore, paginationOptions);
} catch (error: any) { } catch (error) {
if (error.response) { showApiErrorToast(error);
showApiErrorToast(i18n, quasar);
} else {
console.log(error);
}
} }
}); });

View File

@ -1,21 +1,46 @@
import {QVueGlobals} from 'quasar'; import {useQuasar} from 'quasar';
import {useI18n} from 'vue-i18n';
/**
* Sleeps for a given number of milliseconds before resolving.
* @param ms The milliseconds to sleep for.
*/
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export function showApiErrorToast(i18n: any, quasar: QVueGlobals) { function showToast(type: string, messageKey: string) {
const quasar = useQuasar();
const i18n = useI18n();
quasar.notify({ quasar.notify({
message: i18n.t('generalErrors.apiError'), message: i18n.t(messageKey),
type: 'danger', type: type,
position: 'top' position: 'top'
}); });
} }
export function showInfoToast(quasar: QVueGlobals, translatedMessage: string) { /**
quasar.notify({ * Shows a generic API error message toast notification, for when an API call
message: translatedMessage, * fails to return any status. This should only be called within the context of
type: 'info', * a Vue component, as it utilizes some dependencies that are only available
position: 'top' * then.
}); * @param error The error to display.
*/
export function showApiErrorToast(error?: unknown) {
showToast('danger', 'generalErrors.apiError');
if (error) {
console.error(error);
}
}
export function showInfoToast(messageKey: string) {
showToast('info', messageKey);
}
export function showSuccessToast(messageKey: string) {
showToast('positive', messageKey);
}
export function showWarningToast(messageKey: string) {
showToast('warning', messageKey);
} }
export function getDocumentHeight() { export function getDocumentHeight() {