mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-08-09 05:54:34 +02:00
Replace the notify store with 2fauth/ui notifier & 2fauth/stores parser
This commit is contained in:
5
resources/js/app.js
vendored
5
resources/js/app.js
vendored
@ -75,10 +75,11 @@ app
|
||||
.component('Kicker', Kicker)
|
||||
|
||||
// Global error handling
|
||||
// import { useNotifyStore } from '@/stores/notify'
|
||||
// import { useNotify } from '@2fauth/ui'
|
||||
// if (process.env.NODE_ENV != 'development') {
|
||||
// app.config.errorHandler = (err) => {
|
||||
// useNotifyStore().error(err)
|
||||
// useNotify().parse(err)
|
||||
// router.push({ name: 'genericError' })
|
||||
// }
|
||||
// }
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
import SearchBox from '@/components/SearchBox.vue'
|
||||
import userService from '@/services/userService'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const notify = useNotifyStore()
|
||||
const errorHandler = useErrorHandler()
|
||||
const $2fauth = inject('2fauth')
|
||||
|
||||
const props = defineProps({
|
||||
@ -106,7 +106,8 @@
|
||||
orderIsDesc.value == true ? sortDesc() : sortAsc()
|
||||
})
|
||||
.catch(error => {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
})
|
||||
.finally(() => {
|
||||
isFetching.value = false
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const { copy } = useClipboard({ legacy: true })
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -5,13 +5,15 @@
|
||||
import Dots from '@/components/Dots.vue'
|
||||
import twofaccountService from '@/services/twofaccountService'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useDisplayablePassword } from '@/composables/helpers'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const $2fauth = inject('2fauth')
|
||||
const { copy, copied } = useClipboard({ legacy: true })
|
||||
const route = useRoute()
|
||||
@ -117,10 +119,12 @@
|
||||
}
|
||||
// Case 3
|
||||
else if (! props.secret) {
|
||||
notify.error(new Error(t('error.cannot_create_otp_without_secret')))
|
||||
errorHandler.parse(new Error(t('error.cannot_create_otp_without_secret')))
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
else if (! isTimeBased(otpauthParams.value.otp_type) && ! isHMacBased(otpauthParams.value.otp_type)) {
|
||||
notify.error(new Error(t('error.not_a_supported_otp_type')))
|
||||
errorHandler.parse(new Error(t('error.not_a_supported_otp_type')))
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
const { copy } = useClipboard({ legacy: true })
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
|
||||
const props = defineProps({
|
||||
qrContent: String,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import appSettingService from '@/services/appSettingService'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
|
||||
/**
|
||||
* Saves a setting on the backend
|
||||
@ -17,14 +18,15 @@ export async function useAppSettingsUpdater(setting, value, returnValidationErro
|
||||
await appSettingService.update(setting, value, { returnError: true })
|
||||
.then(response => {
|
||||
data = value
|
||||
useNotifyStore().success({ type: 'is-success', text: t('message.settings.forms.setting_saved') })
|
||||
useNotify().success({ text: t('message.settings.forms.setting_saved') })
|
||||
})
|
||||
.catch(err => {
|
||||
if( returnValidationError && err.response.status === 422 ) {
|
||||
error = err
|
||||
}
|
||||
else {
|
||||
useNotifyStore().error(err);
|
||||
useErrorHandler().parse(err)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
|
||||
|
8
resources/js/router/index.js
vendored
8
resources/js/router/index.js
vendored
@ -3,7 +3,8 @@ import middlewarePipeline from "@/router/middlewarePipeline";
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
import authGuard from './middlewares/authGuard'
|
||||
import adminOnly from './middlewares/adminOnly'
|
||||
@ -62,8 +63,9 @@ router.beforeEach((to, from, next) => {
|
||||
const user = useUserStore()
|
||||
const twofaccounts = useTwofaccounts()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const notify = useNotifyStore()
|
||||
const stores = { user: user, twofaccounts: twofaccounts, appSettings: appSettings, notify: notify }
|
||||
const notify = useNotify()
|
||||
const errorHandler = useErrorHandler()
|
||||
const stores = { user: user, twofaccounts: twofaccounts, appSettings: appSettings, notify: notify, errorHandler: errorHandler }
|
||||
const nextMiddleware = {}
|
||||
const context = { to, from, next, nextMiddleware, stores }
|
||||
|
||||
|
6
resources/js/router/middlewares/adminOnly.js
vendored
6
resources/js/router/middlewares/adminOnly.js
vendored
@ -3,12 +3,14 @@
|
||||
*/
|
||||
export default async function adminOnly({ to, next, nextMiddleware, stores }) {
|
||||
const { user } = stores
|
||||
const { notify } = stores
|
||||
const { errorHandler } = stores
|
||||
|
||||
if (! user.isAdmin) {
|
||||
let err = new Error('unauthorized')
|
||||
err.response.status = 403
|
||||
notify.error(err)
|
||||
errorHandler.parse(err)
|
||||
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
else nextMiddleware()
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
export default function noEmptyError({ to, next, nextMiddleware, stores }) {
|
||||
const { notify } = stores
|
||||
const { errorHandler } = stores
|
||||
|
||||
if (notify.err == null && ! to.query.err) {
|
||||
if (errorHandler.lastError == null && ! to.query.err) {
|
||||
// return to home if no err object is set to prevent an empty error message
|
||||
next({ name: 'accounts' });
|
||||
}
|
||||
|
13
resources/js/services/httpClientFactory.js
vendored
13
resources/js/services/httpClientFactory.js
vendored
@ -1,6 +1,6 @@
|
||||
import axios from "axios"
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
export const httpClientFactory = (endpoint = 'api') => {
|
||||
let baseURL
|
||||
@ -62,7 +62,9 @@ export const httpClientFactory = (endpoint = 'api') => {
|
||||
}
|
||||
|
||||
if (error.response && [407].includes(error.response.status)) {
|
||||
useNotifyStore().error(error)
|
||||
useErrorHandler().parse(error)
|
||||
// TODO : Check if calling router here works as expected
|
||||
router.push({ name: 'genericError' })
|
||||
return new Promise(() => {})
|
||||
}
|
||||
|
||||
@ -83,11 +85,14 @@ export const httpClientFactory = (endpoint = 'api') => {
|
||||
|
||||
// Not found
|
||||
if (error.response.status === 404) {
|
||||
useNotifyStore().notFound()
|
||||
// TODO : Check if calling router here works as expected
|
||||
router.push({ name: '404' })
|
||||
return new Promise(() => {})
|
||||
}
|
||||
|
||||
useNotifyStore().error(error)
|
||||
// TODO : Check if calling router here works as expected
|
||||
useErrorHandler().parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
return new Promise(() => {})
|
||||
}
|
||||
)
|
||||
|
4
resources/js/stores/appSettings.js
vendored
4
resources/js/stores/appSettings.js
vendored
@ -1,6 +1,6 @@
|
||||
import appSettingService from '@/services/appSettingService'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
|
||||
export const useAppSettingsStore = defineStore({
|
||||
id: 'appSettings',
|
||||
@ -31,7 +31,7 @@ export const useAppSettingsStore = defineStore({
|
||||
})
|
||||
.catch(error => {
|
||||
// TODO : move the t() call from the store
|
||||
useNotifyStore().alert({ text: t('error.failed_to_retrieve_app_settings') })
|
||||
useNotify().alert({ text: t('error.failed_to_retrieve_app_settings') })
|
||||
})
|
||||
},
|
||||
},
|
||||
|
18
resources/js/stores/groups.js
vendored
18
resources/js/stores/groups.js
vendored
@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import groupService from '@/services/groupService'
|
||||
|
||||
export const useGroups = defineStore({
|
||||
@ -17,7 +17,8 @@ export const useGroups = defineStore({
|
||||
current(state) {
|
||||
const group = state.items.find(item => item.id === parseInt(useUserStore().preferences.activeGroup))
|
||||
|
||||
return group ? group.name : t('message.all')
|
||||
// TODO : restore translated prompt
|
||||
return group ? group.name : 'message.all'
|
||||
},
|
||||
|
||||
withoutTheAllGroup(state) {
|
||||
@ -47,11 +48,13 @@ export const useGroups = defineStore({
|
||||
|
||||
if (index > -1) {
|
||||
this.items[index] = group
|
||||
useNotifyStore().success({ text: t('message.groups.group_name_saved') })
|
||||
// TODO : restore translated message
|
||||
useNotify().success({ text: 'message.groups.group_name_saved' })
|
||||
}
|
||||
else {
|
||||
this.items.push(group)
|
||||
useNotifyStore().success({ text: t('message.groups.group_successfully_created') })
|
||||
// TODO : restore translated message
|
||||
useNotify().success({ text: 'message.groups.group_successfully_created' })
|
||||
}
|
||||
},
|
||||
|
||||
@ -78,10 +81,13 @@ export const useGroups = defineStore({
|
||||
async delete(id) {
|
||||
const user = useUserStore()
|
||||
|
||||
if (confirm(t('message.groups.confirm.delete'))) {
|
||||
// TODO : restore translated message
|
||||
// if (confirm(t('message.groups.confirm.delete'))) {
|
||||
if (confirm('message.groups.confirm.delete')) {
|
||||
await groupService.delete(id).then(response => {
|
||||
this.items = this.items.filter(a => a.id !== id)
|
||||
useNotifyStore().success({ text: t('message.groups.group_successfully_deleted') })
|
||||
// TODO : restore translated message
|
||||
useNotify().success({ text: 'message.groups.group_successfully_deleted' })
|
||||
|
||||
// Reset group filter to 'All' (groupId=0) since the backend has already made
|
||||
// the change automatically. This prevents a new request.
|
||||
|
86
resources/js/stores/notify.js
vendored
86
resources/js/stores/notify.js
vendored
@ -1,86 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import router from '@/router'
|
||||
const { notify } = useNotification()
|
||||
|
||||
export const useNotifyStore = defineStore({
|
||||
id: 'notify',
|
||||
|
||||
state: () => {
|
||||
return {
|
||||
err: null,
|
||||
message: null,
|
||||
originalMessage: null,
|
||||
debug: null,
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
},
|
||||
|
||||
actions: {
|
||||
parseError(err) {
|
||||
// TODO : use the new Notify component
|
||||
// const { t } = useI18n()
|
||||
|
||||
this.$reset
|
||||
this.err = err
|
||||
|
||||
// Hnalde axios response error
|
||||
if (err.response) {
|
||||
if (err.response.status === 407) {
|
||||
this.message = t('error.auth_proxy_failed'),
|
||||
this.originalMessage = t('error.auth_proxy_failed_legend')
|
||||
}
|
||||
else if (err.response.status === 403) {
|
||||
this.message = t('error.unauthorized'),
|
||||
this.originalMessage = t('error.unauthorized_legend')
|
||||
}
|
||||
else if(err.response.data) {
|
||||
this.message = err.response.data.message,
|
||||
this.originalMessage = err.response.data.originalMessage ?? null
|
||||
this.debug = err.response.data.debug ?? null
|
||||
}
|
||||
} else {
|
||||
this.message = err.message
|
||||
this.debug = err.stack ?? null
|
||||
}
|
||||
// else if (err.request) {
|
||||
|
||||
//
|
||||
},
|
||||
|
||||
notFound(err) {
|
||||
router.push({ name: '404' })
|
||||
},
|
||||
|
||||
error(err) {
|
||||
this.parseError(err)
|
||||
router.push({ name: 'genericError' })
|
||||
},
|
||||
|
||||
info(notification) {
|
||||
notify({ type: 'is-info', ...notification})
|
||||
},
|
||||
|
||||
success(notification) {
|
||||
notify({ type: 'is-success', ...notification})
|
||||
},
|
||||
|
||||
warn(notification) {
|
||||
notify({ type: 'is-warning', ...notification})
|
||||
},
|
||||
|
||||
alert(notification) {
|
||||
notify({ type: 'is-danger', ...notification})
|
||||
},
|
||||
|
||||
action(notification) {
|
||||
notify({ type: 'is-dark', ...notification})
|
||||
},
|
||||
|
||||
clear() {
|
||||
notify({ clean: true })
|
||||
}
|
||||
|
||||
},
|
||||
})
|
4
resources/js/stores/twofaccounts.js
vendored
4
resources/js/stores/twofaccounts.js
vendored
@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { startsWithUppercase } from '@/composables/helpers'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import twofaccountService from '@/services/twofaccountService'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
@ -155,7 +155,7 @@ export const useTwofaccounts = defineStore({
|
||||
})
|
||||
this.items = remainingItems
|
||||
this.selectNone()
|
||||
useNotifyStore().success({ text: t('message.twofaccounts.accounts_deleted') })
|
||||
useNotify().success({ text: t('message.twofaccounts.accounts_deleted') })
|
||||
})
|
||||
}
|
||||
},
|
||||
|
11
resources/js/stores/user.js
vendored
11
resources/js/stores/user.js
vendored
@ -5,8 +5,9 @@ import router from '@/router'
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
import { useGroups } from '@/stores/groups'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'user',
|
||||
@ -63,7 +64,8 @@ export const useUserStore = defineStore({
|
||||
*/
|
||||
logout(options = {}) {
|
||||
const { kicked } = options
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const errorHandler = useErrorHandler()
|
||||
|
||||
// async appLogout(evt) {
|
||||
if (this.$2fauth.config.proxyAuth) {
|
||||
@ -85,7 +87,8 @@ export const useUserStore = defineStore({
|
||||
// backend has already detect inactivity on its side. In this case we
|
||||
// don't want any error to be displayed.
|
||||
if (error.response.status !== 401) {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
else this.tossOut()
|
||||
})
|
||||
@ -159,7 +162,7 @@ export const useUserStore = defineStore({
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
notify.alert({ text: t('error.data_cannot_be_refreshed_from_server') })
|
||||
})
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
<script setup>
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const { t } = useI18n()
|
||||
const errorHandler = useNotifyStore()
|
||||
const errorHandler = useErrorHandler()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
@ -25,7 +23,7 @@
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.err) {
|
||||
errorHandler.message = t('error.' + route.query.err)
|
||||
errorHandler.message = 'error.' + route.query.err
|
||||
}
|
||||
})
|
||||
|
||||
@ -45,14 +43,14 @@
|
||||
<modal v-model="showModal" :closable="props.closable">
|
||||
<div class="error-message" v-if="$route.name == '404' || $route.name == 'notFound'">
|
||||
<p class="error-404"></p>
|
||||
<p>{{ $t('error.resource_not_found') }}</p>
|
||||
<p>{{ $t('message.resource_not_found') }}</p>
|
||||
</div>
|
||||
<div v-else class="error-message" >
|
||||
<p class="error-generic"></p>
|
||||
<p>{{ $t('error.error_occured') }} </p>
|
||||
<p v-if="errorHandler.message" class="has-text-grey-lighter">{{ errorHandler.message }}</p>
|
||||
<p>{{ $t('message.error_occured') }} </p>
|
||||
<p v-if="errorHandler.message" class="has-text-grey-lighter">{{ $t(errorHandler.message) }}</p>
|
||||
<p v-if="errorHandler.originalMessage" class="has-text-grey-lighter">{{ errorHandler.originalMessage }}</p>
|
||||
<p v-if="showDebug && errorHandler.debug" class="is-size-7 is-family-code"><br>{{ errorHandler.debug }}</p>
|
||||
<p v-if="showDebug && errorHandler.debug" class="is-size-7 is-family-code pt-3"><br>{{ errorHandler.debug }}</p>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
|
@ -2,13 +2,13 @@
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
|
||||
const router = useRouter()
|
||||
const user = useUserStore()
|
||||
const bus = useBusStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const twofaccounts = useTwofaccounts()
|
||||
|
||||
const qrcodeInput = ref(null)
|
||||
|
@ -3,7 +3,7 @@
|
||||
import systemService from '@/services/systemService'
|
||||
import { useAppSettingsUpdater } from '@/composables/appSettingsUpdater'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import VersionChecker from '@/components/VersionChecker.vue'
|
||||
import CopyButton from '@/components/CopyButton.vue'
|
||||
@ -12,7 +12,7 @@
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
isClearingCache.value = true;
|
||||
|
||||
systemService.clearCache().then(response => {
|
||||
useNotifyStore().success({ type: 'is-success', text: t('message.admin.cache_cleared') })
|
||||
useNotify().success({ text: t('message.admin.cache_cleared') })
|
||||
})
|
||||
.finally(() => {
|
||||
isClearingCache.value = false;
|
||||
|
@ -3,12 +3,14 @@
|
||||
import appSettingService from '@/services/appSettingService'
|
||||
import { useAppSettingsUpdater } from '@/composables/appSettingsUpdater'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
@ -46,7 +48,8 @@
|
||||
})
|
||||
.catch(error => {
|
||||
if( error.response.status !== 404 ) {
|
||||
notify.error(error);
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
<script setup>
|
||||
import AdminTabs from '@/layouts/AdminTabs.vue'
|
||||
import userService from '@/services/userService'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import SearchBox from '@/components/SearchBox.vue'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
const users = ref([])
|
||||
@ -88,7 +90,8 @@
|
||||
users.value = response.data
|
||||
})
|
||||
.catch(error => {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
})
|
||||
.finally(() => {
|
||||
isFetching.value = false
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import AccessLogViewer from '@/components/AccessLogViewer.vue'
|
||||
import userService from '@/services/userService'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
|
||||
const bus = useBusStore()
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const router = useRouter()
|
||||
|
||||
const registerForm = reactive(new Form({
|
||||
|
@ -2,14 +2,16 @@
|
||||
import CopyButton from '@/components/CopyButton.vue'
|
||||
import AccessLogViewer from '@/components/AccessLogViewer.vue'
|
||||
import userService from '@/services/userService'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const router = useRouter()
|
||||
const user = useUserStore()
|
||||
const bus = useBusStore()
|
||||
@ -40,7 +42,8 @@
|
||||
bus.username = managedUser.value.info.name
|
||||
})
|
||||
.catch(error => {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
})
|
||||
.finally(() => {
|
||||
isFetching.value = false
|
||||
@ -79,7 +82,10 @@
|
||||
if(error.response.status === 400) {
|
||||
notify.alert({ text: error.response.data.reason })
|
||||
}
|
||||
else notify.error(error)
|
||||
else {
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -117,7 +123,9 @@
|
||||
managedUser.value.info.is_admin = true
|
||||
}
|
||||
else {
|
||||
notify.error(error.response)
|
||||
// TODO : check if we should return error.response or error
|
||||
errorHandler.parse(error.response)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -140,7 +148,9 @@
|
||||
notify.alert({ text: error.response.data.message })
|
||||
}
|
||||
else {
|
||||
notify.error(error.response)
|
||||
// TODO : check if we should return error.response or error
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -2,16 +2,18 @@
|
||||
import Form from '@/components/formElements/Form'
|
||||
import SsoConnectLink from '@/components/SsoConnectLink.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { webauthnService } from '@/services/webauthn/webauthnService'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const router = useRouter()
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const showWebauthnForm = user.preferences.useWebauthnOnly ? true : useStorage($2fauth.prefix + 'showWebauthnForm', false)
|
||||
const form = reactive(new Form({
|
||||
@ -67,7 +69,8 @@
|
||||
notify.alert({text: t('message.auth.forms.authentication_failed'), duration: 10000 })
|
||||
}
|
||||
else if( error.response.status !== 422 ) {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@ -110,7 +113,8 @@
|
||||
form.errors.set(form.extractErrors(error.response))
|
||||
}
|
||||
else {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -2,12 +2,14 @@
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { webauthnService } from '@/services/webauthn/webauthnService'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const router = useRouter()
|
||||
const showWebauthnRegistration = ref(false)
|
||||
const deviceId = ref(null)
|
||||
@ -57,7 +59,8 @@
|
||||
notify.alert({ text: error.response.data.message })
|
||||
}
|
||||
else {
|
||||
notify.error(error);
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const notify = useNotifyStore()
|
||||
const errorHandler = useErrorHandler()
|
||||
const notify = useNotify()
|
||||
const route = useRoute()
|
||||
|
||||
const isWebauthnReset = route.name == 'webauthn.lost'
|
||||
@ -25,7 +27,8 @@
|
||||
notify.alert({ text: error.response.data.requestFailed, duration:-1 })
|
||||
}
|
||||
else if( error.response.status !== 422 ) {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const notify = useNotifyStore()
|
||||
const errorHandler = useErrorHandler()
|
||||
const notify = useNotify()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
@ -32,7 +34,8 @@
|
||||
notify.alert({ text: error.response.data.resetFailed, duration:-1 })
|
||||
}
|
||||
else if( error.response.status !== 422 ) {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const showWebauthnForm = useStorage($2fauth.prefix + 'showWebauthnForm', false)
|
||||
@ -35,7 +37,8 @@
|
||||
notify.alert({ text: error.response.data.message, duration:-1 })
|
||||
}
|
||||
else {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -2,13 +2,15 @@
|
||||
import Form from '@/components/formElements/Form'
|
||||
import SettingTabs from '@/layouts/SettingTabs.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const router = useRouter()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
@ -45,7 +47,9 @@
|
||||
notify.alert({ text: error.response.data.message })
|
||||
}
|
||||
else if( error.response.status !== 422 ) {
|
||||
notify.error(error.response)
|
||||
// TODO : check if we should return error.response or error
|
||||
errorHandler.parse(error.response)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -67,7 +71,9 @@
|
||||
notify.alert({ text: error.response.data.message })
|
||||
}
|
||||
else if( error.response.status !== 422 ) {
|
||||
notify.error(error.response)
|
||||
// TODO : check if we should return error.response or error
|
||||
errorHandler.parse(error.response)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -88,7 +94,9 @@
|
||||
notify.alert({ text: error.response.data.message })
|
||||
}
|
||||
else if( error.response.status !== 422 ) {
|
||||
notify.error(error.response)
|
||||
// TODO : check if we should return error.response or error
|
||||
errorHandler.parse(error.response)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const form = reactive(new Form({
|
||||
name: t('message.auth.webauthn.my_device')
|
||||
}))
|
||||
|
@ -3,16 +3,18 @@
|
||||
import userService from '@/services/userService'
|
||||
import SettingTabs from '@/layouts/SettingTabs.vue'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const user = useUserStore()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
const { copy } = useClipboard({ legacy: true })
|
||||
@ -61,7 +63,8 @@
|
||||
// The form is already disabled (see isDisabled) so we do nothing more here
|
||||
}
|
||||
else {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -3,7 +3,7 @@
|
||||
import userService from '@/services/userService'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useGroups } from '@/stores/groups'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { timezones } from './timezones'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@ -12,7 +12,7 @@
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const groups = useGroups()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
@ -124,7 +124,7 @@
|
||||
*/
|
||||
function savePreference(preference, value) {
|
||||
userService.updatePreference(preference, value).then(response => {
|
||||
useNotifyStore().success({ type: 'is-success', text: t('message.settings.forms.setting_saved') })
|
||||
useNotify().success({ text: t('message.settings.forms.setting_saved') })
|
||||
|
||||
if(preference === 'lang') {
|
||||
user.applyLanguage()
|
||||
|
@ -4,16 +4,18 @@
|
||||
import { webauthnService } from '@/services/webauthn/webauthnService'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const router = useRouter()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
@ -57,7 +59,8 @@
|
||||
notify.alert({ text: error.response.data.message })
|
||||
}
|
||||
else {
|
||||
notify.error(error);
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -106,7 +109,8 @@
|
||||
// The form is already disabled (see isDisabled) so we do nothing more here
|
||||
}
|
||||
else {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -11,18 +11,20 @@
|
||||
import Dots from '@/components/Dots.vue'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
import { useGroups } from '@/stores/groups'
|
||||
import { useDisplayablePassword } from '@/composables/helpers'
|
||||
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const router = useRouter()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const user = useUserStore()
|
||||
const bus = useBusStore()
|
||||
const { copy, copied } = useClipboard({ legacy: true })
|
||||
|
@ -4,14 +4,16 @@
|
||||
import QrContentDisplay from '@/components/QrContentDisplay.vue'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { QrcodeStream } from 'vue-qrcode-reader'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const bus = useBusStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
|
||||
const cameraIsOn = ref(false)
|
||||
const selectedCamera = ref(null)
|
||||
@ -55,7 +57,8 @@
|
||||
} else if (error.name === 'StreamApiNotSupportedError') {
|
||||
errorPhrase.value = 'stream_api_not_supported'
|
||||
} else {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,12 @@
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
import { useGroups } from '@/stores/groups'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useErrorHandler } from '@2fauth/stores'
|
||||
|
||||
const errorHandler = useErrorHandler()
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const router = useRouter()
|
||||
@ -19,7 +21,7 @@
|
||||
const user = useUserStore()
|
||||
const twofaccounts = useTwofaccounts()
|
||||
const bus = useBusStore()
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const form = reactive(new Form({
|
||||
service: '',
|
||||
account: '',
|
||||
@ -389,7 +391,8 @@
|
||||
}
|
||||
else notify.alert({ text: t(error.response.data.message) })
|
||||
} else {
|
||||
notify.error(error)
|
||||
errorHandler.parse(error)
|
||||
router.push({ name: 'genericError' })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -4,7 +4,7 @@
|
||||
import OtpDisplay from '@/components/OtpDisplay.vue'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import { FormTextarea } from '@2fauth/formcontrols'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useNotify } from '@2fauth/ui'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const notify = useNotify()
|
||||
const user = useUserStore()
|
||||
const bus = useBusStore()
|
||||
const twofaccounts = useTwofaccounts()
|
||||
|
@ -29,7 +29,6 @@
|
||||
"error.wrong_current_password": "Wrong current password",
|
||||
"error.wrong_password": "Wrong password",
|
||||
"error.resource_not_found": "Resource not found",
|
||||
"error.error_occured": "An error occured:",
|
||||
"error.refresh": "Refresh",
|
||||
"error.no_valid_otp": "No valid OTP resource in this QR code",
|
||||
"error.something_wrong_with_server": "Something is wrong with your server",
|
||||
@ -241,7 +240,6 @@
|
||||
"message.reset_extension_description": "The extension will be stripped of its current configuration and 2FAuth data. It will be available again for rebinding to a 2FAuth user account.",
|
||||
"message.reset_extension": "Reset extension",
|
||||
"message.reset": "Reset",
|
||||
"message.resource_not_found": "Resource not found",
|
||||
"message.resources": "Resources",
|
||||
"message.retry_or_reset_extension": "You can try to refresh (maybe it was a temporary problem) or you can reset the extension and provide a new token.",
|
||||
"message.save": "Save",
|
||||
|
@ -29,7 +29,6 @@
|
||||
"error.wrong_current_password": "Mauvais mot de passe (actuel)",
|
||||
"error.wrong_password": "Mauvais mot de passe",
|
||||
"error.resource_not_found": "Ressource introuvable",
|
||||
"error.error_occured": "Une erreur est survenue :",
|
||||
"error.refresh": "Actualiser",
|
||||
"error.no_valid_otp": "Aucune donnée OTP valide dans ce QR code",
|
||||
"error.something_wrong_with_server": "Il y a un problème avec votre serveur",
|
||||
@ -241,7 +240,6 @@
|
||||
"message.reset_extension_description": "Toutes les données de configuration et de double authentification seront supprimées. L'extension pourra être reconnectée à un compte utilisateur 2FAuth.",
|
||||
"message.reset_extension": "Réinitialiser l'extension",
|
||||
"message.reset": "Réinitialiser",
|
||||
"message.resource_not_found": "Ressource introuvable",
|
||||
"message.resources": "Ressources",
|
||||
"message.retry_or_reset_extension": "Essayez d'actualiser (ce n'était peut-être qu'un problème temporaire) ou alors réinitialisez l'extension et fournissez un nouveau jeton d'accès.",
|
||||
"message.save": "Enregistrer",
|
||||
|
Reference in New Issue
Block a user