Add invite user feature for admin
This commit is contained in:
63
front/src/InviteUser.vue
Normal file
63
front/src/InviteUser.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { postInviteUser, extractFormErrorFromField } from './api.js'
|
||||
|
||||
const emit = defineEmits(['invited'])
|
||||
|
||||
const error = ref(null)
|
||||
const username = ref('')
|
||||
|
||||
const titleError = computed(() => {
|
||||
return extractFormErrorFromField('Username', error.value)
|
||||
})
|
||||
|
||||
const errorExtracted = computed(() => {
|
||||
if (error && error.value && error.value) {
|
||||
return error.value['error']
|
||||
}
|
||||
})
|
||||
|
||||
function inviteUser() {
|
||||
postInviteUser(username.value).then((res) => {
|
||||
if (res.ok) {
|
||||
username.value = ''
|
||||
emit('invited')
|
||||
} else {
|
||||
res.json().then((json) => {
|
||||
error.value = json
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="subtitle">
|
||||
{{ $t('inviteuser.title') }}
|
||||
</h2>
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<input
|
||||
:class="'input is-medium ' + (error ? 'is-danger' : '')"
|
||||
type="text"
|
||||
maxlength="20"
|
||||
v-model="username"
|
||||
@keyup.enter="inviteUser"
|
||||
:placeholder="$t('inviteuser.placeholder')"
|
||||
/>
|
||||
<p v-if="titleError" class="help is-danger">{{ titleError }}</p>
|
||||
<p v-else-if="errorExtracted" class="help is-danger">{{ errorExtracted }}</p>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button @click="inviteUser" class="button is-medium">
|
||||
<span class="icon" :title="$t('inviteuser.invite')">
|
||||
<b-icon-plus />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
35
front/src/LinkCopy.vue
Normal file
35
front/src/LinkCopy.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { useTemplateRef, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
token: String,
|
||||
})
|
||||
|
||||
const linkElement = useTemplateRef('link')
|
||||
const copied = ref(false)
|
||||
|
||||
function onCopy() {
|
||||
navigator.clipboard
|
||||
.writeText(linkElement.value.href)
|
||||
.then(() => (copied.value = true))
|
||||
.catch((e) => console.log(e))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="centered">
|
||||
<a ref="link" :href="'/signup?invite=' + token">
|
||||
{{ $t('linkcopy.link') }}
|
||||
</a>
|
||||
<button @click="onCopy" class="ml-2 button is-small">
|
||||
<span v-if="!copied" class="icon" :title="$t('linkcopy.copy')">
|
||||
<b-icon-copy />
|
||||
</span>
|
||||
<span v-else class="icon" :title="$t('linkcopy.copied')">
|
||||
<b-icon-check />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,76 +1,16 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { getUsers } from './api.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const limit = 50
|
||||
const pageNumber = ref(1)
|
||||
import UsersTable from './UsersTable.vue'
|
||||
import InviteUser from './InviteUser.vue'
|
||||
|
||||
const offset = computed(() => (pageNumber.value - 1) * limit)
|
||||
|
||||
const data = ref(null)
|
||||
const error = ref(null)
|
||||
|
||||
let totalUsersNumber = computed(() =>
|
||||
typeof data != 'undefined' && data.value != null ? data.value['count'] : 0,
|
||||
)
|
||||
let pageTotal = computed(() => Math.ceil(totalUsersNumber.value / limit))
|
||||
|
||||
fetchData()
|
||||
|
||||
function fetchData() {
|
||||
getUsers(data, error, limit, offset.value)
|
||||
}
|
||||
|
||||
function pageChange(newPageNumber) {
|
||||
pageNumber.value = newPageNumber
|
||||
data.value = null
|
||||
fetchData()
|
||||
}
|
||||
//used to refresh
|
||||
const tableKey = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="error">{{ $t('usersmanagement.error', { error: error.message }) }}</div>
|
||||
<div v-else-if="data">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>#</th>
|
||||
<th>{{ $t('usersmanagement.name') }}</th>
|
||||
<th>{{ $t('usersmanagement.admin') }}</th>
|
||||
<th>
|
||||
<abbr :title="$t('usersmanagement.addedbookshelp')">{{
|
||||
$t('usersmanagement.addedbooks')
|
||||
}}</abbr>
|
||||
</th>
|
||||
<th>
|
||||
<abbr :title="$t('usersmanagement.bookshelp')">{{ $t('usersmanagement.books') }}</abbr>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in data.users" :key="user.id">
|
||||
<th class="numbercell">{{ user.id }}</th>
|
||||
<td>{{ user.name }}</td>
|
||||
<td class="boolcell"><input type="checkbox" disabled :checked="user.admin" /></td>
|
||||
<td class="numbercell">{{ user.addedbookscount }}</td>
|
||||
<td class="numbercell">{{ user.userbookscount }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Pagination
|
||||
:pageNumber="pageNumber"
|
||||
:pageTotal="pageTotal"
|
||||
maxItemDisplayed="11"
|
||||
@pageChange="pageChange"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>{{ $t('usersmanagement.loading') }}</div>
|
||||
<InviteUser @invited="tableKey += 1" class="mb-5" />
|
||||
<UsersTable :key="tableKey" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.boolcell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.numbercell {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
89
front/src/UsersTable.vue
Normal file
89
front/src/UsersTable.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { getUsers } from './api.js'
|
||||
import LinkCopy from './LinkCopy.vue'
|
||||
|
||||
const limit = 50
|
||||
const pageNumber = ref(1)
|
||||
|
||||
const offset = computed(() => (pageNumber.value - 1) * limit)
|
||||
|
||||
const data = ref(null)
|
||||
const error = ref(null)
|
||||
|
||||
let totalUsersNumber = computed(() =>
|
||||
typeof data != 'undefined' && data.value != null ? data.value['count'] : 0,
|
||||
)
|
||||
let pageTotal = computed(() => Math.ceil(totalUsersNumber.value / limit))
|
||||
|
||||
fetchData()
|
||||
|
||||
function fetchData() {
|
||||
getUsers(data, error, limit, offset.value)
|
||||
}
|
||||
|
||||
function pageChange(newPageNumber) {
|
||||
pageNumber.value = newPageNumber
|
||||
data.value = null
|
||||
fetchData()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="error">{{ $t('usersmanagement.error', { error: error.message }) }}</div>
|
||||
<div v-else-if="data">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>#</th>
|
||||
<th>{{ $t('usersmanagement.name') }}</th>
|
||||
<th>{{ $t('usersmanagement.active') }}</th>
|
||||
<th>{{ $t('usersmanagement.admin') }}</th>
|
||||
<th>
|
||||
<abbr :title="$t('usersmanagement.addedbookshelp')">{{
|
||||
$t('usersmanagement.addedbooks')
|
||||
}}</abbr>
|
||||
</th>
|
||||
<th>
|
||||
<abbr :title="$t('usersmanagement.bookshelp')">{{ $t('usersmanagement.books') }}</abbr>
|
||||
</th>
|
||||
<th>
|
||||
<abbr :title="$t('usersmanagement.invitelinkhelp')">{{
|
||||
$t('usersmanagement.invitelink')
|
||||
}}</abbr>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in data.users" :key="user.id">
|
||||
<th class="numbercell">{{ user.id }}</th>
|
||||
<td>{{ user.name }}</td>
|
||||
<td class="boolcell"><input type="checkbox" disabled :checked="user.activated" /></td>
|
||||
<td class="boolcell"><input type="checkbox" disabled :checked="user.admin" /></td>
|
||||
<td class="numbercell">{{ user.addedbookscount }}</td>
|
||||
<td class="numbercell">{{ user.userbookscount }}</td>
|
||||
<td>
|
||||
<LinkCopy v-if="user.invitetoken" :token="user.invitetoken" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Pagination
|
||||
:pageNumber="pageNumber"
|
||||
:pageTotal="pageTotal"
|
||||
maxItemDisplayed="11"
|
||||
@pageChange="pageChange"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>{{ $t('usersmanagement.loading') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.boolcell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.numbercell {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -151,6 +151,10 @@ export function postCollectionChangePosition(collectionId, itemId, position) {
|
||||
)
|
||||
}
|
||||
|
||||
export function postInviteUser(username) {
|
||||
return genericPayloadCall('/ws/admin/inviteuser', { username: username }, 'POST')
|
||||
}
|
||||
|
||||
export function deleteCollectionItem(itemId) {
|
||||
return deleteApiCall('/ws/collection/item/' + itemId)
|
||||
}
|
||||
|
||||
@@ -105,11 +105,24 @@
|
||||
"usersmanagement": {
|
||||
"error": "Error when loading users: {error}",
|
||||
"name": "Name",
|
||||
"active": "Active ?",
|
||||
"admin": "Is Admin ?",
|
||||
"addedbooks": "Added Books",
|
||||
"addedbookshelp": "Number of books the user created or imported.",
|
||||
"invitelink": "Invite Link",
|
||||
"invitelinkhelp": "Send this link to the user so they can create their account.",
|
||||
"books": "Books Number",
|
||||
"bookshelp": "Total number of books of the user.",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"linkcopy": {
|
||||
"link": "Link",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied"
|
||||
},
|
||||
"inviteuser": {
|
||||
"title": "Invite user",
|
||||
"placeholder": "Username",
|
||||
"invite": "Invite"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,11 +105,24 @@
|
||||
"usersmanagement": {
|
||||
"error": "Erreur pendant le chargement des utilisateurs: {error}",
|
||||
"name": "Nom",
|
||||
"active": "Activé ?",
|
||||
"admin": "Administrateur ?",
|
||||
"addedbooks": "Livres Ajoutés",
|
||||
"addedbookshelp": "Nombre de livres créés ou importés par l'utilisateur.",
|
||||
"invitelink": "Lien d'invitation",
|
||||
"invitelinkhelp": "Lien à envoyer à l'utilisateur pour l'activation de son compte.",
|
||||
"books": "Nombre De Livres",
|
||||
"bookshelp": "Le nombre total de livres de cet utilisateur.",
|
||||
"loading": "Chargement..."
|
||||
},
|
||||
"linkcopy": {
|
||||
"link": "Lien",
|
||||
"copy": "Copier",
|
||||
"copied": "Copié"
|
||||
},
|
||||
"inviteuser": {
|
||||
"title": "Inviter un nouvel utilisateur",
|
||||
"placeholder": "Nom d'utilisateur",
|
||||
"invite": "Inviter"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user