Skip to content

Commit ae6b9f7

Browse files
committed
feat: create and edit user with roles
1 parent b7d7055 commit ae6b9f7

26 files changed

+272
-233
lines changed

app/components/users/UserBioPanel.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
2+
import { avatarText } from '#imports'
23
import { type User, type UserWithRoles, useUserStore } from '@base/stores/admin/user'
34
import UserDrawer from './UserDrawer.vue'
4-
import { avatarText } from '#imports'
55
66
defineProps<{
77
user: UserWithRoles
@@ -14,7 +14,7 @@ const route = useRoute('admin-users-id')
1414
1515
const userStore = useUserStore()
1616
17-
async function handleSubmitEdit(payload: Partial<User>) {
17+
async function handleSubmitEdit(payload: Partial<User> & { roles: string[], organizations: string[] }) {
1818
await userStore.updateUser(route.params.id, payload)
1919
}
2020
</script>

app/components/users/UserDrawer.vue

+31-20
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,52 @@
11
<script setup lang="ts">
2+
import type { User, UserWithRoles } from '@base/stores/admin/user'
3+
import { emailValidator, requiredValidator } from '#imports'
24
import { useOrganizationStore } from '@base/stores/admin/organization'
35
import { useRoleStore } from '@base/stores/admin/role'
46
import { cloneDeep } from 'lodash-es'
57
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
6-
import type { User, UserWithRoles } from '@base/stores/admin/user'
7-
import { emailValidator, requiredValidator } from '#imports'
88
99
const props = defineProps<{
1010
user?: UserWithRoles
1111
}>()
1212
1313
const emit = defineEmits<{
14-
(e: 'edit', payload: Partial<User>): void
15-
(e: 'create', payload: Partial<User>): void
14+
(e: 'edit', payload: Partial<User> & { roles: string[], organizations: string[] }): void
15+
(e: 'create', payload: Partial<User> & { roles: string[], organizations: string[] }): void
1616
(e: 'cancel'): void
1717
}>()
1818
1919
const modelValue = defineModel<boolean>('modelValue', {
2020
default: false,
2121
})
2222
23-
// const roleStore = useRoleStore()
23+
const roleStore = useRoleStore()
24+
25+
const organizationStore = useOrganizationStore()
26+
27+
const { data: roleData, execute: fetchRoles } = useAsyncData(() => roleStore.fetchRoles(), {
28+
immediate: false,
29+
})
2430
25-
// const organizationStore = useOrganizationStore()
31+
const { data: organizationData, execute: fetchOrganizations } = useAsyncData(() => organizationStore.fetchOrganizations(), {
32+
immediate: false,
33+
})
2634
27-
function getDefaultFormData(): Partial<User> {
35+
function getDefaultFormData(): Partial<User> & { roles: string[], organizations: string[] } {
2836
return {
2937
email: '',
30-
email_verified: new Date(),
3138
phone: '',
3239
password: '',
3340
full_name: '',
3441
avatar_url: '',
3542
country: '',
3643
language: '',
3744
postcode: '',
38-
status: 'resolved',
45+
status: 'active',
3946
address: '',
4047
city: '',
48+
roles: [],
49+
organizations: [],
4150
}
4251
}
4352
@@ -46,6 +55,10 @@ const formData = ref(getDefaultFormData())
4655
let syncStop: () => void
4756
watch(modelValue, (value) => {
4857
if (value) {
58+
// non-block fetching
59+
fetchRoles()
60+
fetchOrganizations()
61+
4962
syncStop = syncRef(computed(() => props.user), formData, {
5063
direction: 'ltr',
5164
transform: {
@@ -130,18 +143,17 @@ function handleCancel() {
130143
<!-- 👉 Form -->
131144
<VForm ref="formRef">
132145
<VRow>
133-
<!-- TODO: Add Role -->
134-
<!-- <VCol cols="12">
146+
<VCol cols="12">
135147
<VSelect
136-
v-model="formData.role_id"
148+
v-model="formData.roles"
137149
label="Select Role"
138-
:rules="[requiredValidator]"
150+
multiple
151+
:items="roleData?.data || []"
139152
placeholder="Select Role"
140-
:items="roleList"
141153
item-title="name"
142154
item-value="id"
143155
/>
144-
</VCol> -->
156+
</VCol>
145157

146158
<!-- 👉 Full name -->
147159
<VCol cols="12">
@@ -173,17 +185,16 @@ function handleCancel() {
173185
/>
174186
</VCol>
175187

176-
<!-- TODO: Add Organization -->
177-
<!-- <VCol cols="12">
188+
<VCol cols="12">
178189
<VSelect
179-
v-model="formData.organization_id"
190+
v-model="formData.organizations"
180191
label="Select Organization"
181192
placeholder="Select Organization"
182-
:items="[{ id: null, name: 'No Organization' }, ...localOrganization]"
193+
:items="organizationData?.data || []"
183194
item-title="name"
184195
item-value="id"
185196
/>
186-
</VCol> -->
197+
</VCol>
187198

188199
<!-- 👉 Country -->
189200
<VCol cols="12">

app/pages/admin.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
definePageMeta({
3-
// action: 'manage',
4-
// subject: 'user',
3+
action: 'manage',
4+
subject: 'User',
55
sidebar: {
66
title: 'Users & Permissions',
77
icon: { icon: 'ri-admin-line' },

app/pages/admin/organizations.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import AddNewUserDrawer from '@base/components/users/UserDrawer.vue'
33
44
definePageMeta({
55
action: 'manage',
6-
subject: 'organization',
6+
subject: 'Organization',
77
sidebar: {
88
title: 'Organizations',
99
icon: { icon: 'ri-building-line' },

app/pages/admin/permissions.vue

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { usePermissionStore } from '@base/stores/admin/permission'
66
type Permission = InferSelectModel<typeof sysPermissionTable>
77
88
definePageMeta({
9+
action: 'manage',
10+
subject: 'Permission',
911
sidebar: {
1012
title: 'Permissions',
1113
icon: { icon: 'ri-lock-2-line' },

app/pages/admin/roles.vue

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { sortBy } from 'lodash-es'
77
// import UserList from '@base/components/roles/UserList.vue'
88
99
definePageMeta({
10+
action: 'manage',
11+
subject: 'Role',
1012
sidebar: {
1113
title: 'Roles',
1214
icon: { icon: 'ri-shield-user-line' },

app/pages/admin/users/index.vue

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<script setup lang="ts">
22
import type { RoleWithPermissions } from '@base/stores/admin/role'
3-
import type { UserWithRoles } from '@base/stores/admin/user'
3+
import type { User, UserWithRoles } from '@base/stores/admin/user'
4+
import { avatarText } from '#imports'
45
import { useRoleStore } from '@base/stores/admin/role'
56
import { useUserStore } from '@base/stores/admin/user'
67
import { match } from 'ts-pattern'
7-
import { avatarText } from '#imports'
88
99
definePageMeta({
10+
action: 'manage',
11+
subject: 'User',
1012
sidebar: {
1113
title: 'Users',
1214
icon: { icon: 'ri-id-card-line' },
@@ -35,7 +37,7 @@ const userQuery = ref({
3537
keyword: '',
3638
})
3739
38-
const { data: userData, refresh: reFetchUsers } = useLazyAsyncData('users', () => userStore.fetchUsers(userQuery.value), {
40+
const { data: userData, refresh: reFetchUsers } = useLazyAsyncData('users', () => userStore.fetchUsers({ ...userQuery.value, withCount: true }), {
3941
server: false,
4042
})
4143
@@ -79,6 +81,16 @@ async function handleDeleteUser(user: UserWithRoles) {
7981
}
8082
catch {}
8183
}
84+
85+
const isCreateUserDrawerVisible = ref(false)
86+
87+
async function handleSubmit(payload: Partial<User> & { roles: string[], organizations: string[] }) {
88+
await userStore.createUser(payload)
89+
90+
isCreateUserDrawerVisible.value = false
91+
92+
reFetchUsers()
93+
}
8294
</script>
8395

8496
<template>
@@ -104,8 +116,8 @@ async function handleDeleteUser(user: UserWithRoles) {
104116
density="compact"
105117
style="min-width: 200px;"
106118
/>
107-
<!-- 👉 Add user button -->
108-
<VBtn>
119+
120+
<VBtn @click="isCreateUserDrawerVisible = true">
109121
{{ $t('Add New User') }}
110122
</VBtn>
111123
</div>
@@ -230,5 +242,9 @@ async function handleDeleteUser(user: UserWithRoles) {
230242
<!-- SECTION -->
231243
</VCard>
232244
</VCol>
245+
<UserDrawer
246+
v-model="isCreateUserDrawerVisible"
247+
@create="handleSubmit"
248+
/>
233249
</VRow>
234250
</template>

app/stores/admin/organization.ts

+16-61
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,41 @@
11
import type { sysOrganizationTable } from '@base/server/db/schemas'
2-
import type { InferSelectModel } from 'drizzle-orm'
32
import type { ParsedFilterQuery } from '@base/server/utils/filter'
3+
import type { InferSelectModel } from 'drizzle-orm'
44

55
export type Organization = InferSelectModel<typeof sysOrganizationTable>
66

77
export const useOrganizationStore = defineStore('organization', () => {
8-
const organizationList = ref<Organization[]>([])
9-
const totalOrganizations = ref<number>(0)
10-
const organizationDetail = ref<Organization | null>(null)
11-
128
async function fetchOrganizations(options?: ParsedFilterQuery) {
13-
try {
14-
const response = await $api<Organization[]>('/api/organizations', {
15-
query: options,
16-
})
17-
18-
if (response) {
19-
organizationList.value = response.data
20-
totalOrganizations.value = response.total
21-
}
22-
}
23-
catch (error) {
24-
console.error('Error fetching organizations:', error)
25-
}
9+
return $api<{ total: number, data: Organization[] }>('/organizations', {
10+
query: options,
11+
})
2612
}
2713

2814
async function fetchOrganizationDetail(organizationId: string) {
29-
try {
30-
const response = await $api<Organization>(`/api/organizations/${organizationId}`, {
31-
method: 'GET',
32-
})
33-
34-
organizationDetail.value = response.data
35-
}
36-
catch (error) {
37-
console.error('Error fetching organization detail:', error)
38-
}
15+
return $api<{ data: Organization }>(`/organizations/${organizationId}`)
3916
}
4017

4118
async function createOrganization(body: Partial<Organization>) {
42-
try {
43-
const response = await $api<Organization>('/api/organizations', {
44-
method: 'POST',
45-
body,
46-
})
47-
48-
return response
49-
}
50-
catch (error) {
51-
console.error('Error creating organization:', error)
52-
}
19+
return await $api<{ data: Organization }>('/organizations', {
20+
method: 'POST',
21+
body,
22+
})
5323
}
5424

5525
async function updateOrganization(organizationId: string, body: Partial<Organization>) {
56-
try {
57-
const response = await $api<Organization>(`/api/organizations/${organizationId}`, {
58-
method: 'PATCH',
59-
body,
60-
})
61-
62-
return response
63-
}
64-
catch (error) {
65-
console.error('Error updating organization:', error)
66-
}
26+
return await $api<{ data: Organization }>(`/organizations/${organizationId}`, {
27+
method: 'PATCH',
28+
body,
29+
})
6730
}
6831

6932
async function deleteOrganization(organizationId: string) {
70-
try {
71-
await $api(`/api/organizations/${organizationId}`, {
72-
method: 'DELETE',
73-
})
74-
}
75-
catch (error) {
76-
console.error('Error deleting organization:', error)
77-
}
33+
return await $api(`/organizations/${organizationId}`, {
34+
method: 'DELETE',
35+
})
7836
}
7937

8038
return {
81-
organizationList,
82-
organizationDetail,
83-
totalOrganizations,
8439
fetchOrganizations,
8540
fetchOrganizationDetail,
8641
updateOrganization,

app/stores/admin/user.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ export const useUserStore = defineStore('user', () => {
2424
})
2525
}
2626

27-
async function createUser(body: Partial<User> & { permissions?: string[] }) {
27+
async function createUser(body: Partial<User> & { roles?: string[], organizations?: string[] }) {
2828
return await $api<User>('/users', {
2929
method: 'POST',
3030
body,
3131
})
3232
}
3333

34-
async function updateUser(userId: string, body: Partial<User> & { permissions?: string[] }) {
34+
async function updateUser(userId: string, body: Partial<User> & { roles?: string[], organizations?: string[] }) {
3535
return await $api(`/users/${userId}`, {
3636
method: 'PATCH',
3737
body,

server/api/auth/signup.post.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { createHmac } from 'node:crypto'
21
import { Buffer } from 'node:buffer'
3-
import bcrypt from 'bcrypt'
2+
import { createHmac } from 'node:crypto'
43
import { useUser } from '@base/server/composables/useUser'
4+
import bcrypt from 'bcrypt'
55
import { z } from 'zod'
66

77
export default defineEventHandler(async (event) => {

server/api/organizations/[organizationUId].delete.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
import { eq } from 'drizzle-orm'
2-
import { sysOrganizationTable } from '@base/server/db/schemas'
1+
import { useOrganization } from '@base/server/composables/useOrganization'
32

43
export default defineEventHandler(async (event) => {
54
try {
65
const { organizationUId } = await defineEventOptions(event, { auth: true, params: ['organizationUId'] })
76

8-
const sysOrganization = await db.delete(sysOrganizationTable)
9-
.where(
10-
eq(sysOrganizationTable.id, organizationUId),
11-
)
12-
.returning()
7+
const { deleteOrganizationById } = useOrganization()
138

14-
setResponseStatus(event, 201)
15-
16-
return { data: sysOrganization[0] }
9+
await deleteOrganizationById(organizationUId)
1710
}
1811
catch (error: any) {
1912
throw parseError(error)

0 commit comments

Comments
 (0)