diff --git a/lang/en.json b/lang/en.json
index 2df4079a..c77a010d 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -26,6 +26,9 @@
"save": "Save",
"create": "Create",
"cancel": "Cancel",
+ "accept": "Accept",
+ "decline": "Decline",
+ "welcome": "Welcome",
"update": "Update",
"deselect": "Deselect",
"download": "Download",
diff --git a/resources/scripts/admin/admin-router.js b/resources/scripts/admin/admin-router.js
index ffc3825d..18ab534a 100644
--- a/resources/scripts/admin/admin-router.js
+++ b/resources/scripts/admin/admin-router.js
@@ -136,7 +136,16 @@ const AdminUpdateApp = () =>
const AdminFileDisk = () =>
import('@/scripts/admin/views/settings/FileDiskSetting.vue')
+const NoCompanyView = () =>
+ import('@/scripts/admin/views/NoCompanyView.vue')
+
export default [
+ {
+ path: '/admin/no-company',
+ name: 'no.company',
+ component: NoCompanyView,
+ meta: { requiresAuth: true },
+ },
{
path: '/installation',
component: LayoutInstallation,
diff --git a/resources/scripts/admin/components/modal-components/InviteMemberModal.vue b/resources/scripts/admin/components/modal-components/InviteMemberModal.vue
new file mode 100644
index 00000000..0e2ccb26
--- /dev/null
+++ b/resources/scripts/admin/components/modal-components/InviteMemberModal.vue
@@ -0,0 +1,132 @@
+
+
+
+
+ {{ $t('members.invite_member') }}
+
+
+
+
+
+
+
+
+
diff --git a/resources/scripts/admin/stores/global.js b/resources/scripts/admin/stores/global.js
index a40c5de0..1787fd59 100644
--- a/resources/scripts/admin/stores/global.js
+++ b/resources/scripts/admin/stores/global.js
@@ -70,6 +70,13 @@ export const useGlobalStore = (useWindow = false) => {
moduleStore.apiToken = response.data.global_settings.api_token
moduleStore.enableModules = response.data.modules
+ // invitation store
+ if (response.data.pending_invitations) {
+ const { useInvitationStore } = await import('@/scripts/admin/stores/invitation')
+ const invitationStore = useInvitationStore()
+ invitationStore.setPendingInvitations(response.data.pending_invitations)
+ }
+
// company store
companyStore.companies = response.data.companies
companyStore.selectedCompany = response.data.current_company
diff --git a/resources/scripts/admin/stores/invitation.js b/resources/scripts/admin/stores/invitation.js
new file mode 100644
index 00000000..da551120
--- /dev/null
+++ b/resources/scripts/admin/stores/invitation.js
@@ -0,0 +1,56 @@
+import { defineStore } from 'pinia'
+import { useNotificationStore } from '@/scripts/stores/notification'
+import { useGlobalStore } from '@/scripts/admin/stores/global'
+import http from '@/scripts/http'
+
+export const useInvitationStore = defineStore('invitation', {
+ state: () => ({
+ pendingInvitations: [],
+ }),
+
+ actions: {
+ setPendingInvitations(invitations) {
+ this.pendingInvitations = invitations
+ },
+
+ async fetchPending() {
+ const response = await http.get('/api/v1/invitations/pending')
+ this.pendingInvitations = response.data.invitations
+ return response
+ },
+
+ async accept(token) {
+ const notificationStore = useNotificationStore()
+ const globalStore = useGlobalStore()
+
+ const response = await http.post(`/api/v1/invitations/${token}/accept`)
+
+ notificationStore.showNotification({
+ type: 'success',
+ message: 'Invitation accepted!',
+ })
+
+ // Refresh bootstrap to get updated companies list
+ await globalStore.bootstrap()
+
+ return response
+ },
+
+ async decline(token) {
+ const notificationStore = useNotificationStore()
+
+ const response = await http.post(`/api/v1/invitations/${token}/decline`)
+
+ this.pendingInvitations = this.pendingInvitations.filter(
+ (inv) => inv.token !== token
+ )
+
+ notificationStore.showNotification({
+ type: 'success',
+ message: 'Invitation declined.',
+ })
+
+ return response
+ },
+ },
+})
diff --git a/resources/scripts/admin/stores/members.js b/resources/scripts/admin/stores/members.js
index 605b2af6..d223175a 100644
--- a/resources/scripts/admin/stores/members.js
+++ b/resources/scripts/admin/stores/members.js
@@ -12,6 +12,7 @@ export const useMembersStore = (useWindow = false) => {
roles: [],
users: [],
totalUsers: 0,
+ pendingInvitations: [],
currentUser: null,
selectAllField: false,
selectedUsers: [],
@@ -226,6 +227,63 @@ export const useMembersStore = (useWindow = false) => {
this.selectAllField = true
}
},
+
+ fetchPendingInvitations() {
+ return new Promise((resolve, reject) => {
+ http
+ .get('/api/v1/company-invitations')
+ .then((response) => {
+ this.pendingInvitations = response.data.invitations
+ resolve(response)
+ })
+ .catch((err) => {
+ handleError(err)
+ reject(err)
+ })
+ })
+ },
+
+ inviteMember(data) {
+ return new Promise((resolve, reject) => {
+ http
+ .post('/api/v1/company-invitations', data)
+ .then((response) => {
+ const notificationStore = useNotificationStore()
+ notificationStore.showNotification({
+ type: 'success',
+ message: global.t('members.invited_message'),
+ })
+ this.fetchPendingInvitations()
+ resolve(response)
+ })
+ .catch((err) => {
+ handleError(err)
+ reject(err)
+ })
+ })
+ },
+
+ cancelInvitation(id) {
+ return new Promise((resolve, reject) => {
+ http
+ .delete(`/api/v1/company-invitations/${id}`)
+ .then((response) => {
+ const notificationStore = useNotificationStore()
+ notificationStore.showNotification({
+ type: 'success',
+ message: global.t('members.invitation_cancelled'),
+ })
+ this.pendingInvitations = this.pendingInvitations.filter(
+ (inv) => inv.id !== id
+ )
+ resolve(response)
+ })
+ .catch((err) => {
+ handleError(err)
+ reject(err)
+ })
+ })
+ },
},
})()
}
diff --git a/resources/scripts/admin/views/NoCompanyView.vue b/resources/scripts/admin/views/NoCompanyView.vue
new file mode 100644
index 00000000..77035b95
--- /dev/null
+++ b/resources/scripts/admin/views/NoCompanyView.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+ {{ $t('general.welcome') }}
+
+
+
+
+
+
+ {{ $t('members.pending_invitations') }}
+
+
+
+
+
+
+ {{ invitation.company?.name }}
+
+
+ {{ invitation.role?.title }} ·
+ {{ $t('members.invited_by') }}: {{ invitation.invited_by?.name }}
+
+
+
+
+ {{ $t('general.accept') }}
+
+
+ {{ $t('general.decline') }}
+
+
+
+
+
+
+
+
+
+
+ You don't belong to any company yet. Ask your administrator to invite you.
+
+
+
+
+
+
+ {{ $t('navigation.logout') }}
+
+
+
+
+
+
+
diff --git a/resources/scripts/admin/views/members/Index.vue b/resources/scripts/admin/views/members/Index.vue
index 724a9e9d..cf4b4f87 100644
--- a/resources/scripts/admin/views/members/Index.vue
+++ b/resources/scripts/admin/views/members/Index.vue
@@ -27,7 +27,22 @@
+
+
+
+ {{ $t('members.invite_member') }}
+
+
+
- {{ $t('members.add_user') }}
+ {{ $t('members.add_member') }}
@@ -181,6 +196,47 @@
+
+
+
+
+ {{ $t('members.pending_invitations') }}
+
+
+
+
+
+
+ {{ invitation.email }}
+
+
+ {{ invitation.role?.title }} ·
+ {{ $t('members.invited_by') }}: {{ invitation.invited_by?.name }}
+
+
+
+ {{ $t('members.cancel_invitation') }}
+
+
+
+
+
+
+
@@ -194,6 +250,7 @@ import { useDialogStore } from '@/scripts/stores/dialog'
import { useUserStore } from '@/scripts/admin/stores/user'
import AstronautIcon from '@/scripts/components/icons/empty/AstronautIcon.vue'
import UserDropdown from '@/scripts/admin/components/dropdowns/MemberIndexDropdown.vue'
+import InviteMemberModal from '@/scripts/admin/components/modal-components/InviteMemberModal.vue'
import abilities from '@/scripts/admin/stub/abilities'
const notificationStore = useNotificationStore()
@@ -204,6 +261,7 @@ const userStore = useUserStore()
const router = useRouter()
let showFilters = ref(false)
+let showInviteModal = ref(false)
let isFetchingInitialData = ref(true)
let id = ref(null)
let sortedBy = ref('created_at')
@@ -277,8 +335,13 @@ watch(
onMounted(() => {
usersStore.fetchUsers()
usersStore.fetchRoles()
+ usersStore.fetchPendingInvitations()
})
+function cancelInvitation(id) {
+ usersStore.cancelInvitation(id)
+}
+
onUnmounted(() => {
if (usersStore.selectAllField) {
usersStore.selectAllUsers()
diff --git a/resources/scripts/components/CompanySwitcher.vue b/resources/scripts/components/CompanySwitcher.vue
index 78aedacc..31e50463 100644
--- a/resources/scripts/components/CompanySwitcher.vue
+++ b/resources/scripts/components/CompanySwitcher.vue
@@ -132,6 +132,58 @@
+
+
+
+
+
+
+
+ {{ initGenerator(invitation.company?.name || '?') }}
+
+
+ {{ invitation.company?.name }}
+ {{ invitation.role?.title }}
+
+
+
+
+
+
+
+
+
+