web/satellite/vuetify: added notifications
Added notifications to Vuetify app. Populated existing functionality with notifications. Issue: https://github.com/storj/storj/issues/6087 Change-Id: I8339c372bb32fbf1e0ea136c92383494c129b4b6
This commit is contained in:
parent
f57bc81ce7
commit
185ebe3dcf
@ -3,12 +3,14 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<router-view />
|
<router-view />
|
||||||
|
<Notifications />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { useConfigStore } from '@/store/modules/configStore';
|
import { useConfigStore } from '@/store/modules/configStore';
|
||||||
|
import Notifications from '@poc/layouts/default/Notifications.vue';
|
||||||
|
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
|
@ -99,7 +99,8 @@ import { ProjectInvitationResponse } from '@/types/projects';
|
|||||||
import { ProjectRole } from '@/types/projectMembers';
|
import { ProjectRole } from '@/types/projectMembers';
|
||||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
import IconProject from '@poc/components/icons/IconProject.vue';
|
import IconProject from '@poc/components/icons/IconProject.vue';
|
||||||
import IconSettings from '@poc/components/icons/IconSettings.vue';
|
import IconSettings from '@poc/components/icons/IconSettings.vue';
|
||||||
@ -116,6 +117,7 @@ const emit = defineEmits<{
|
|||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const isDeclining = ref<boolean>(false);
|
const isDeclining = ref<boolean>(false);
|
||||||
|
|
||||||
@ -139,10 +141,18 @@ async function declineInvitation(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await projectsStore.respondToInvitation(props.item.id, ProjectInvitationResponse.Decline);
|
await projectsStore.respondToInvitation(props.item.id, ProjectInvitationResponse.Decline);
|
||||||
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
|
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
|
||||||
} catch { /* empty */ }
|
} catch (error) {
|
||||||
|
error.message = `Failed to decline project invitation. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||||
|
}
|
||||||
|
|
||||||
await projectsStore.getUserInvitations().catch(_ => {});
|
try {
|
||||||
await projectsStore.getProjects().catch(_ => {});
|
await projectsStore.getUserInvitations();
|
||||||
|
await projectsStore.getProjects();
|
||||||
|
} catch (error) {
|
||||||
|
error.message = `Failed to reload projects and invitations list. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||||
|
}
|
||||||
|
|
||||||
isDeclining.value = false;
|
isDeclining.value = false;
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,8 @@ import { ProjectRole } from '@/types/projectMembers';
|
|||||||
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
|
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
|
||||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
import IconSettings from '@poc/components/icons/IconSettings.vue';
|
import IconSettings from '@poc/components/icons/IconSettings.vue';
|
||||||
import IconTeam from '@poc/components/icons/IconTeam.vue';
|
import IconTeam from '@poc/components/icons/IconTeam.vue';
|
||||||
@ -150,6 +151,7 @@ const decliningIds = ref(new Set<string>());
|
|||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const sortBy = [{ key: 'name', order: 'asc' }];
|
const sortBy = [{ key: 'name', order: 'asc' }];
|
||||||
const headers = [
|
const headers = [
|
||||||
@ -186,10 +188,18 @@ async function declineInvitation(item: ProjectItemModel): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await projectsStore.respondToInvitation(item.id, ProjectInvitationResponse.Decline);
|
await projectsStore.respondToInvitation(item.id, ProjectInvitationResponse.Decline);
|
||||||
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
|
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
|
||||||
} catch { /* empty */ }
|
} catch (error) {
|
||||||
|
error.message = `Failed to decline project invitation. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||||
|
}
|
||||||
|
|
||||||
await projectsStore.getUserInvitations().catch(_ => {});
|
try {
|
||||||
await projectsStore.getProjects().catch(_ => {});
|
await projectsStore.getUserInvitations();
|
||||||
|
await projectsStore.getProjects();
|
||||||
|
} catch (error) {
|
||||||
|
error.message = `Failed to reload projects and invitations list. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||||
|
}
|
||||||
|
|
||||||
decliningIds.value.delete(item.id);
|
decliningIds.value.delete(item.id);
|
||||||
}
|
}
|
||||||
|
@ -93,8 +93,9 @@ import {
|
|||||||
import { useLoading } from '@/composables/useLoading';
|
import { useLoading } from '@/composables/useLoading';
|
||||||
import { useUsersStore } from '@/store/modules/usersStore';
|
import { useUsersStore } from '@/store/modules/usersStore';
|
||||||
import { UpdatedUser } from '@/types/users';
|
import { UpdatedUser } from '@/types/users';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
const rules = [
|
const rules = [
|
||||||
(value: string) => (!!value || 'Can\'t be empty'),
|
(value: string) => (!!value || 'Can\'t be empty'),
|
||||||
@ -103,6 +104,7 @@ const rules = [
|
|||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const userStore = useUsersStore();
|
const userStore = useUsersStore();
|
||||||
const { isLoading, withLoading } = useLoading();
|
const { isLoading, withLoading } = useLoading();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean,
|
modelValue: boolean,
|
||||||
@ -129,8 +131,10 @@ async function onChangeName(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await userStore.updateUser(new UpdatedUser(name.value, name.value));
|
await userStore.updateUser(new UpdatedUser(name.value, name.value));
|
||||||
|
|
||||||
|
notify.success('Account info successfully updated!');
|
||||||
analyticsStore.eventTriggered(AnalyticsEvent.PROFILE_UPDATED);
|
analyticsStore.eventTriggered(AnalyticsEvent.PROFILE_UPDATED);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.EDIT_PROFILE_MODAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,8 @@ import { useConfigStore } from '@/store/modules/configStore';
|
|||||||
import { AuthHttpApi } from '@/api/auth';
|
import { AuthHttpApi } from '@/api/auth';
|
||||||
import { RouteConfig } from '@/types/router';
|
import { RouteConfig } from '@/types/router';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
const DELAY_BEFORE_REDIRECT = 2000; // 2 sec
|
const DELAY_BEFORE_REDIRECT = 2000; // 2 sec
|
||||||
const auth: AuthHttpApi = new AuthHttpApi();
|
const auth: AuthHttpApi = new AuthHttpApi();
|
||||||
@ -140,6 +141,7 @@ const analyticsStore = useAnalyticsStore();
|
|||||||
const { config } = useConfigStore().state;
|
const { config } = useConfigStore().state;
|
||||||
const { isLoading, withLoading } = useLoading();
|
const { isLoading, withLoading } = useLoading();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean,
|
modelValue: boolean,
|
||||||
@ -167,8 +169,10 @@ async function onChangePassword(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await auth.changePassword(oldPassword.value, newPassword.value);
|
await auth.changePassword(oldPassword.value, newPassword.value);
|
||||||
|
|
||||||
|
notify.success('Password successfully changed!');
|
||||||
analyticsStore.eventTriggered(AnalyticsEvent.PASSWORD_CHANGED);
|
analyticsStore.eventTriggered(AnalyticsEvent.PASSWORD_CHANGED);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.CHANGE_PASSWORD_MODAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +184,9 @@ async function onChangePassword(): Promise<void> {
|
|||||||
// TODO: this reload will be unnecessary once vuetify poc has its own login and/or becomes the primary app
|
// TODO: this reload will be unnecessary once vuetify poc has its own login and/or becomes the primary app
|
||||||
location.reload();
|
location.reload();
|
||||||
}, DELAY_BEFORE_REDIRECT);
|
}, DELAY_BEFORE_REDIRECT);
|
||||||
} catch (error) { /* empty */ }
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.CHANGE_PASSWORD_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
emit('update:modelValue', false);
|
emit('update:modelValue', false);
|
||||||
});
|
});
|
||||||
|
@ -162,8 +162,9 @@ import QRCode from 'qrcode';
|
|||||||
import { useLoading } from '@/composables/useLoading';
|
import { useLoading } from '@/composables/useLoading';
|
||||||
import { useConfigStore } from '@/store/modules/configStore';
|
import { useConfigStore } from '@/store/modules/configStore';
|
||||||
import { useUsersStore } from '@/store/modules/usersStore';
|
import { useUsersStore } from '@/store/modules/usersStore';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
const rules = [
|
const rules = [
|
||||||
(value: string) => (!!value || 'Can\'t be empty'),
|
(value: string) => (!!value || 'Can\'t be empty'),
|
||||||
@ -176,6 +177,7 @@ const analyticsStore = useAnalyticsStore();
|
|||||||
const { config } = useConfigStore().state;
|
const { config } = useConfigStore().state;
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const { isLoading, withLoading } = useLoading();
|
const { isLoading, withLoading } = useLoading();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const canvas = ref<HTMLCanvasElement>();
|
const canvas = ref<HTMLCanvasElement>();
|
||||||
const innerContent = ref<Component | null>(null);
|
const innerContent = ref<Component | null>(null);
|
||||||
@ -240,6 +242,7 @@ function enable(): void {
|
|||||||
|
|
||||||
analyticsStore.eventTriggered(AnalyticsEvent.MFA_ENABLED);
|
analyticsStore.eventTriggered(AnalyticsEvent.MFA_ENABLED);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.ENABLE_MFA_MODAL);
|
||||||
isError.value = true;
|
isError.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -253,7 +256,7 @@ async function showCodes() {
|
|||||||
await usersStore.generateUserMFARecoveryCodes();
|
await usersStore.generateUserMFARecoveryCodes();
|
||||||
step.value = 2;
|
step.value = 2;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
/* empty */
|
notify.notifyError(error, AnalyticsErrorEventSource.ENABLE_MFA_MODAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +270,7 @@ watch(canvas, async val => {
|
|||||||
try {
|
try {
|
||||||
await QRCode.toCanvas(canvas.value, qrLink.value);
|
await QRCode.toCanvas(canvas.value, qrLink.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
/* empty */
|
notify.notifyError(error, AnalyticsErrorEventSource.ENABLE_MFA_MODAL);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -77,9 +77,9 @@ import {
|
|||||||
|
|
||||||
import { ProjectInvitationResponse } from '@/types/projects';
|
import { ProjectInvitationResponse } from '@/types/projects';
|
||||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
import { RouteConfig } from '@/types/router';
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean,
|
modelValue: boolean,
|
||||||
@ -99,6 +99,7 @@ const emit = defineEmits<{
|
|||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const isAccepting = ref<boolean>(false);
|
const isAccepting = ref<boolean>(false);
|
||||||
const isDeclining = ref<boolean>(false);
|
const isDeclining = ref<boolean>(false);
|
||||||
@ -108,6 +109,7 @@ const isDeclining = ref<boolean>(false);
|
|||||||
*/
|
*/
|
||||||
function openProject(): void {
|
function openProject(): void {
|
||||||
projectsStore.selectProject(props.id);
|
projectsStore.selectProject(props.id);
|
||||||
|
notify.success('Invite accepted!');
|
||||||
router.push(`/projects/${props.id}/dashboard`);
|
router.push(`/projects/${props.id}/dashboard`);
|
||||||
analyticsStore.pageVisit('/projects/dashboard');
|
analyticsStore.pageVisit('/projects/dashboard');
|
||||||
}
|
}
|
||||||
@ -118,7 +120,8 @@ function openProject(): void {
|
|||||||
async function respondToInvitation(response: ProjectInvitationResponse): Promise<void> {
|
async function respondToInvitation(response: ProjectInvitationResponse): Promise<void> {
|
||||||
if (isDeclining.value || isAccepting.value) return;
|
if (isDeclining.value || isAccepting.value) return;
|
||||||
|
|
||||||
const isLoading = response === ProjectInvitationResponse.Accept ? isAccepting : isDeclining;
|
const accepted = response === ProjectInvitationResponse.Accept;
|
||||||
|
const isLoading = accepted ? isAccepting : isDeclining;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
@ -126,16 +129,26 @@ async function respondToInvitation(response: ProjectInvitationResponse): Promise
|
|||||||
await projectsStore.respondToInvitation(props.id, response);
|
await projectsStore.respondToInvitation(props.id, response);
|
||||||
success = true;
|
success = true;
|
||||||
analyticsStore.eventTriggered(
|
analyticsStore.eventTriggered(
|
||||||
response === ProjectInvitationResponse.Accept ?
|
accepted ?
|
||||||
AnalyticsEvent.PROJECT_INVITATION_ACCEPTED :
|
AnalyticsEvent.PROJECT_INVITATION_ACCEPTED :
|
||||||
AnalyticsEvent.PROJECT_INVITATION_DECLINED,
|
AnalyticsEvent.PROJECT_INVITATION_DECLINED,
|
||||||
);
|
);
|
||||||
} catch { /* empty */ }
|
} catch (error) {
|
||||||
|
const action = accepted ? 'accept' : 'decline';
|
||||||
|
error.message = `Failed to ${action} project invitation. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.JOIN_PROJECT_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
await projectsStore.getUserInvitations().catch(_ => {});
|
try {
|
||||||
await projectsStore.getProjects().catch(_ => { success = false; });
|
await projectsStore.getUserInvitations();
|
||||||
|
await projectsStore.getProjects();
|
||||||
|
} catch (error) {
|
||||||
|
success = false;
|
||||||
|
error.message = `Failed to reload projects and invitations list. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.JOIN_PROJECT_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
if (response === ProjectInvitationResponse.Accept && success) openProject();
|
if (accepted && success) openProject();
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -95,9 +95,12 @@ import {
|
|||||||
import { useLoading } from '@/composables/useLoading';
|
import { useLoading } from '@/composables/useLoading';
|
||||||
import { useUsersStore } from '@/store/modules/usersStore';
|
import { useUsersStore } from '@/store/modules/usersStore';
|
||||||
import { Duration } from '@/utils/time';
|
import { Duration } from '@/utils/time';
|
||||||
|
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const { isLoading, withLoading } = useLoading();
|
const { isLoading, withLoading } = useLoading();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean,
|
modelValue: boolean,
|
||||||
@ -137,7 +140,9 @@ async function onChangeTimeout(): Promise<void> {
|
|||||||
await withLoading(async () => {
|
await withLoading(async () => {
|
||||||
try {
|
try {
|
||||||
await usersStore.updateSettings({ sessionDuration: duration.value.nanoseconds });
|
await usersStore.updateSettings({ sessionDuration: duration.value.nanoseconds });
|
||||||
|
notify.success(`Session timeout changed successfully. Your session timeout is ${duration.value?.shortString}.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.EDIT_TIMEOUT_MODAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,11 @@ import DefaultBar from './AppBar.vue';
|
|||||||
import DefaultView from './View.vue';
|
import DefaultView from './View.vue';
|
||||||
|
|
||||||
import { useUsersStore } from '@/store/modules/usersStore';
|
import { useUsersStore } from '@/store/modules/usersStore';
|
||||||
|
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lifecycle hook after initial render.
|
* Lifecycle hook after initial render.
|
||||||
@ -26,6 +29,8 @@ const usersStore = useUsersStore();
|
|||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
try {
|
try {
|
||||||
await usersStore.getSettings();
|
await usersStore.getSettings();
|
||||||
} catch (error) { /* empty */ }
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -31,9 +31,12 @@ import { useABTestingStore } from '@/store/modules/abTestingStore';
|
|||||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||||
import { useAppStore } from '@poc/store/appStore';
|
import { useAppStore } from '@poc/store/appStore';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
|
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const billingStore = useBillingStore();
|
const billingStore = useBillingStore();
|
||||||
@ -75,7 +78,7 @@ watch(() => route.params.projectId, async newId => selectProject(newId as string
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Lifecycle hook after initial render.
|
* Lifecycle hook after initial render.
|
||||||
* Pre fetches user`s and project information.
|
* Pre-fetches user`s and project information.
|
||||||
*/
|
*/
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
try {
|
try {
|
||||||
@ -85,6 +88,7 @@ onBeforeMount(async () => {
|
|||||||
usersStore.getSettings(),
|
usersStore.getSettings(),
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
|
||||||
setTimeout(async () => await router.push(RouteConfig.Login.path), 1000);
|
setTimeout(async () => await router.push(RouteConfig.Login.path), 1000);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -92,11 +96,17 @@ onBeforeMount(async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await billingStore.setupAccount();
|
await billingStore.setupAccount();
|
||||||
} catch (error) { /* empty */ }
|
} catch (error) {
|
||||||
|
error.message = `Unable to setup account. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await billingStore.getCreditCards();
|
await billingStore.getCreditCards();
|
||||||
} catch (error) { /* empty */ }
|
} catch (error) {
|
||||||
|
error.message = `Unable to get credit cards. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
selectProject(route.params.projectId as string);
|
selectProject(route.params.projectId as string);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (C) 2023 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-snackbar
|
||||||
|
v-model="doNotificationsExist"
|
||||||
|
position="fixed"
|
||||||
|
location="top right"
|
||||||
|
z-index="99999"
|
||||||
|
variant="text"
|
||||||
|
contained
|
||||||
|
>
|
||||||
|
<v-alert
|
||||||
|
v-for="item in notifications"
|
||||||
|
:key="item.id"
|
||||||
|
closable
|
||||||
|
variant="elevated"
|
||||||
|
:title="title(item.type)"
|
||||||
|
:text="item.message"
|
||||||
|
:type="getType(item.type)"
|
||||||
|
rounded="lg"
|
||||||
|
class="my-2"
|
||||||
|
border
|
||||||
|
@mouseover="() => onMouseOver(item.id)"
|
||||||
|
@mouseleave="() => onMouseLeave(item.id)"
|
||||||
|
/>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { VAlert, VSnackbar } from 'vuetify/components';
|
||||||
|
|
||||||
|
import { useNotificationsStore } from '@/store/modules/notificationsStore';
|
||||||
|
import { DelayedNotification, NOTIFICATION_TYPES } from '@/types/DelayedNotification';
|
||||||
|
|
||||||
|
const notificationsStore = useNotificationsStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if any notifications are in queue.
|
||||||
|
*/
|
||||||
|
const doNotificationsExist = computed((): boolean => {
|
||||||
|
return notifications.value.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all notification queue from store.
|
||||||
|
*/
|
||||||
|
const notifications = computed((): DelayedNotification[] => {
|
||||||
|
return notificationsStore.state.notificationQueue as DelayedNotification[];
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns notification title based on type.
|
||||||
|
* @param itemType
|
||||||
|
*/
|
||||||
|
function title(itemType: string): string {
|
||||||
|
const type = getType(itemType);
|
||||||
|
const [firstLetter, ...rest] = type;
|
||||||
|
|
||||||
|
return `${firstLetter.toUpperCase()}${rest.join('')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns notification type.
|
||||||
|
* @param itemType
|
||||||
|
*/
|
||||||
|
function getType(itemType: string): string {
|
||||||
|
switch (itemType) {
|
||||||
|
case NOTIFICATION_TYPES.SUCCESS:
|
||||||
|
return 'success';
|
||||||
|
case NOTIFICATION_TYPES.ERROR:
|
||||||
|
return 'error';
|
||||||
|
case NOTIFICATION_TYPES.WARNING:
|
||||||
|
return 'warning';
|
||||||
|
default:
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces notification to stay on page on mouse over it.
|
||||||
|
*/
|
||||||
|
function onMouseOver(id: string): void {
|
||||||
|
notificationsStore.pauseNotification(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume notification flow when mouse leaves notification.
|
||||||
|
*/
|
||||||
|
function onMouseLeave(id: string): void {
|
||||||
|
notificationsStore.resumeNotification(id);
|
||||||
|
}
|
||||||
|
</script>
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
|
import { App } from 'vue';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
|
|
||||||
import router from '../router';
|
import router from '../router';
|
||||||
@ -15,13 +16,16 @@ import router from '../router';
|
|||||||
import { loadFonts } from './webfontloader';
|
import { loadFonts } from './webfontloader';
|
||||||
import vuetify from './vuetify';
|
import vuetify from './vuetify';
|
||||||
|
|
||||||
|
import NotificatorPlugin from '@/utils/plugins/notificator';
|
||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
|
|
||||||
export function registerPlugins (app) {
|
export function registerPlugins(app: App<Element>) {
|
||||||
loadFonts();
|
loadFonts();
|
||||||
app
|
app
|
||||||
.use(vuetify)
|
.use(vuetify)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(pinia);
|
.use(pinia)
|
||||||
|
.use(NotificatorPlugin);
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,8 @@ import {
|
|||||||
|
|
||||||
import { User, UserSettings } from '@/types/users';
|
import { User, UserSettings } from '@/types/users';
|
||||||
import { useUsersStore } from '@/store/modules/usersStore';
|
import { useUsersStore } from '@/store/modules/usersStore';
|
||||||
|
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
import { Duration } from '@/utils/time';
|
import { Duration } from '@/utils/time';
|
||||||
|
|
||||||
import ChangePasswordDialog from '@poc/components/dialogs/ChangePasswordDialog.vue';
|
import ChangePasswordDialog from '@poc/components/dialogs/ChangePasswordDialog.vue';
|
||||||
@ -183,6 +185,7 @@ import EnableMFADialog from '@poc/components/dialogs/EnableMFADialog.vue';
|
|||||||
import SetSessionTimeoutDialog from '@poc/components/dialogs/SetSessionTimeoutDialog.vue';
|
import SetSessionTimeoutDialog from '@poc/components/dialogs/SetSessionTimeoutDialog.vue';
|
||||||
|
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const isChangePasswordDialogShown = ref<boolean>(false);
|
const isChangePasswordDialogShown = ref<boolean>(false);
|
||||||
const isChangeNameDialogShown = ref<boolean>(false);
|
const isChangeNameDialogShown = ref<boolean>(false);
|
||||||
@ -208,7 +211,7 @@ async function toggleEnableMFADialog() {
|
|||||||
await usersStore.generateUserMFASecret();
|
await usersStore.generateUserMFASecret();
|
||||||
isEnableMFADialogShown.value = true;
|
isEnableMFADialogShown.value = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
/* empty */
|
notify.notifyError(error, AnalyticsErrorEventSource.ACCOUNT_SETTINGS_AREA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,8 @@ import { useBucketsStore } from '@/store/modules/bucketsStore';
|
|||||||
import { DataStamp, Project, ProjectLimits } from '@/types/projects';
|
import { DataStamp, Project, ProjectLimits } from '@/types/projects';
|
||||||
import { Dimensions, Size } from '@/utils/bytesSize';
|
import { Dimensions, Size } from '@/utils/bytesSize';
|
||||||
import { ChartUtils } from '@/utils/chart';
|
import { ChartUtils } from '@/utils/chart';
|
||||||
|
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
|
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
|
||||||
import PageSubtitleComponent from '@poc/components/PageSubtitleComponent.vue';
|
import PageSubtitleComponent from '@poc/components/PageSubtitleComponent.vue';
|
||||||
@ -106,6 +108,8 @@ const agStore = useAccessGrantsStore();
|
|||||||
const billingStore = useBillingStore();
|
const billingStore = useBillingStore();
|
||||||
const bucketsStore = useBucketsStore();
|
const bucketsStore = useBucketsStore();
|
||||||
|
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const isDataFetching = ref<boolean>(true);
|
const isDataFetching = ref<boolean>(true);
|
||||||
const chartWidth = ref<number>(0);
|
const chartWidth = ref<number>(0);
|
||||||
const chartContainer = ref<ComponentPublicInstance>();
|
const chartContainer = ref<ComponentPublicInstance>();
|
||||||
@ -323,7 +327,9 @@ onMounted(async (): Promise<void> => {
|
|||||||
agStore.getAccessGrants(FIRST_PAGE, projectID),
|
agStore.getAccessGrants(FIRST_PAGE, projectID),
|
||||||
bucketsStore.getBuckets(FIRST_PAGE, projectID),
|
bucketsStore.getBuckets(FIRST_PAGE, projectID),
|
||||||
]);
|
]);
|
||||||
} catch (error) { /* empty */ } finally {
|
} catch (error) {
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_DASHBOARD_PAGE);
|
||||||
|
} finally {
|
||||||
isDataFetching.value = false;
|
isDataFetching.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -134,8 +134,9 @@ import {
|
|||||||
|
|
||||||
import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
|
import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
|
||||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||||
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
|
import { useNotify } from '@/utils/hooks';
|
||||||
|
|
||||||
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
|
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
|
||||||
import PageSubtitleComponent from '@poc/components/PageSubtitleComponent.vue';
|
import PageSubtitleComponent from '@poc/components/PageSubtitleComponent.vue';
|
||||||
@ -144,6 +145,7 @@ import TeamTableComponent from '@poc/components/TeamTableComponent.vue';
|
|||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const pmStore = useProjectMembersStore();
|
const pmStore = useProjectMembersStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
const isLoading = ref<boolean>(false);
|
const isLoading = ref<boolean>(false);
|
||||||
const dialog = ref<boolean>(false);
|
const dialog = ref<boolean>(false);
|
||||||
@ -167,7 +169,10 @@ async function onAddUsersClick(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await pmStore.inviteMembers([email.value], selectedProjectID.value);
|
await pmStore.inviteMembers([email.value], selectedProjectID.value);
|
||||||
} catch (_) {
|
notify.notify('Invites sent!');
|
||||||
|
} catch (error) {
|
||||||
|
error.message = `Error adding project members. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -176,7 +181,10 @@ async function onAddUsersClick(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await pmStore.getProjectMembers(1, selectedProjectID.value);
|
await pmStore.getProjectMembers(1, selectedProjectID.value);
|
||||||
} catch (error) { /* empty */ }
|
} catch (error) {
|
||||||
|
error.message = `Unable to fetch project members. ${error.message}`;
|
||||||
|
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
dialog.value = false;
|
dialog.value = false;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
Loading…
Reference in New Issue
Block a user