@drax/identity-vue 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +61 -0
  2. package/package.json +65 -0
  3. package/src/assets/base.css +86 -0
  4. package/src/assets/logo.svg +1 -0
  5. package/src/assets/main.css +35 -0
  6. package/src/combobox/PermissionCombobox.vue +38 -0
  7. package/src/combobox/RoleCombobox.vue +37 -0
  8. package/src/components/IdentityChangeOwnPassword/IdentityChangeOwnPassword.vue +139 -0
  9. package/src/components/IdentityLogin/IdentityLogin.vue +124 -0
  10. package/src/components/IdentityProfileAvatar/IdentityProfileAvatar.vue +38 -0
  11. package/src/components/IdentityProfileDrawer/IdentityProfileDrawer.vue +82 -0
  12. package/src/components/IdentityProfileView/IdentityProfileView.vue +31 -0
  13. package/src/components/PermissionSelector/PermissionSelector.vue +89 -0
  14. package/src/composables/useAuth.ts +53 -0
  15. package/src/composables/useI18nValidation.ts +11 -0
  16. package/src/composables/useRole.ts +93 -0
  17. package/src/composables/useUser.ts +96 -0
  18. package/src/cruds/role-crud/RoleCrud.vue +165 -0
  19. package/src/cruds/role-crud/RoleList.vue +110 -0
  20. package/src/cruds/user-crud/UserCrud.vue +222 -0
  21. package/src/cruds/user-crud/UserList.vue +100 -0
  22. package/src/forms/RoleForm.vue +44 -0
  23. package/src/forms/UserCreateForm.vue +101 -0
  24. package/src/forms/UserEditForm.vue +87 -0
  25. package/src/forms/UserPasswordForm.vue +81 -0
  26. package/src/i18n/I18n.ts +10 -0
  27. package/src/i18n/I18nMessages.ts +21 -0
  28. package/src/icons/IconCommunity.vue +7 -0
  29. package/src/index.ts +51 -0
  30. package/src/pages/RoleCrudPage.vue +12 -0
  31. package/src/pages/UserCrudPage.vue +12 -0
  32. package/src/stores/auth/AuthStore.ts +38 -0
  33. package/src/views/RoleView.vue +32 -0
  34. package/src/views/UserView.vue +34 -0
@@ -0,0 +1,110 @@
1
+ <script setup lang="ts">
2
+
3
+ import {defineProps, ref} from "vue";
4
+ import {useRole} from "../../composables/useRole";
5
+ import {useAuth} from "../../composables/useAuth";
6
+ import {useI18n} from "vue-i18n";
7
+
8
+ const {hasPermission} = useAuth()
9
+ const {paginateRole} = useRole()
10
+ const {t} = useI18n()
11
+
12
+ const props = defineProps({
13
+ filterEnable: {
14
+ type: Boolean,
15
+ default: false,
16
+ }
17
+ })
18
+
19
+ const itemsPerPage = ref(5)
20
+ const page = ref(1)
21
+ const headers = ref([
22
+ //{title: 'ID', align: 'start', sortable: false, key: 'id'},
23
+ { title: t('role.name'), key: 'name', align: 'start' },
24
+ { title: t('role.permissions'), key: 'permissions', align: 'start' },
25
+ { title: t('role.readonly'), key: 'readonly', align: 'start' },
26
+ { title: '', key: 'actions', align: 'start', minWidth: '150px' },
27
+ ])
28
+
29
+ const serverItems = ref([])
30
+ const totalItems = ref(0)
31
+ const loading = ref(false)
32
+ const search = ref('')
33
+
34
+ async function loadItems(){
35
+ try{
36
+ loading.value = true
37
+ const r = await paginateRole(page.value,itemsPerPage.value, search.value)
38
+ serverItems.value = r.items
39
+ totalItems.value = r.total
40
+ }catch (e){
41
+ console.error(e)
42
+ }finally {
43
+ loading.value = false
44
+ }
45
+ }
46
+
47
+
48
+
49
+ defineExpose({
50
+ loadItems
51
+ });
52
+
53
+ </script>
54
+
55
+ <template>
56
+ <v-data-table-server
57
+ v-if="hasPermission('user:manage')"
58
+ v-model:items-per-page="itemsPerPage"
59
+ v-model:page="page"
60
+ :headers="headers"
61
+ :items="serverItems"
62
+ :items-length="totalItems"
63
+ :loading="loading"
64
+ :search="search"
65
+ item-value="name"
66
+ @update:options="loadItems"
67
+ >
68
+ <template v-slot:top>
69
+ <v-toolbar border density="compact" v-if="filterEnable" class="grey-lighten-1">
70
+ <v-toolbar-title>{{ $t('action.filter') }}</v-toolbar-title>
71
+ <v-spacer></v-spacer>
72
+ <v-text-field v-model="search" hide-details
73
+ density="compact" class="mr-2"
74
+ variant="outlined"
75
+ append-inner-icon="mdi-magnify"
76
+ :label="$t('action.search')"
77
+ single-line clearable @click:clear="() => search = ''"
78
+ />
79
+
80
+ </v-toolbar>
81
+ </template>
82
+ <template v-slot:item.permissions="{ value }" >
83
+ <v-chip v-for="permission in value"
84
+ :key="permission" color="green"
85
+ class="ma-1"
86
+ >
87
+ {{$t ? $t(`permission.${permission}`) : permission }}
88
+ </v-chip>
89
+ </template>
90
+
91
+ <template v-slot:item.readonly="{ value }" >
92
+ <v-chip v-if="value" color="red" >
93
+ <v-icon class="mdi mdi-pencil-off-outline"></v-icon>
94
+ </v-chip>
95
+ <v-chip v-else color="green">
96
+ <v-icon class="mdi mdi-pencil-outline"></v-icon>
97
+ </v-chip>
98
+ </template>
99
+
100
+ <template v-slot:item.actions="{item}" >
101
+ <v-btn v-if="hasPermission('user:update')" :disabled="item.readonly" icon="mdi-pencil" variant="text" color="primary" @click="$emit('toEdit', item)"></v-btn>
102
+ <v-btn v-if="hasPermission('user:delete')" :disabled="item.readonly" icon="mdi-delete" class="mr-1" variant="text" color="red" @click="$emit('toDelete', item)"></v-btn>
103
+ </template>
104
+
105
+ </v-data-table-server>
106
+ </template>
107
+
108
+ <style scoped>
109
+
110
+ </style>
@@ -0,0 +1,222 @@
1
+ <script setup lang="ts">
2
+ import {computed, ref} from 'vue'
3
+ import UserList from "./UserList.vue";
4
+ import {useUser} from "../../composables/useUser";
5
+ import type {IUser} from "@drax/identity-front";
6
+ import type {IUserCreate, IUserUpdate, IUserPassword} from "@drax/identity-front/src/interfaces/IUser";
7
+ import UserCreateForm from "../../forms/UserCreateForm.vue";
8
+ import UserEditForm from "../../forms/UserEditForm.vue";
9
+ import UserPasswordForm from "../../forms/UserPasswordForm.vue";
10
+ import UserView from "../../views/UserView.vue";
11
+
12
+ const {createUser, editUser, changeUserPassword, deleteUser, loading, userError, inputErrors} = useUser()
13
+
14
+ interface UserList {
15
+ loadItems: () => void;
16
+ items: IUser[];
17
+ }
18
+
19
+ type DialogMode = 'create' | 'edit' | 'delete' | 'changePassword' | null;
20
+
21
+
22
+ let dialog = ref(false);
23
+ let dialogMode = ref<DialogMode>(null);
24
+ let dialogTitle = ref('');
25
+ const userList = ref<UserList | null>(null);
26
+ let createForm = ref<IUserCreate>({name: "", username: "", password: "", email: "", phone: "", role: "", active: true})
27
+ let editForm = ref<IUserUpdate>({name: "", username: "", email: "", phone: "", role: "", active: true})
28
+ let passwordForm = ref<IUserPassword>({newPassword: "", confirmPassword: ""})
29
+ let passwordChanged = ref(false);
30
+ let target = ref<IUser>();
31
+ let targetId = ref<string>('');
32
+ let actionButtonEnable = ref(true);
33
+ let filterEnable = ref(false);
34
+
35
+ function cancel() {
36
+ dialog.value = false
37
+ inputErrors.value = {}
38
+ userError.value = '';
39
+ dialogMode.value = null
40
+ dialogTitle.value = ''
41
+ targetId.value = ''
42
+ target.value = undefined
43
+ actionButtonEnable.value = true
44
+ }
45
+
46
+ async function save() {
47
+
48
+ try {
49
+ if (dialogMode.value === 'create') {
50
+ await createUser(createForm.value)
51
+ } else if (dialogMode.value === 'edit') {
52
+ await editUser(targetId.value, editForm.value)
53
+ } else if (dialogMode.value === 'changePassword') {
54
+ if (passwordForm.value.newPassword === passwordForm.value.confirmPassword) {
55
+ passwordChanged.value = await changeUserPassword(targetId.value, passwordForm.value.newPassword)
56
+ actionButtonEnable.value = false
57
+ return
58
+ } else {
59
+ return
60
+ }
61
+ } else if (dialogMode.value === 'delete') {
62
+ await deleteUser(targetId.value)
63
+ }
64
+ dialog.value = false
65
+ inputErrors.value = {}
66
+ userError.value = '';
67
+ if (userList.value !== null) {
68
+ userList.value.loadItems()
69
+ }
70
+ } catch (e) {
71
+ console.error(e)
72
+ if (e instanceof Error) {
73
+ userError.value = e.message
74
+ }
75
+ }
76
+ }
77
+
78
+ let buttonText = computed(() => {
79
+ switch (dialogMode.value) {
80
+ case 'create':
81
+ return 'action.create'
82
+ case 'edit':
83
+ return 'action.update'
84
+ case 'delete':
85
+ return 'action.delete'
86
+ default:
87
+ return 'action.sent'
88
+ }
89
+ })
90
+
91
+ function toCreate() {
92
+ actionButtonEnable.value = true
93
+ dialogMode.value = 'create';
94
+ dialogTitle.value = 'user.creating';
95
+ createForm.value = {name: "", username: "", password: "", email: "", phone: "", role: "", active: true}
96
+ dialog.value = true;
97
+ }
98
+
99
+ function toEdit(item: IUser) {
100
+ actionButtonEnable.value = true
101
+ dialogMode.value = 'edit';
102
+ dialogTitle.value = 'user.updating';
103
+ const {id, ...rest} = item;
104
+ targetId.value = id;
105
+ rest.role = rest.role ? rest.role.id : null
106
+ editForm.value = {...rest}
107
+ dialog.value = true;
108
+ }
109
+
110
+ function toDelete(item: IUser) {
111
+ actionButtonEnable.value = true
112
+ dialogMode.value = 'delete';
113
+ dialogTitle.value = 'user.deleting';
114
+ target.value = item
115
+ const {id} = item;
116
+ targetId.value = id;
117
+ dialog.value = true;
118
+ }
119
+
120
+ function toChangePassword(item: IUser) {
121
+ actionButtonEnable.value = true
122
+ dialogMode.value = 'changePassword';
123
+ dialogTitle.value = 'user.changingPassword';
124
+ target.value = item
125
+ const {id} = item;
126
+ targetId.value = id;
127
+ dialog.value = true;
128
+ }
129
+
130
+
131
+ </script>
132
+
133
+ <template>
134
+ <v-container>
135
+
136
+ <v-sheet border rounded>
137
+ <v-toolbar>
138
+ <v-toolbar-title>{{ $t('user.managing') }}</v-toolbar-title>
139
+ <v-spacer></v-spacer>
140
+ <v-btn icon @click="filterEnable = !filterEnable">
141
+ <v-icon>{{ filterEnable ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
142
+ </v-btn>
143
+ <v-btn color="primary" @click="toCreate">
144
+ {{$t('action.create') }}
145
+ </v-btn>
146
+ </v-toolbar>
147
+ <v-theme-provider with-background class="pa-2 rounded-b">
148
+ <UserList
149
+ ref="userList"
150
+ @toEdit="toEdit"
151
+ @toDelete="toDelete"
152
+ @toChangePassword="toChangePassword"
153
+ :filterEnable="filterEnable"
154
+ />
155
+ </v-theme-provider>
156
+ </v-sheet>
157
+
158
+ <v-dialog v-model="dialog" max-width="800">
159
+ <v-sheet border>
160
+ <v-toolbar>
161
+ <v-toolbar-title>{{ $t(dialogTitle) }}</v-toolbar-title>
162
+ </v-toolbar>
163
+ <v-card class="pa-10">
164
+ <v-card-text v-if="userError">
165
+ <v-alert type="error">{{ userError }}</v-alert>
166
+ </v-card-text>
167
+ <v-card-text>
168
+ <UserCreateForm
169
+ v-if="dialogMode === 'create'"
170
+ v-model="createForm"
171
+ :inputErrors="inputErrors"
172
+ />
173
+
174
+ <UserEditForm
175
+ v-if="dialogMode === 'edit'"
176
+ v-model="editForm"
177
+ :inputErrors="inputErrors"
178
+ />
179
+
180
+ <UserView v-if="dialogMode === 'delete'
181
+ && target" :user="target"
182
+ />
183
+
184
+
185
+ <UserPasswordForm
186
+ v-if="dialogMode === 'changePassword'&& target"
187
+ v-model="passwordForm"
188
+ :inputErrors="inputErrors"
189
+ :passwordChanged="passwordChanged"
190
+ />
191
+
192
+
193
+ </v-card-text>
194
+ <v-card-actions>
195
+ <v-spacer></v-spacer>
196
+ <v-btn
197
+ variant="text"
198
+ @click="cancel"
199
+ :loading="loading">
200
+ {{ actionButtonEnable ? $t('action.cancel') : $t('action.close') }}
201
+ </v-btn>
202
+ <v-btn
203
+ v-if="actionButtonEnable"
204
+ variant="flat"
205
+ :color="dialogMode==='delete' ? 'red' : 'primary'"
206
+ @click="save"
207
+ :loading="loading"
208
+ >
209
+ {{ $t(buttonText) }}
210
+ </v-btn>
211
+ </v-card-actions>
212
+
213
+ </v-card>
214
+ </v-sheet>
215
+ </v-dialog>
216
+
217
+ </v-container>
218
+ </template>
219
+
220
+ <style scoped>
221
+
222
+ </style>
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+
3
+ import {ref, defineProps} from "vue";
4
+ import {useUser} from "../../composables/useUser";
5
+ import {useAuth} from "../../composables/useAuth";
6
+ import {useI18n} from "vue-i18n";
7
+
8
+ const {hasPermission} = useAuth()
9
+ const {paginateUser} = useUser()
10
+ const {t} = useI18n()
11
+
12
+ const props = defineProps({
13
+ filterEnable: {
14
+ type: Boolean,
15
+ default: false,
16
+ }
17
+ })
18
+
19
+ const itemsPerPage = ref(5)
20
+ const page = ref(1)
21
+ const headers = ref([
22
+ //{title: 'ID', align: 'start', sortable: false, key: 'id'},
23
+ { title: t('user.name'), key: 'name', align: 'start' },
24
+ { title: t('user.username'), key: 'username', align: 'start' },
25
+ { title: t('user.email'), key: 'email', align: 'start' },
26
+ { title: t('user.role'), key: 'role.name', align: 'start' },
27
+ { title: t('user.active'), key: 'active', align: 'start' },
28
+ { title: '', key: 'actions', align: 'start', fixed:true },
29
+ ])
30
+
31
+ const serverItems = ref([])
32
+ const totalItems = ref(0)
33
+ const loading = ref(false)
34
+ const search = ref('')
35
+
36
+ async function loadItems(){
37
+ try{
38
+ loading.value = true
39
+ const r = await paginateUser(page.value,itemsPerPage.value, search.value)
40
+ serverItems.value = r.items
41
+ totalItems.value = r.total
42
+ }catch (e){
43
+ console.error(e)
44
+ }finally {
45
+ loading.value = false
46
+ }
47
+ }
48
+
49
+
50
+
51
+ defineExpose({
52
+ loadItems
53
+ });
54
+
55
+ </script>
56
+
57
+ <template>
58
+ <v-data-table-server
59
+ v-if="hasPermission('user:manage')"
60
+ v-model:items-per-page="itemsPerPage"
61
+ v-model:page="page"
62
+ :headers="headers"
63
+ :items="serverItems"
64
+ :items-length="totalItems"
65
+ :loading="loading"
66
+ :search="search"
67
+ item-value="name"
68
+ @update:options="loadItems"
69
+ >
70
+ <template v-slot:top>
71
+ <v-toolbar border density="compact" v-if="filterEnable" class="grey-lighten-1">
72
+ <v-toolbar-title>{{ $t('action.filter') }}</v-toolbar-title>
73
+ <v-spacer></v-spacer>
74
+ <v-text-field v-model="search" hide-details
75
+ density="compact" class="mr-2"
76
+ variant="outlined"
77
+ append-inner-icon="mdi-magnify"
78
+ :label="$t('action.search')"
79
+ single-line clearable @click:clear="() => search = ''"
80
+ />
81
+ </v-toolbar>
82
+ </template>
83
+ <template v-slot:item.active="{ value }" >
84
+ <v-chip :color="value ? 'green':'red'" >
85
+ {{ value ? 'Active' : 'Inactive' }}
86
+ </v-chip>
87
+ </template>
88
+
89
+ <template v-slot:item.actions="{item}" >
90
+ <v-btn v-if="hasPermission('user:update')" icon="mdi-pencil" variant="text" color="primary" @click="$emit('toEdit', item)"></v-btn>
91
+ <v-btn v-if="hasPermission('user:update')" icon="mdi-lock" variant="text" color="orange" @click="$emit('toChangePassword', item)"></v-btn>
92
+ <v-btn v-if="hasPermission('user:delete')" icon="mdi-delete" class="mr-1" variant="text" color="red" @click="$emit('toDelete', item)"></v-btn>
93
+ </template>
94
+
95
+ </v-data-table-server>
96
+ </template>
97
+
98
+ <style scoped>
99
+
100
+ </style>
@@ -0,0 +1,44 @@
1
+ <script setup lang="ts">
2
+ import {defineModel, type PropType} from "vue";
3
+ import type {IRole} from "@drax/identity-front";
4
+ import {useI18nValidation} from "../composables/useI18nValidation";
5
+ import type {IClientInputError} from "@drax/common-front";
6
+ import PermissionSelector from "../components/PermissionSelector/PermissionSelector.vue";
7
+
8
+ const {$ta} = useI18nValidation()
9
+
10
+ const props = defineProps({
11
+ inputErrors: {
12
+ type: Object as PropType<IClientInputError>,
13
+ default: () => ({name: "", permissions: "", readonly: ""})
14
+ }
15
+ })
16
+
17
+ const form = defineModel<IRole>({
18
+ type: Object,
19
+ default: () => ({name: "", permissions: [], readonly: false})
20
+ })
21
+
22
+ </script>
23
+
24
+ <template>
25
+ <form>
26
+ <v-text-field
27
+ variant="outlined"
28
+ id="name-input"
29
+ label="Name"
30
+ v-model="form.name"
31
+ prepend-inner-icon="mdi-card-account-details"
32
+ required
33
+ :error-messages="$ta(inputErrors.name)"
34
+ ></v-text-field>
35
+
36
+
37
+ <PermissionSelector v-model="form.permissions"></PermissionSelector>
38
+
39
+ </form>
40
+ </template>
41
+
42
+ <style scoped>
43
+
44
+ </style>
@@ -0,0 +1,101 @@
1
+ <script setup lang="ts">
2
+ import {ref, defineModel, type PropType} from "vue";
3
+ import RoleCombobox from "../combobox/RoleCombobox.vue";
4
+ import type {IClientInputError} from "@drax/common-front";
5
+ import type {IUserCreate} from "@drax/identity-front/src/interfaces/IUser";
6
+ import {useI18nValidation} from "../composables/useI18nValidation";
7
+
8
+ const {$ta} = useI18nValidation()
9
+
10
+ const props = defineProps({
11
+ inputErrors: {
12
+ type: Object as PropType<IClientInputError>,
13
+ default: () => ({name: "", username: "", password: "", email: "", phone: "", role: "", active: ""})
14
+ }
15
+ })
16
+
17
+ const form = defineModel<IUserCreate>({
18
+ type: Object,
19
+ default: () => ({name: "", username: "", password: "", email: "", phone: "", role: "", active: true})
20
+ })
21
+
22
+ let passwordVisibility = ref(false)
23
+
24
+ </script>
25
+
26
+ <template>
27
+ <form>
28
+
29
+ <v-text-field
30
+ variant="outlined"
31
+ id="name-input"
32
+ label="Name"
33
+ v-model="form.name"
34
+ prepend-inner-icon="mdi-card-account-details"
35
+ required
36
+ :error-messages="$ta(inputErrors.name)"
37
+ ></v-text-field>
38
+
39
+ <v-text-field
40
+ variant="outlined"
41
+ id="username-input"
42
+ label="Username"
43
+ v-model="form.username"
44
+ prepend-inner-icon="mdi-account-question"
45
+ required
46
+ autocomplete="new-username"
47
+ :error-messages="$ta(inputErrors.username)"
48
+ ></v-text-field>
49
+ <v-text-field
50
+ variant="outlined"
51
+ id="password-input"
52
+ label="Password"
53
+ v-model="form.password"
54
+ :type="passwordVisibility ? 'text': 'password'"
55
+ required
56
+ prepend-inner-icon="mdi-lock-outline"
57
+ :append-inner-icon="passwordVisibility ? 'mdi-eye-off': 'mdi-eye'"
58
+ @click:append-inner="passwordVisibility = !passwordVisibility"
59
+ autocomplete="new-password"
60
+ :error-messages="$ta(inputErrors.password)"
61
+ ></v-text-field>
62
+
63
+ <RoleCombobox
64
+ v-model="form.role"
65
+ :error-messages="$ta(inputErrors.role)"
66
+ ></RoleCombobox>
67
+
68
+ <v-text-field
69
+ v-model="form.email"
70
+ variant="outlined"
71
+ id="email-input"
72
+ label="Email"
73
+ prepend-inner-icon="mdi-email"
74
+ required
75
+ :error-messages="$ta(inputErrors.email)"
76
+ ></v-text-field>
77
+
78
+ <v-text-field
79
+ v-model="form.phone"
80
+ variant="outlined"
81
+ id="phone-input"
82
+ label="Phone"
83
+ prepend-inner-icon="mdi-phone"
84
+ required
85
+ :error-messages="$ta(inputErrors.phone)"
86
+ ></v-text-field>
87
+
88
+ <v-switch
89
+ id="active-input"
90
+ v-model="form.active"
91
+ color="primary"
92
+ label="Active"
93
+ :true-value="true"
94
+ :false-value="false"
95
+ ></v-switch>
96
+ </form>
97
+ </template>
98
+
99
+ <style scoped>
100
+
101
+ </style>
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ import {ref, defineModel, type PropType} from "vue";
3
+ import RoleCombobox from "../combobox/RoleCombobox.vue";
4
+ import type {IClientInputError} from "@drax/common-front";
5
+ import type {IUserUpdate} from "@drax/identity-front/src/interfaces/IUser";
6
+ import {useI18nValidation} from "../composables/useI18nValidation";
7
+ const {$ta} = useI18nValidation()
8
+
9
+ const props = defineProps({
10
+ inputErrors: {
11
+ type: Object as PropType<IClientInputError>,
12
+ default: () => ({name: "", username: "", email: "", phone: "", role: "", active: ""})
13
+ }
14
+ })
15
+
16
+ const form = defineModel<IUserUpdate>({
17
+ type: Object,
18
+ default: () => ({name: "", username: "", email: "", phone: "", role: "", active: true})
19
+ })
20
+
21
+
22
+ </script>
23
+
24
+ <template>
25
+ <form>
26
+ <v-sheet>
27
+ </v-sheet>
28
+
29
+ <v-text-field
30
+ variant="outlined"
31
+ id="name-input"
32
+ label="Name"
33
+ v-model="form.name"
34
+ prepend-inner-icon="mdi-card-account-details"
35
+ required
36
+ :error-messages="$ta(inputErrors.name)"
37
+ ></v-text-field>
38
+ <v-text-field
39
+ variant="outlined"
40
+ id="username-input"
41
+ label="Username"
42
+ v-model="form.username"
43
+ prepend-inner-icon="mdi-account-question"
44
+ required
45
+ :error-messages="$ta(inputErrors.username)"
46
+ autocomplete="new-username"
47
+ ></v-text-field>
48
+
49
+ <RoleCombobox
50
+ v-model="form.role"
51
+ :error-messages="$ta(inputErrors.role)"
52
+ ></RoleCombobox>
53
+
54
+ <v-text-field
55
+ v-model="form.email"
56
+ variant="outlined"
57
+ id="email-input"
58
+ label="Email"
59
+ prepend-inner-icon="mdi-email"
60
+ required
61
+ :error-messages="$ta(inputErrors.email)"
62
+ ></v-text-field>
63
+
64
+ <v-text-field
65
+ v-model="form.phone"
66
+ variant="outlined"
67
+ id="phone-input"
68
+ label="Phone"
69
+ prepend-inner-icon="mdi-phone"
70
+ required
71
+ :error-messages="$ta(inputErrors.phone)"
72
+ ></v-text-field>
73
+
74
+ <v-switch
75
+ id="active-input"
76
+ v-model="form.active"
77
+ color="primary"
78
+ label="Active"
79
+ :true-value="true"
80
+ :false-value="false"
81
+ ></v-switch>
82
+ </form>
83
+ </template>
84
+
85
+ <style scoped>
86
+
87
+ </style>