mirror of
https://github.com/directus/directus.git
synced 2026-01-24 02:08:00 -05:00
add user invitation
This commit is contained in:
@@ -31,6 +31,11 @@
|
||||
"create_user": "Create User",
|
||||
"create_webhook": "Create Webhook",
|
||||
|
||||
"invite_users": "Invite Users",
|
||||
"add_to_invite_user": "admin@example.com, user@example.com...",
|
||||
"invite": "Invite",
|
||||
"emails": "Emails",
|
||||
|
||||
"connection_excellent": "Excellent Connection",
|
||||
"connection_good": "Good Connection",
|
||||
"connection_fair": "Fair Connection",
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-button rounded icon @click="userInviteModalActive = true" v-tooltip.bottom="$t('invite_users')">
|
||||
<v-icon name="person_add" />
|
||||
</v-button>
|
||||
|
||||
<v-button
|
||||
rounded
|
||||
icon
|
||||
@@ -55,6 +59,8 @@
|
||||
<settings-navigation />
|
||||
</template>
|
||||
|
||||
<users-invite v-model="userInviteModalActive" :role="primaryKey" />
|
||||
|
||||
<div class="roles">
|
||||
<v-notice v-if="adminEnabled" type="info">
|
||||
{{ $t('admins_have_all_permissions') }}
|
||||
@@ -88,6 +94,7 @@ import useItem from '@/composables/use-item';
|
||||
import { useUserStore } from '@/stores/';
|
||||
import RoleInfoSidebarDetail from './components/role-info-sidebar-detail.vue';
|
||||
import PermissionsOverview from './components/permissions-overview.vue';
|
||||
import UsersInvite from '@/views/private/components/users-invite';
|
||||
|
||||
type Values = {
|
||||
[field: string]: any;
|
||||
@@ -95,7 +102,7 @@ type Values = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'roles-item',
|
||||
components: { SettingsNavigation, RevisionsDrawerDetail, RoleInfoSidebarDetail, PermissionsOverview },
|
||||
components: { SettingsNavigation, RevisionsDrawerDetail, RoleInfoSidebarDetail, PermissionsOverview, UsersInvite },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: String,
|
||||
@@ -108,7 +115,7 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const userStore = useUserStore();
|
||||
|
||||
const userInviteModalActive = ref(false);
|
||||
const { primaryKey } = toRefs(props);
|
||||
|
||||
const { edits, item, saving, loading, error, save, remove, deleting, isBatch } = useItem(
|
||||
@@ -142,6 +149,7 @@ export default defineComponent({
|
||||
deleting,
|
||||
isBatch,
|
||||
adminEnabled,
|
||||
userInviteModalActive,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,10 @@
|
||||
<v-icon name="edit" outline />
|
||||
</v-button>
|
||||
|
||||
<v-button rounded icon @click="userInviteModalActive = true" v-tooltip.bottom="$t('invite_users')">
|
||||
<v-icon name="person_add" />
|
||||
</v-button>
|
||||
|
||||
<v-button rounded icon :to="addNewLink" v-tooltip.bottom="$t('create_user')">
|
||||
<v-icon name="add" />
|
||||
</v-button>
|
||||
@@ -58,6 +62,8 @@
|
||||
<users-navigation :current-role="queryFilters && queryFilters.role" />
|
||||
</template>
|
||||
|
||||
<users-invite v-model="userInviteModalActive" />
|
||||
|
||||
<component
|
||||
class="layout"
|
||||
ref="layoutRef"
|
||||
@@ -104,6 +110,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, PropType } from '@vue/composition-api';
|
||||
import UsersNavigation from '../components/navigation.vue';
|
||||
import UsersInvite from '@/views/private/components/users-invite';
|
||||
|
||||
import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
@@ -120,7 +127,7 @@ type Item = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'users-collection',
|
||||
components: { UsersNavigation, LayoutSidebarDetail, SearchInput },
|
||||
components: { UsersNavigation, LayoutSidebarDetail, SearchInput, UsersInvite },
|
||||
props: {
|
||||
queryFilters: {
|
||||
type: Object as PropType<Record<string, string>>,
|
||||
@@ -130,6 +137,7 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
const { roles } = useNavigation();
|
||||
const layoutRef = ref<LayoutComponent | null>(null);
|
||||
const userInviteModalActive = ref(false);
|
||||
|
||||
const selection = ref<Item[]>([]);
|
||||
|
||||
@@ -175,6 +183,7 @@ export default defineComponent({
|
||||
searchQuery,
|
||||
marked,
|
||||
clearFilters,
|
||||
userInviteModalActive,
|
||||
};
|
||||
|
||||
function useBatchDelete() {
|
||||
|
||||
4
app/src/views/private/components/users-invite/index.ts
Normal file
4
app/src/views/private/components/users-invite/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import UsersInvite from './users-invite.vue';
|
||||
|
||||
export { UsersInvite };
|
||||
export default UsersInvite;
|
||||
121
app/src/views/private/components/users-invite/users-invite.vue
Normal file
121
app/src/views/private/components/users-invite/users-invite.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<v-dialog :active="active" @toggle="$emit('toggle', $event)" @esc="$emit('toggle', false)">
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('invite_users') }}</v-card-title>
|
||||
|
||||
<v-card-text class="grid">
|
||||
<div class="field">
|
||||
<span class="type-label">{{ $t('emails') }}</span>
|
||||
<interface-tags
|
||||
v-model="emails"
|
||||
:placeholder="$t('add_to_invite_user')"
|
||||
icon-right="email"
|
||||
whitespace=""
|
||||
></interface-tags>
|
||||
</div>
|
||||
<div class="field" v-if="role === null">
|
||||
<span class="type-label">{{ $t('role') }}</span>
|
||||
<v-select v-model="roleSelected" :items="roles"></v-select>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-button secondary @click="$emit('toggle', false)">{{ $t('cancel') }}</v-button>
|
||||
<v-button @click="inviteUsers" :disabled="emails === null || emails.length === 0" :loading="loading">
|
||||
{{ $t('invite') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, PropType, watch } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import { useNotificationsStore } from '@/stores';
|
||||
import i18n from '@/lang';
|
||||
|
||||
export default defineComponent({
|
||||
model: {
|
||||
prop: 'active',
|
||||
event: 'toggle',
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const notifications = useNotificationsStore();
|
||||
const emails = ref<string[]>([]);
|
||||
const roles = ref<Record<string, any>[]>([]);
|
||||
const roleSelected = ref<string | null>(props.role);
|
||||
const loading = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
() => {
|
||||
loadRoles();
|
||||
}
|
||||
);
|
||||
|
||||
return { emails, inviteUsers, roles, roleSelected, loading };
|
||||
|
||||
async function inviteUsers() {
|
||||
loading.value = true;
|
||||
try {
|
||||
await Promise.all(
|
||||
emails.value.map((email) => {
|
||||
return api.post('/users/invite', {
|
||||
email,
|
||||
role: roleSelected.value,
|
||||
});
|
||||
})
|
||||
);
|
||||
emit('toggle', false);
|
||||
} catch (err) {
|
||||
notifications.add({
|
||||
title: i18n.t('server_error'),
|
||||
text: err.message,
|
||||
persist: true,
|
||||
type: 'error',
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRoles() {
|
||||
const response = await api.get('/roles');
|
||||
|
||||
roles.value = response.data.data.map((role: Record<string, any>) => ({
|
||||
text: role.name,
|
||||
value: role.id,
|
||||
}));
|
||||
|
||||
if (roles.value.length > 0 && roleSelected.value === null) {
|
||||
roleSelected.value = roles.value[0].value;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/styles/mixins/form-grid';
|
||||
|
||||
.v-card-text {
|
||||
--v-form-vertical-gap: 20px;
|
||||
|
||||
@include form-grid;
|
||||
}
|
||||
|
||||
.v-card-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user