mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-11-29 19:53:11 +01:00
Set up the Groups view & Create/Update forms
This commit is contained in:
parent
4fd5559e5f
commit
a52cc2dcc9
9
resources/js_vue3/router/index.js
vendored
9
resources/js_vue3/router/index.js
vendored
@ -10,9 +10,8 @@ import CreateAccount from '../views/twofaccounts/Create.vue'
|
|||||||
import EditAccount from '../views/twofaccounts/Edit.vue'
|
import EditAccount from '../views/twofaccounts/Edit.vue'
|
||||||
import ImportAccount from '../views/twofaccounts/Import.vue'
|
import ImportAccount from '../views/twofaccounts/Import.vue'
|
||||||
import QRcodeAccount from '../views/twofaccounts/QRcode.vue'
|
import QRcodeAccount from '../views/twofaccounts/QRcode.vue'
|
||||||
import Groups from '../views/Groups.vue'
|
import Groups from '../views/groups/Groups.vue'
|
||||||
// import CreateGroup from './views/groups/Create.vue'
|
import CreateUpdateGroup from '../views/groups/CreateUpdate.vue'
|
||||||
// import EditGroup from './views/groups/Edit.vue'
|
|
||||||
import Login from '../views/auth/Login.vue'
|
import Login from '../views/auth/Login.vue'
|
||||||
import Register from '../views/auth/Register.vue'
|
import Register from '../views/auth/Register.vue'
|
||||||
// import Autolock from './views/auth/Autolock.vue'
|
// import Autolock from './views/auth/Autolock.vue'
|
||||||
@ -45,8 +44,8 @@ const router = createRouter({
|
|||||||
{ path: '/account/:twofaccountId/qrcode', name: 'showQRcode', component: QRcodeAccount, meta: { middlewares: [authGuard] } },
|
{ path: '/account/:twofaccountId/qrcode', name: 'showQRcode', component: QRcodeAccount, meta: { middlewares: [authGuard] } },
|
||||||
|
|
||||||
{ path: '/groups', name: 'groups', component: Groups, meta: { middlewares: [authGuard] }, props: true },
|
{ path: '/groups', name: 'groups', component: Groups, meta: { middlewares: [authGuard] }, props: true },
|
||||||
// { path: '/group/create', name: 'createGroup', component: CreateGroup, meta: { middlewares: [authGuard] } },
|
{ path: '/group/create', name: 'createGroup', component: CreateUpdateGroup, meta: { middlewares: [authGuard] } },
|
||||||
// { path: '/group/:groupId/edit', name: 'editGroup', component: EditGroup, meta: { middlewares: [authGuard] }, props: true },
|
{ path: '/group/:groupId/edit', name: 'editGroup', component: CreateUpdateGroup, meta: { middlewares: [authGuard] }, props: true },
|
||||||
|
|
||||||
{ path: '/settings/options', name: 'settings.options', component: SettingsOptions, meta: { middlewares: [authGuard], showAbout: true } },
|
{ path: '/settings/options', name: 'settings.options', component: SettingsOptions, meta: { middlewares: [authGuard], showAbout: true } },
|
||||||
{ path: '/settings/account', name: 'settings.account', component: SettingsAccount, meta: { middlewares: [authGuard], showAbout: true } },
|
{ path: '/settings/account', name: 'settings.account', component: SettingsAccount, meta: { middlewares: [authGuard], showAbout: true } },
|
||||||
|
10
resources/js_vue3/services/groupService.js
vendored
10
resources/js_vue3/services/groupService.js
vendored
@ -11,8 +11,16 @@ export default {
|
|||||||
return apiClient.get('groups')
|
return apiClient.get('groups')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get(id, config = {}) {
|
||||||
|
return apiClient.get('/groups/' + id, { ...config })
|
||||||
|
},
|
||||||
|
|
||||||
assign(accountsIds, groupId, config = {}) {
|
assign(accountsIds, groupId, config = {}) {
|
||||||
return apiClient.post('/groups/' + groupId + '/assign', {ids: accountsIds})
|
return apiClient.post('/groups/' + groupId + '/assign', {ids: accountsIds})
|
||||||
}
|
},
|
||||||
|
|
||||||
|
delete(id, config = {}) {
|
||||||
|
return apiClient.delete('/groups/' + id, { ...config })
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
1
resources/js_vue3/stores/bus.js
vendored
1
resources/js_vue3/stores/bus.js
vendored
@ -10,6 +10,7 @@ export const useBusStore = defineStore({
|
|||||||
goBackTo: null,
|
goBackTo: null,
|
||||||
returnTo: null,
|
returnTo: null,
|
||||||
inManagementMode: false,
|
inManagementMode: false,
|
||||||
|
editedGroupName: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
55
resources/js_vue3/stores/groups.js
vendored
55
resources/js_vue3/stores/groups.js
vendored
@ -1,5 +1,6 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
import groupService from '@/services/groupService'
|
import groupService from '@/services/groupService'
|
||||||
|
|
||||||
export const useGroups = defineStore({
|
export const useGroups = defineStore({
|
||||||
@ -16,10 +17,42 @@ export const useGroups = defineStore({
|
|||||||
const group = state.items.find(item => item.id === parseInt(useUserStore().preferences.activeGroup))
|
const group = state.items.find(item => item.id === parseInt(useUserStore().preferences.activeGroup))
|
||||||
|
|
||||||
return group ? group.name : trans('commons.all')
|
return group ? group.name : trans('commons.all')
|
||||||
}
|
},
|
||||||
|
|
||||||
|
withoutTheAllGroup(state) {
|
||||||
|
return state.items.filter(item => item.id > 0)
|
||||||
|
},
|
||||||
|
|
||||||
|
theAllGroup(state) {
|
||||||
|
return state.items.find(item => item.id == 0)
|
||||||
|
},
|
||||||
|
|
||||||
|
isEmpty() {
|
||||||
|
return this.withoutTheAllGroup.length == 0
|
||||||
|
},
|
||||||
|
|
||||||
|
count() {
|
||||||
|
return this.withoutTheAllGroup.length
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
/**
|
||||||
|
* Adds or edits a group
|
||||||
|
* @param {object} group
|
||||||
|
*/
|
||||||
|
addOrEdit(group) {
|
||||||
|
const index = this.items.findIndex(g => g.id === parseInt(group.id))
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
this.items[index] = group
|
||||||
|
useNotifyStore().success({ text: trans('groups.group_name_saved') })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.items.push(group)
|
||||||
|
useNotifyStore().success({ text: trans('groups.group_successfully_created') })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the groups collection from the backend
|
* Fetches the groups collection from the backend
|
||||||
@ -30,5 +63,25 @@ export const useGroups = defineStore({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a group
|
||||||
|
*/
|
||||||
|
async delete(id) {
|
||||||
|
const user = useUserStore()
|
||||||
|
|
||||||
|
if (confirm(trans('groups.confirm.delete'))) {
|
||||||
|
await groupService.delete(id).then(response => {
|
||||||
|
this.items = this.items.filter(a => a.id !== id)
|
||||||
|
useNotifyStore().success({ text: trans('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.
|
||||||
|
if( parseInt(user.preferences.activeGroup) === id ) {
|
||||||
|
user.preferences.activeGroup = 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
80
resources/js_vue3/views/groups/CreateUpdate.vue
Normal file
80
resources/js_vue3/views/groups/CreateUpdate.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<script setup>
|
||||||
|
import Form from '@/components/formElements/Form'
|
||||||
|
import groupService from '@/services/groupService'
|
||||||
|
import { useGroups } from '@/stores/groups'
|
||||||
|
import { useBusStore } from '@/stores/bus'
|
||||||
|
|
||||||
|
const groups = useGroups()
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const bus = useBusStore()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
groupId: [Number, String]
|
||||||
|
})
|
||||||
|
|
||||||
|
const isEditMode = computed(() => {
|
||||||
|
return props.groupId != undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = reactive(new Form({
|
||||||
|
name: '',
|
||||||
|
}))
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// We get the name to edit from the bus store to prevent request latency
|
||||||
|
if (route.name == 'editGroup') {
|
||||||
|
if (bus.editedGroupName) {
|
||||||
|
form.name = bus.editedGroupName
|
||||||
|
bus.editedGroupName = undefined
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
groupService.get(props.groupId).then(response => {
|
||||||
|
form.name = response.data.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper to call the appropriate function at form submit
|
||||||
|
*/
|
||||||
|
function handleSubmit() {
|
||||||
|
isEditMode.value ? updateGroup() : createGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form to the backend to store the new group
|
||||||
|
*/
|
||||||
|
async function createGroup() {
|
||||||
|
form.post('/api/v1/groups').then(response => {
|
||||||
|
groups.addOrEdit(response.data)
|
||||||
|
router.push({ name: 'groups' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form to the backend to update the group
|
||||||
|
*/
|
||||||
|
async function updateGroup() {
|
||||||
|
form.put('/api/v1/groups/' + props.groupId).then(response => {
|
||||||
|
groups.addOrEdit(response.data)
|
||||||
|
router.push({ name: 'groups' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FormWrapper :title="isEditMode ? $t('groups.forms.rename_group') : $t('groups.forms.new_group')">
|
||||||
|
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
|
||||||
|
<FormField v-model="form.name" fieldName="name" :fieldError="form.errors.get('name')" label="commons.name" autofocus />
|
||||||
|
<FormButtons
|
||||||
|
:submitId="isEditMode ? 'btnEditGroup' : 'btnCreateGroup'"
|
||||||
|
:isBusy="form.isBusy"
|
||||||
|
:caption="isEditMode ? $t('commons.save') : $t('commons.create')"
|
||||||
|
:showCancelButton="true"
|
||||||
|
cancelLandingView="groups" />
|
||||||
|
</form>
|
||||||
|
</FormWrapper>
|
||||||
|
</template>
|
79
resources/js_vue3/views/groups/Groups.vue
Normal file
79
resources/js_vue3/views/groups/Groups.vue
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<script setup>
|
||||||
|
import { UseColorMode } from '@vueuse/components'
|
||||||
|
import { useGroups } from '@/stores/groups'
|
||||||
|
import { useBusStore } from '@/stores/bus'
|
||||||
|
|
||||||
|
const groups = useGroups()
|
||||||
|
const bus = useBusStore()
|
||||||
|
const isFetching = ref(false)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// We want the store to be up-to-date to manage the groups
|
||||||
|
isFetching.value = groups.isEmpty
|
||||||
|
|
||||||
|
await groups.fetch()
|
||||||
|
.finally(() => {
|
||||||
|
isFetching.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// We use the beforeRouteLeave with the bus store to pass the group
|
||||||
|
// name to the edit view to prevent a new request in the editGroup component.
|
||||||
|
// This could be achieved using <a> anchor with a @click event here but then
|
||||||
|
// the link won't have a href which is poor design for accessibility.
|
||||||
|
onBeforeRouteLeave((to, from) => {
|
||||||
|
if (to.name == 'editGroup') {
|
||||||
|
bus.editedGroupName = groups.items.find(g => g.id == to.params.groupId)?.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ResponsiveWidthWrapper>
|
||||||
|
<h1 class="title has-text-grey-dark">
|
||||||
|
{{ $t('groups.groups') }}
|
||||||
|
</h1>
|
||||||
|
<div class="is-size-7-mobile">
|
||||||
|
{{ $t('groups.manage_groups_legend')}}
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 mb-6">
|
||||||
|
<RouterLink class="is-link mt-5" :to="{ name: 'createGroup' }">
|
||||||
|
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('groups.create_group') }}
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
<div v-if="!groups.isEmpty">
|
||||||
|
<div v-for="group in groups.withoutTheAllGroup" :key="group.id" class="group-item is-size-5 is-size-6-mobile">
|
||||||
|
{{ group.name }}
|
||||||
|
<!-- delete icon -->
|
||||||
|
<UseColorMode v-slot="{ mode }">
|
||||||
|
<button class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark' : 'is-white'" @click="groups.delete(group.id)" :title="$t('commons.delete')">
|
||||||
|
{{ $t('commons.delete') }}
|
||||||
|
</button>
|
||||||
|
</UseColorMode>
|
||||||
|
<!-- edit link -->
|
||||||
|
<RouterLink :to="{ name: 'editGroup', params: { groupId: group.id }}" class="has-text-grey px-1" :title="$t('commons.rename')">
|
||||||
|
<FontAwesomeIcon :icon="['fas', 'pen-square']" />
|
||||||
|
</RouterLink>
|
||||||
|
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ group.twofaccounts_count }} {{ $t('twofaccounts.accounts') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 is-size-7 is-pulled-right">
|
||||||
|
{{ $t('groups.deleting_group_does_not_delete_accounts')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isFetching && groups.isEmpty" class="has-text-centered">
|
||||||
|
<span class="is-size-4">
|
||||||
|
<FontAwesomeIcon :icon="['fas', 'spinner']" spin />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- footer -->
|
||||||
|
<VueFooter :showButtons="true">
|
||||||
|
<!-- close button -->
|
||||||
|
<p class="control">
|
||||||
|
<UseColorMode v-slot="{ mode }">
|
||||||
|
<RouterLink id="btnClose" :to="{ name: 'accounts' }" class="button is-rounded" :class="{'is-dark' : mode == 'dark'}">{{ $t('commons.close') }}</RouterLink>
|
||||||
|
</UseColorMode>
|
||||||
|
</p>
|
||||||
|
</VueFooter>
|
||||||
|
</ResponsiveWidthWrapper>
|
||||||
|
</template>
|
Loading…
Reference in New Issue
Block a user