forked from open-webui/open-webui
		
	feat: edit user support
This commit is contained in:
		
							parent
							
								
									0262be4724
								
							
						
					
					
						commit
						fb0c64379d
					
				
					 9 changed files with 371 additions and 43 deletions
				
			
		|  | @ -75,26 +75,20 @@ class SignupForm(BaseModel): | |||
| 
 | ||||
| 
 | ||||
| class AuthsTable: | ||||
| 
 | ||||
|     def __init__(self, db): | ||||
|         self.db = db | ||||
|         self.db.create_tables([Auth]) | ||||
| 
 | ||||
|     def insert_new_auth(self, | ||||
|                         email: str, | ||||
|                         password: str, | ||||
|                         name: str, | ||||
|                         role: str = "pending") -> Optional[UserModel]: | ||||
|     def insert_new_auth( | ||||
|         self, email: str, password: str, name: str, role: str = "pending" | ||||
|     ) -> Optional[UserModel]: | ||||
|         print("insert_new_auth") | ||||
| 
 | ||||
|         id = str(uuid.uuid4()) | ||||
| 
 | ||||
|         auth = AuthModel(**{ | ||||
|             "id": id, | ||||
|             "email": email, | ||||
|             "password": password, | ||||
|             "active": True | ||||
|         }) | ||||
|         auth = AuthModel( | ||||
|             **{"id": id, "email": email, "password": password, "active": True} | ||||
|         ) | ||||
|         result = Auth.create(**auth.model_dump()) | ||||
| 
 | ||||
|         user = Users.insert_new_user(id, name, email, role) | ||||
|  | @ -104,8 +98,7 @@ class AuthsTable: | |||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     def authenticate_user(self, email: str, | ||||
|                           password: str) -> Optional[UserModel]: | ||||
|     def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: | ||||
|         print("authenticate_user", email) | ||||
|         try: | ||||
|             auth = Auth.get(Auth.email == email, Auth.active == True) | ||||
|  | @ -129,6 +122,15 @@ class AuthsTable: | |||
|         except: | ||||
|             return False | ||||
| 
 | ||||
|     def update_email_by_id(self, id: str, email: str) -> bool: | ||||
|         try: | ||||
|             query = Auth.update(email=email).where(Auth.id == id) | ||||
|             result = query.execute() | ||||
| 
 | ||||
|             return True if result == 1 else False | ||||
|         except: | ||||
|             return False | ||||
| 
 | ||||
|     def delete_auth_by_id(self, id: str) -> bool: | ||||
|         try: | ||||
|             # Delete User | ||||
|  | @ -137,8 +139,7 @@ class AuthsTable: | |||
|             if result: | ||||
|                 # Delete Auth | ||||
|                 query = Auth.delete().where(Auth.id == id) | ||||
|                 query.execute( | ||||
|                 )  # Remove the rows, return number of rows removed. | ||||
|                 query.execute()  # Remove the rows, return number of rows removed. | ||||
| 
 | ||||
|                 return True | ||||
|             else: | ||||
|  |  | |||
|  | @ -44,17 +44,21 @@ class UserRoleUpdateForm(BaseModel): | |||
|     role: str | ||||
| 
 | ||||
| 
 | ||||
| class UsersTable: | ||||
| class UserUpdateForm(BaseModel): | ||||
|     name: str | ||||
|     email: str | ||||
|     profile_image_url: str | ||||
|     password: Optional[str] = None | ||||
| 
 | ||||
| 
 | ||||
| class UsersTable: | ||||
|     def __init__(self, db): | ||||
|         self.db = db | ||||
|         self.db.create_tables([User]) | ||||
| 
 | ||||
|     def insert_new_user(self, | ||||
|                         id: str, | ||||
|                         name: str, | ||||
|                         email: str, | ||||
|                         role: str = "pending") -> Optional[UserModel]: | ||||
|     def insert_new_user( | ||||
|         self, id: str, name: str, email: str, role: str = "pending" | ||||
|     ) -> Optional[UserModel]: | ||||
|         user = UserModel( | ||||
|             **{ | ||||
|                 "id": id, | ||||
|  | @ -63,7 +67,8 @@ class UsersTable: | |||
|                 "role": role, | ||||
|                 "profile_image_url": get_gravatar_url(email), | ||||
|                 "timestamp": int(time.time()), | ||||
|             }) | ||||
|             } | ||||
|         ) | ||||
|         result = User.create(**user.model_dump()) | ||||
|         if result: | ||||
|             return user | ||||
|  | @ -93,8 +98,7 @@ class UsersTable: | |||
|     def get_num_users(self) -> Optional[int]: | ||||
|         return User.select().count() | ||||
| 
 | ||||
|     def update_user_role_by_id(self, id: str, | ||||
|                                role: str) -> Optional[UserModel]: | ||||
|     def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]: | ||||
|         try: | ||||
|             query = User.update(role=role).where(User.id == id) | ||||
|             query.execute() | ||||
|  | @ -104,6 +108,16 @@ class UsersTable: | |||
|         except: | ||||
|             return None | ||||
| 
 | ||||
|     def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]: | ||||
|         try: | ||||
|             query = User.update(**updated).where(User.id == id) | ||||
|             query.execute() | ||||
| 
 | ||||
|             user = User.get(User.id == id) | ||||
|             return UserModel(**model_to_dict(user)) | ||||
|         except: | ||||
|             return None | ||||
| 
 | ||||
|     def delete_user_by_id(self, id: str) -> bool: | ||||
|         try: | ||||
|             # Delete User Chats | ||||
|  | @ -112,8 +126,7 @@ class UsersTable: | |||
|             if result: | ||||
|                 # Delete User | ||||
|                 query = User.delete().where(User.id == id) | ||||
|                 query.execute( | ||||
|                 )  # Remove the rows, return number of rows removed. | ||||
|                 query.execute()  # Remove the rows, return number of rows removed. | ||||
| 
 | ||||
|                 return True | ||||
|             else: | ||||
|  |  | |||
|  | @ -8,10 +8,10 @@ from pydantic import BaseModel | |||
| import time | ||||
| import uuid | ||||
| 
 | ||||
| from apps.web.models.users import UserModel, UserRoleUpdateForm, Users | ||||
| from apps.web.models.users import UserModel, UserUpdateForm, UserRoleUpdateForm, Users | ||||
| from apps.web.models.auths import Auths | ||||
| 
 | ||||
| from utils.utils import get_current_user | ||||
| from utils.utils import get_current_user, get_password_hash | ||||
| from constants import ERROR_MESSAGES | ||||
| 
 | ||||
| router = APIRouter() | ||||
|  | @ -22,9 +22,7 @@ router = APIRouter() | |||
| 
 | ||||
| 
 | ||||
| @router.get("/", response_model=List[UserModel]) | ||||
| async def get_users(skip: int = 0, | ||||
|                     limit: int = 50, | ||||
|                     user=Depends(get_current_user)): | ||||
| async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_current_user)): | ||||
|     if user.role != "admin": | ||||
|         raise HTTPException( | ||||
|             status_code=status.HTTP_403_FORBIDDEN, | ||||
|  | @ -34,25 +32,58 @@ async def get_users(skip: int = 0, | |||
| 
 | ||||
| 
 | ||||
| ############################ | ||||
| # UpdateUserRole | ||||
| # UpdateUserById | ||||
| ############################ | ||||
| 
 | ||||
| 
 | ||||
| @router.post("/update/role", response_model=Optional[UserModel]) | ||||
| async def update_user_role(form_data: UserRoleUpdateForm, | ||||
|                            user=Depends(get_current_user)): | ||||
|     if user.role != "admin": | ||||
| @router.post("/{user_id}/update", response_model=Optional[UserModel]) | ||||
| async def update_user_by_id( | ||||
|     user_id: str, form_data: UserUpdateForm, session_user=Depends(get_current_user) | ||||
| ): | ||||
|     if session_user.role != "admin": | ||||
|         raise HTTPException( | ||||
|             status_code=status.HTTP_403_FORBIDDEN, | ||||
|             detail=ERROR_MESSAGES.ACCESS_PROHIBITED, | ||||
|         ) | ||||
| 
 | ||||
|     if user.id != form_data.id: | ||||
|         return Users.update_user_role_by_id(form_data.id, form_data.role) | ||||
|     user = Users.get_user_by_id(user_id) | ||||
| 
 | ||||
|     if user: | ||||
|         if form_data.email != user.email: | ||||
|             email_user = Users.get_user_by_email(form_data.email) | ||||
|             if email_user: | ||||
|                 raise HTTPException( | ||||
|                     status_code=status.HTTP_400_BAD_REQUEST, | ||||
|                     detail=ERROR_MESSAGES.EMAIL_TAKEN, | ||||
|                 ) | ||||
| 
 | ||||
|         if form_data.password: | ||||
|             hashed = get_password_hash(form_data.password) | ||||
|             print(hashed) | ||||
|             Auths.update_user_password_by_id(user_id, hashed) | ||||
| 
 | ||||
|         Auths.update_email_by_id(user_id, form_data.email) | ||||
|         updated_user = Users.update_user_by_id( | ||||
|             user_id, | ||||
|             { | ||||
|                 "name": form_data.name, | ||||
|                 "email": form_data.email, | ||||
|                 "profile_image_url": form_data.profile_image_url, | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
|         if updated_user: | ||||
|             return updated_user | ||||
|         else: | ||||
|             raise HTTPException( | ||||
|                 status_code=status.HTTP_400_BAD_REQUEST, | ||||
|                 detail=ERROR_MESSAGES.DEFAULT(), | ||||
|             ) | ||||
| 
 | ||||
|     else: | ||||
|         raise HTTPException( | ||||
|             status_code=status.HTTP_403_FORBIDDEN, | ||||
|             detail=ERROR_MESSAGES.ACTION_PROHIBITED, | ||||
|             status_code=status.HTTP_400_BAD_REQUEST, | ||||
|             detail=ERROR_MESSAGES.USER_NOT_FOUND, | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										11
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -9,6 +9,7 @@ | |||
| 			"version": "0.0.1", | ||||
| 			"dependencies": { | ||||
| 				"@sveltejs/adapter-node": "^1.3.1", | ||||
| 				"dayjs": "^1.11.10", | ||||
| 				"file-saver": "^2.0.5", | ||||
| 				"highlight.js": "^11.9.0", | ||||
| 				"idb": "^7.1.1", | ||||
|  | @ -1577,6 +1578,11 @@ | |||
| 				"node": ">=4" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/dayjs": { | ||||
| 			"version": "1.11.10", | ||||
| 			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", | ||||
| 			"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" | ||||
| 		}, | ||||
| 		"node_modules/debug": { | ||||
| 			"version": "4.3.4", | ||||
| 			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | ||||
|  | @ -4940,6 +4946,11 @@ | |||
| 			"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"dayjs": { | ||||
| 			"version": "1.11.10", | ||||
| 			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", | ||||
| 			"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" | ||||
| 		}, | ||||
| 		"debug": { | ||||
| 			"version": "4.3.4", | ||||
| 			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | ||||
|  |  | |||
|  | @ -40,6 +40,7 @@ | |||
| 	"type": "module", | ||||
| 	"dependencies": { | ||||
| 		"@sveltejs/adapter-node": "^1.3.1", | ||||
| 		"dayjs": "^1.11.10", | ||||
| 		"file-saver": "^2.0.5", | ||||
| 		"highlight.js": "^11.9.0", | ||||
| 		"idb": "^7.1.1", | ||||
|  |  | |||
|  | @ -84,3 +84,43 @@ export const deleteUserById = async (token: string, userId: string) => { | |||
| 
 | ||||
| 	return res; | ||||
| }; | ||||
| 
 | ||||
| type UserUpdateForm = { | ||||
| 	profile_image_url: string; | ||||
| 	email: string; | ||||
| 	name: string; | ||||
| 	password: string; | ||||
| }; | ||||
| 
 | ||||
| export const updateUserById = async (token: string, userId: string, user: UserUpdateForm) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/update`, { | ||||
| 		method: 'POST', | ||||
| 		headers: { | ||||
| 			'Content-Type': 'application/json', | ||||
| 			Authorization: `Bearer ${token}` | ||||
| 		}, | ||||
| 		body: JSON.stringify({ | ||||
| 			profile_image_url: user.profile_image_url, | ||||
| 			email: user.email, | ||||
| 			name: user.name, | ||||
| 			password: user.password !== '' ? user.password : undefined | ||||
| 		}) | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
| 			if (!res.ok) throw await res.json(); | ||||
| 			return res.json(); | ||||
| 		}) | ||||
| 		.catch((err) => { | ||||
| 			console.log(err); | ||||
| 			error = err.detail; | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 	if (error) { | ||||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res; | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										172
									
								
								src/lib/components/admin/EditUserModal.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								src/lib/components/admin/EditUserModal.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,172 @@ | |||
| <script lang="ts"> | ||||
| 	import toast from 'svelte-french-toast'; | ||||
| 	import dayjs from 'dayjs'; | ||||
| 	import { createEventDispatcher } from 'svelte'; | ||||
| 	import { onMount } from 'svelte'; | ||||
| 
 | ||||
| 	import { updateUserById } from '$lib/apis/users'; | ||||
| 	import Modal from '../common/Modal.svelte'; | ||||
| 
 | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	export let show = false; | ||||
| 	export let selectedUser; | ||||
| 	export let sessionUser; | ||||
| 
 | ||||
| 	let _user = { | ||||
| 		profile_image_url: '', | ||||
| 		name: '', | ||||
| 		email: '', | ||||
| 		password: '' | ||||
| 	}; | ||||
| 
 | ||||
| 	const submitHandler = async () => { | ||||
| 		const res = await updateUserById(localStorage.token, selectedUser.id, _user).catch((error) => { | ||||
| 			toast.error(error); | ||||
| 		}); | ||||
| 
 | ||||
| 		if (res) { | ||||
| 			dispatch('save'); | ||||
| 			show = false; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	onMount(() => { | ||||
| 		if (selectedUser) { | ||||
| 			_user = selectedUser; | ||||
| 			_user.password = ''; | ||||
| 		} | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <Modal size="sm" bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 			<div class=" text-lg font-medium self-center">Edit User</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
| 				on:click={() => { | ||||
| 					show = false; | ||||
| 				}} | ||||
| 			> | ||||
| 				<svg | ||||
| 					xmlns="http://www.w3.org/2000/svg" | ||||
| 					viewBox="0 0 20 20" | ||||
| 					fill="currentColor" | ||||
| 					class="w-5 h-5" | ||||
| 				> | ||||
| 					<path | ||||
| 						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" | ||||
| 					/> | ||||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200"> | ||||
| 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> | ||||
| 				<form | ||||
| 					class="flex flex-col w-full" | ||||
| 					on:submit|preventDefault={() => { | ||||
| 						submitHandler(); | ||||
| 					}} | ||||
| 				> | ||||
| 					<div class=" flex items-center rounded-md py-2 px-4 w-full"> | ||||
| 						<div class=" self-center mr-5"> | ||||
| 							<img | ||||
| 								src={selectedUser.profile_image_url} | ||||
| 								class=" max-w-[55px] object-cover rounded-full" | ||||
| 								alt="User profile" | ||||
| 							/> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div> | ||||
| 							<div class=" self-center capitalize font-semibold">{selectedUser.name}</div> | ||||
| 
 | ||||
| 							<div class="text-xs text-gray-500"> | ||||
| 								Created at {dayjs(selectedUser.timestamp * 1000).format('MMMM DD, YYYY')} | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<hr class=" dark:border-gray-800 my-3 w-full" /> | ||||
| 
 | ||||
| 					<div class=" flex flex-col space-y-1.5"> | ||||
| 						<div class="flex flex-col w-full"> | ||||
| 							<div class=" mb-1 text-xs text-gray-500">Email</div> | ||||
| 
 | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none" | ||||
| 									type="email" | ||||
| 									bind:value={_user.email} | ||||
| 									autocomplete="off" | ||||
| 									required | ||||
| 									disabled={_user.id == sessionUser.id} | ||||
| 								/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div class="flex flex-col w-full"> | ||||
| 							<div class=" mb-1 text-xs text-gray-500">Name</div> | ||||
| 
 | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||
| 									type="text" | ||||
| 									bind:value={_user.name} | ||||
| 									autocomplete="off" | ||||
| 									required | ||||
| 								/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div class="flex flex-col w-full"> | ||||
| 							<div class=" mb-1 text-xs text-gray-500">New Password</div> | ||||
| 
 | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||
| 									type="password" | ||||
| 									bind:value={_user.password} | ||||
| 									autocomplete="new-password" | ||||
| 								/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div class="flex justify-end pt-3 text-sm font-medium"> | ||||
| 						<button | ||||
| 							class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" | ||||
| 							type="submit" | ||||
| 						> | ||||
| 							Save | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</form> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </Modal> | ||||
| 
 | ||||
| <style> | ||||
| 	input::-webkit-outer-spin-button, | ||||
| 	input::-webkit-inner-spin-button { | ||||
| 		/* display: none; <- Crashes Chrome on hover */ | ||||
| 		-webkit-appearance: none; | ||||
| 		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ | ||||
| 	} | ||||
| 
 | ||||
| 	.tabs::-webkit-scrollbar { | ||||
| 		display: none; /* for Chrome, Safari and Opera */ | ||||
| 	} | ||||
| 
 | ||||
| 	.tabs { | ||||
| 		-ms-overflow-style: none; /* IE and Edge */ | ||||
| 		scrollbar-width: none; /* Firefox */ | ||||
| 	} | ||||
| 
 | ||||
| 	input[type='number'] { | ||||
| 		-moz-appearance: textfield; /* Firefox */ | ||||
| 	} | ||||
| </style> | ||||
|  | @ -3,8 +3,18 @@ | |||
| 	import { fade, blur } from 'svelte/transition'; | ||||
| 
 | ||||
| 	export let show = true; | ||||
| 	export let size = 'md'; | ||||
| 
 | ||||
| 	let mounted = false; | ||||
| 
 | ||||
| 	const sizeToWidth = (size) => { | ||||
| 		if (size === 'sm') { | ||||
| 			return 'w-[30rem]'; | ||||
| 		} else { | ||||
| 			return 'w-[40rem]'; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	onMount(() => { | ||||
| 		mounted = true; | ||||
| 	}); | ||||
|  | @ -28,7 +38,9 @@ | |||
| 		}} | ||||
| 	> | ||||
| 		<div | ||||
| 			class="m-auto rounded-xl max-w-full w-[40rem] mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl" | ||||
| 			class="m-auto rounded-xl max-w-full {sizeToWidth( | ||||
| 				size | ||||
| 			)} mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl" | ||||
| 			transition:fade={{ delay: 100, duration: 200 }} | ||||
| 			on:click={(e) => { | ||||
| 				e.stopPropagation(); | ||||
|  |  | |||
|  | @ -8,11 +8,15 @@ | |||
| 
 | ||||
| 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; | ||||
| 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths'; | ||||
| 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte'; | ||||
| 
 | ||||
| 	let loaded = false; | ||||
| 	let users = []; | ||||
| 
 | ||||
| 	let selectedUser = null; | ||||
| 
 | ||||
| 	let signUpEnabled = true; | ||||
| 	let showEditUserModal = false; | ||||
| 
 | ||||
| 	const updateRoleHandler = async (id, role) => { | ||||
| 		const res = await updateUserRole(localStorage.token, id, role).catch((error) => { | ||||
|  | @ -25,6 +29,17 @@ | |||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const editUserPasswordHandler = async (id, password) => { | ||||
| 		const res = await deleteUserById(localStorage.token, id).catch((error) => { | ||||
| 			toast.error(error); | ||||
| 			return null; | ||||
| 		}); | ||||
| 		if (res) { | ||||
| 			users = await getUsers(localStorage.token); | ||||
| 			toast.success('Successfully updated'); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const deleteUserHandler = async (id) => { | ||||
| 		const res = await deleteUserById(localStorage.token, id).catch((error) => { | ||||
| 			toast.error(error); | ||||
|  | @ -51,6 +66,17 @@ | |||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| {#key selectedUser} | ||||
| 	<EditUserModal | ||||
| 		bind:show={showEditUserModal} | ||||
| 		{selectedUser} | ||||
| 		sessionUser={$user} | ||||
| 		on:save={async () => { | ||||
| 			users = await getUsers(localStorage.token); | ||||
| 		}} | ||||
| 	/> | ||||
| {/key} | ||||
| 
 | ||||
| <div | ||||
| 	class=" bg-white dark:bg-gray-800 dark:text-gray-100 min-h-screen w-full flex justify-center font-mona" | ||||
| > | ||||
|  | @ -154,7 +180,28 @@ | |||
| 												}}>{user.role}</button | ||||
| 											> | ||||
| 										</td> | ||||
| 										<td class="px-6 py-4 text-center flex justify-center"> | ||||
| 										<td class="px-6 py-4 space-x-1 text-center flex justify-center"> | ||||
| 											<button | ||||
| 												class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex" | ||||
| 												on:click={async () => { | ||||
| 													showEditUserModal = !showEditUserModal; | ||||
| 													selectedUser = user; | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													viewBox="0 0 16 16" | ||||
| 													fill="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 												> | ||||
| 													<path | ||||
| 														fill-rule="evenodd" | ||||
| 														d="M11.013 2.513a1.75 1.75 0 0 1 2.475 2.474L6.226 12.25a2.751 2.751 0 0 1-.892.596l-2.047.848a.75.75 0 0 1-.98-.98l.848-2.047a2.75 2.75 0 0 1 .596-.892l7.262-7.261Z" | ||||
| 														clip-rule="evenodd" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											</button> | ||||
| 
 | ||||
| 											<button | ||||
| 												class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex" | ||||
| 												on:click={async () => { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek