11<script setup lang="ts">
2- import { computed } from ' vue'
2+ import { ref , computed } from ' vue'
33import { useI18n } from ' vue-i18n'
4+ import { toast } from ' vue-sonner'
45import { Badge } from ' @/components/ui/badge'
6+ import { Button } from ' @/components/ui/button'
57import { DsCard } from ' @/components/ui/ds-card'
8+ import {
9+ Dialog ,
10+ DialogContent ,
11+ DialogDescription ,
12+ DialogFooter ,
13+ DialogHeader ,
14+ DialogTitle ,
15+ DialogTrigger ,
16+ } from ' @/components/ui/dialog'
17+ import {
18+ Select ,
19+ SelectContent ,
20+ SelectItem ,
21+ SelectTrigger ,
22+ SelectValue ,
23+ } from ' @/components/ui/select'
624import { Mail , Github , Shield } from ' lucide-vue-next'
25+ import { getEnv } from ' @/utils/env'
726import type { User } from ' @/views/admin/users/types'
827
928const props = defineProps <{
1029 user: User
1130}>()
1231
32+ const emit = defineEmits <{
33+ roleChanged: []
34+ }>()
35+
1336const { t } = useI18n ()
1437
38+ // Dialog state
39+ const isDialogOpen = ref (false )
40+ const selectedRole = ref (props .user .role_id || ' global_user' )
41+ const isChangingRole = ref (false )
42+
1543// Computed properties for display
1644const displayName = computed (() => {
1745 const firstName = props .user .first_name || ' '
@@ -28,6 +56,64 @@ const authTypeBadge = computed(() => {
2856 text: isEmail ? t (' adminUsers.userDetail.values.email' ) : t (' adminUsers.userDetail.values.github' )
2957 }
3058})
59+
60+ // Role options
61+ const roleOptions = [
62+ { value: ' global_admin' , label: ' Global Administrator' },
63+ { value: ' global_user' , label: ' Global User' }
64+ ]
65+
66+ // Change role handler
67+ async function handleChangeRole() {
68+ if (! selectedRole .value || selectedRole .value === props .user .role_id ) {
69+ isDialogOpen .value = false
70+ return
71+ }
72+
73+ try {
74+ isChangingRole .value = true
75+ const apiUrl = getEnv (' VITE_DEPLOYSTACK_BACKEND_URL' )
76+
77+ const response = await fetch (` ${apiUrl }/api/admin/users/${props .user .id }/role ` , {
78+ method: ' PUT' ,
79+ headers: {
80+ ' Content-Type' : ' application/json' ,
81+ },
82+ credentials: ' include' ,
83+ body: JSON .stringify ({
84+ role_id: selectedRole .value
85+ })
86+ })
87+
88+ const data = await response .json ()
89+
90+ if (! response .ok ) {
91+ throw new Error (data .error || ' Failed to change role' )
92+ }
93+
94+ toast .success (' Role changed successfully' , {
95+ description: ` User role updated to ${roleOptions .find (r => r .value === selectedRole .value )?.label } `
96+ })
97+
98+ isDialogOpen .value = false
99+ emit (' roleChanged' )
100+ } catch (error ) {
101+ const errorMessage = error instanceof Error ? error .message : ' Unknown error'
102+ toast .error (' Failed to change role' , {
103+ description: errorMessage
104+ })
105+ } finally {
106+ isChangingRole .value = false
107+ }
108+ }
109+
110+ // Reset selected role when dialog opens
111+ function handleDialogOpen(open : boolean ) {
112+ isDialogOpen .value = open
113+ if (open ) {
114+ selectedRole .value = props .user .role_id || ' global_user'
115+ }
116+ }
31117 </script >
32118
33119<template >
@@ -59,14 +145,6 @@ const authTypeBadge = computed(() => {
59145 <dd class =" mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0" >{{ user.email }}</dd >
60146 </div >
61147
62- <!-- Role -->
63- <div class =" py-4 sm:grid sm:grid-cols-3 sm:gap-4" >
64- <dt class =" text-sm font-medium text-gray-900" >{{ t('adminUsers.userDetail.fields.role') }}</dt >
65- <dd class =" mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0" >
66- {{ user.role ? user.role.name : t('adminUsers.userDetail.values.noRoleAssigned') }}
67- </dd >
68- </div >
69-
70148 <!-- Registration Method -->
71149 <div class =" py-4 sm:grid sm:grid-cols-3 sm:gap-4" >
72150 <dt class =" text-sm font-medium text-gray-900" >{{ t('adminUsers.userDetail.fields.registrationMethod') }}</dt >
@@ -103,6 +181,75 @@ const authTypeBadge = computed(() => {
103181 </dl >
104182 </DsCard >
105183
184+ <!-- Role Card -->
185+ <DsCard :title =" t('adminUsers.userDetail.fields.role')" >
186+ <p class =" text-sm text-muted-foreground mb-6" >
187+ User role and permissions within the DeployStack system
188+ </p >
189+
190+ <dl class =" divide-y divide-gray-100" >
191+ <!-- Role ID -->
192+ <div class =" py-4 sm:grid sm:grid-cols-3 sm:gap-4" >
193+ <dt class =" text-sm font-medium text-gray-900" >Role</dt >
194+ <dd class =" mt-1 text-sm text-gray-700 sm:col-span-2 sm:mt-0" >
195+ {{ user.role_id || t('adminUsers.userDetail.values.noRoleAssigned') }}
196+ </dd >
197+ </div >
198+ </dl >
199+
200+ <template #footer-actions >
201+ <Dialog :open =" isDialogOpen" @update:open =" handleDialogOpen" >
202+ <DialogTrigger as-child >
203+ <Button >
204+ Change Role
205+ </Button >
206+ </DialogTrigger >
207+ <DialogContent class =" sm:max-w-[425px]" >
208+ <DialogHeader >
209+ <DialogTitle >Change User Role</DialogTitle >
210+ <DialogDescription >
211+ Update the role for {{ user.username }}. This will affect their permissions across the system.
212+ </DialogDescription >
213+ </DialogHeader >
214+
215+ <div class =" space-y-4 py-4" >
216+ <div class =" space-y-2" >
217+ <label class =" text-sm font-medium" >Select Role</label >
218+ <Select v-model =" selectedRole" >
219+ <SelectTrigger >
220+ <SelectValue placeholder =" Select a role" />
221+ </SelectTrigger >
222+ <SelectContent >
223+ <SelectItem
224+ v-for =" option in roleOptions"
225+ :key =" option.value"
226+ :value =" option.value"
227+ >
228+ {{ option.label }}
229+ </SelectItem >
230+ </SelectContent >
231+ </Select >
232+ </div >
233+ </div >
234+
235+ <DialogFooter >
236+ <Button variant =" outline" @click =" isDialogOpen = false" :disabled =" isChangingRole" >
237+ Cancel
238+ </Button >
239+ <Button
240+ @click =" handleChangeRole"
241+ :loading =" isChangingRole"
242+ loading-text =" Changing..."
243+ :disabled =" isChangingRole || selectedRole === user.role_id"
244+ >
245+ Change Role
246+ </Button >
247+ </DialogFooter >
248+ </DialogContent >
249+ </Dialog >
250+ </template >
251+ </DsCard >
252+
106253 <!-- Permissions Card -->
107254 <DsCard v-if =" user.role && user.role.permissions.length > 0" :title =" t('adminUsers.userDetail.fields.permissions')" >
108255 <ul role =" list" class =" divide-y divide-gray-100 rounded-md border border-gray-200" >
0 commit comments