Newer
Older
import { useUserStore } from "@/stores/users";
import { onMounted, reactive } from "vue";
import { RoleEnum, type UserOutExtended } from "@/client";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import BootstrapToast from "@/components/BootstrapToast.vue";
import { Toast } from "bootstrap";
import CreateUserModal from "@/components/admin/CreateUserModal.vue";
const userRepository = useUserStore();
type RoleList = RoleEnum[];
let successRoleToast: Toast | undefined;
let successInvitationToast: Toast | undefined;
const userState = reactive<{
loading: boolean;
newUserRoles: RoleList[];
editUserRoles: boolean[];
editedUser?: UserOutExtended;
errorMessage: string;
searchString: string;
userRoles: RoleEnum[];
searched: boolean;
}>({
loading: false,
users: [],
newUserRoles: [],
editUserRoles: [],
editedUser: undefined,
errorMessage: "",
searchString: "",
userRoles: [],
inspectUser: undefined,
searched: false,
});
function searchUsers() {
userState.loading = true;
userRepository
.fetchUsers(
userState.searchString ? userState.searchString : undefined,
userState.userRoles ? userState.userRoles : undefined,
)
.then((users) => {
userState.users = users;
userState.newUserRoles = users.map((user) => user.roles);
userState.editUserRoles = users.map(() => false);
})
.finally(() => {
userState.loading = false;
userState.searched = true;
});
}
function saveUserRoles(index: number) {
userState.loading = true;
userState.editedUser = userState.users[index];
userRepository
.updateUserRoles(userState.users[index].uid, userState.newUserRoles[index])
.then((user) => {
userState.users[index] = user;
userState.editUserRoles[index] = false;
})
.catch((err) => {
userState.errorMessage = err.body?.detail;
errorToast?.show();
})
.finally(() => {
userState.loading = false;
});
}
function resetUserRoles(index: number) {
userState.editUserRoles[index] = false;
userState.newUserRoles[index] = JSON.parse(
JSON.stringify(userState.users[index].roles),
);
}
function resendInvitationEmail(uid: string) {
userState.loading = true;
userRepository
.resendInvitationEmail(uid)
.then(() => {
successInvitationToast?.show();
})
.finally(() => {
userState.loading = false;
});
}
successRoleToast = new Toast("#change-role-success-toast");
successInvitationToast = new Toast("#resend-invitation-success-toast");
errorToast = new Toast("#change-role-error-toast");
});
<bootstrap-toast toast-id="resend-invitation-success-toast">
Successfully resend invitation email
</bootstrap-toast>
<bootstrap-toast toast-id="change-role-success-toast">
Successfully change roles of user {{ userState.editedUser?.display_name }}
</bootstrap-toast>
<bootstrap-toast toast-id="change-role-error-toast" color-class="danger">
<template #default
>Couldn't change the roles of
{{ userState.editedUser?.display_name }}
</template>
<template #body>Error: {{ userState.errorMessage }}</template>
</bootstrap-toast>
<create-user-modal modal-id="create-user-modal" />
class="row border-bottom mb-4 justify-content-between align-items-center pb-2 pe-2"
<h2 class="w-fit">Manage Users</h2>
<button
type="button"
class="btn btn-primary w-fit"
data-bs-toggle="modal"
data-bs-target="#create-user-modal"
>
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
</div>
<form @submit.prevent="searchUsers" id="admin-user-search-form">
<div class="d-flex justify-content-evenly align-content-center">
<div class="mx-2">
<label for="admin-user-state-select" class="form-label"
>User Roles</label
>
<select
v-model="userState.userRoles"
multiple
class="form-select mb-4 w-fit"
id="admin-user-state-select"
>
<option v-for="role in Object.values(RoleEnum)" :key="role">
{{ role }}
</option>
</select>
</div>
<div class="flex-fill mx-2">
<label for="admin-user-name-search" class="form-label"
>Name of a user</label
>
<div class="input-group">
<div class="input-group-text">
<font-awesome-icon icon="fa-solid fa-search" />
</div>
<input
id="admin-user-name-search"
type="text"
class="form-control"
maxlength="32"
minlength="3"
v-model="userState.searchString"
placeholder="Search for user name"
/>
</div>
</div>
</div>
<button
type="submit"
class="btn btn-primary w-fit"
:disabled="userState.loading"
>
Search
</button>
</form>
<table
class="table table-striped align-middle caption-top"
v-if="userState.users"
>
<caption>
Displaying
{{
userState.users.length
}}
Users
</caption>
<thead>
<tr>
<th scope="col"><b>Name</b></th>
<th scope="col">UID</th>
<th
v-for="role in Object.values(RoleEnum)"
:key="role"
scope="col"
class="text-center"
>
{{ role.toUpperCase() }}
</th>
<th scope="col"></th>
</tr>
</thead>
<tbody v-if="userState.users.length === 0">
<tr>
<td colspan="8" class="text-center fst-italic fw-light">
<template v-if="userState.searched"
>No Users found with specified filters
</template>
<template v-else>Select a filter and search for Users</template>
</td>
</tr>
</tbody>
<tbody v-else>
<tr v-for="(user, index) in userState.users" :key="user.uid">
<th scope="row">{{ user.display_name }}</th>
<td>{{ user.uid }}</td>
<td
v-for="role in Object.values(RoleEnum)"
:key="role"
class="text-center"
>
<input
type="checkbox"
class="form-check-input"
:value="role"
:disabled="!userState.editUserRoles[index]"
v-model="userState.newUserRoles[index]"
/>
<td class="text-end" v-if="userState.editUserRoles[index]">
<div
class="btn-group btn-group-sm"
role="group"
aria-label="Basic mixed styles example"
>
<button
type="button"
class="btn btn-success"
:disabled="userState.loading"
@click="saveUserRoles(index)"
>
Save
</button>
<button
type="button"
class="btn btn-danger"
:disabled="userState.loading"
@click="resetUserRoles(index)"
>
Cancel
</button>
</div>
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
<div class="btn-group">
<button
type="button"
class="btn btn-sm btn-secondary"
@click="userState.editUserRoles[index] = true"
>
Edit Roles
</button>
<button
v-if="user.invitation_token_created_at"
type="button"
class="btn btn-secondary btn-sm dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<a
class="dropdown-item"
:class="{ disabled: userState.loading }"
:aria-disabled="userState.loading"
@click="resendInvitationEmail(user.uid)"
>Resend invitation email</a
>
</li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<style scoped></style>