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: | class AuthsTable: | ||||||
| 
 |  | ||||||
|     def __init__(self, db): |     def __init__(self, db): | ||||||
|         self.db = db |         self.db = db | ||||||
|         self.db.create_tables([Auth]) |         self.db.create_tables([Auth]) | ||||||
| 
 | 
 | ||||||
|     def insert_new_auth(self, |     def insert_new_auth( | ||||||
|                         email: str, |         self, email: str, password: str, name: str, role: str = "pending" | ||||||
|                         password: str, |     ) -> Optional[UserModel]: | ||||||
|                         name: str, |  | ||||||
|                         role: str = "pending") -> Optional[UserModel]: |  | ||||||
|         print("insert_new_auth") |         print("insert_new_auth") | ||||||
| 
 | 
 | ||||||
|         id = str(uuid.uuid4()) |         id = str(uuid.uuid4()) | ||||||
| 
 | 
 | ||||||
|         auth = AuthModel(**{ |         auth = AuthModel( | ||||||
|             "id": id, |             **{"id": id, "email": email, "password": password, "active": True} | ||||||
|             "email": email, |         ) | ||||||
|             "password": password, |  | ||||||
|             "active": True |  | ||||||
|         }) |  | ||||||
|         result = Auth.create(**auth.model_dump()) |         result = Auth.create(**auth.model_dump()) | ||||||
| 
 | 
 | ||||||
|         user = Users.insert_new_user(id, name, email, role) |         user = Users.insert_new_user(id, name, email, role) | ||||||
|  | @ -104,8 +98,7 @@ class AuthsTable: | ||||||
|         else: |         else: | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|     def authenticate_user(self, email: str, |     def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: | ||||||
|                           password: str) -> Optional[UserModel]: |  | ||||||
|         print("authenticate_user", email) |         print("authenticate_user", email) | ||||||
|         try: |         try: | ||||||
|             auth = Auth.get(Auth.email == email, Auth.active == True) |             auth = Auth.get(Auth.email == email, Auth.active == True) | ||||||
|  | @ -129,6 +122,15 @@ class AuthsTable: | ||||||
|         except: |         except: | ||||||
|             return False |             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: |     def delete_auth_by_id(self, id: str) -> bool: | ||||||
|         try: |         try: | ||||||
|             # Delete User |             # Delete User | ||||||
|  | @ -137,8 +139,7 @@ class AuthsTable: | ||||||
|             if result: |             if result: | ||||||
|                 # Delete Auth |                 # Delete Auth | ||||||
|                 query = Auth.delete().where(Auth.id == id) |                 query = Auth.delete().where(Auth.id == id) | ||||||
|                 query.execute( |                 query.execute()  # Remove the rows, return number of rows removed. | ||||||
|                 )  # Remove the rows, return number of rows removed. |  | ||||||
| 
 | 
 | ||||||
|                 return True |                 return True | ||||||
|             else: |             else: | ||||||
|  |  | ||||||
|  | @ -44,17 +44,21 @@ class UserRoleUpdateForm(BaseModel): | ||||||
|     role: str |     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): |     def __init__(self, db): | ||||||
|         self.db = db |         self.db = db | ||||||
|         self.db.create_tables([User]) |         self.db.create_tables([User]) | ||||||
| 
 | 
 | ||||||
|     def insert_new_user(self, |     def insert_new_user( | ||||||
|                         id: str, |         self, id: str, name: str, email: str, role: str = "pending" | ||||||
|                         name: str, |     ) -> Optional[UserModel]: | ||||||
|                         email: str, |  | ||||||
|                         role: str = "pending") -> Optional[UserModel]: |  | ||||||
|         user = UserModel( |         user = UserModel( | ||||||
|             **{ |             **{ | ||||||
|                 "id": id, |                 "id": id, | ||||||
|  | @ -63,7 +67,8 @@ class UsersTable: | ||||||
|                 "role": role, |                 "role": role, | ||||||
|                 "profile_image_url": get_gravatar_url(email), |                 "profile_image_url": get_gravatar_url(email), | ||||||
|                 "timestamp": int(time.time()), |                 "timestamp": int(time.time()), | ||||||
|             }) |             } | ||||||
|  |         ) | ||||||
|         result = User.create(**user.model_dump()) |         result = User.create(**user.model_dump()) | ||||||
|         if result: |         if result: | ||||||
|             return user |             return user | ||||||
|  | @ -93,8 +98,7 @@ class UsersTable: | ||||||
|     def get_num_users(self) -> Optional[int]: |     def get_num_users(self) -> Optional[int]: | ||||||
|         return User.select().count() |         return User.select().count() | ||||||
| 
 | 
 | ||||||
|     def update_user_role_by_id(self, id: str, |     def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]: | ||||||
|                                role: str) -> Optional[UserModel]: |  | ||||||
|         try: |         try: | ||||||
|             query = User.update(role=role).where(User.id == id) |             query = User.update(role=role).where(User.id == id) | ||||||
|             query.execute() |             query.execute() | ||||||
|  | @ -104,6 +108,16 @@ class UsersTable: | ||||||
|         except: |         except: | ||||||
|             return None |             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: |     def delete_user_by_id(self, id: str) -> bool: | ||||||
|         try: |         try: | ||||||
|             # Delete User Chats |             # Delete User Chats | ||||||
|  | @ -112,8 +126,7 @@ class UsersTable: | ||||||
|             if result: |             if result: | ||||||
|                 # Delete User |                 # Delete User | ||||||
|                 query = User.delete().where(User.id == id) |                 query = User.delete().where(User.id == id) | ||||||
|                 query.execute( |                 query.execute()  # Remove the rows, return number of rows removed. | ||||||
|                 )  # Remove the rows, return number of rows removed. |  | ||||||
| 
 | 
 | ||||||
|                 return True |                 return True | ||||||
|             else: |             else: | ||||||
|  |  | ||||||
|  | @ -8,10 +8,10 @@ from pydantic import BaseModel | ||||||
| import time | import time | ||||||
| import uuid | 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 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 | from constants import ERROR_MESSAGES | ||||||
| 
 | 
 | ||||||
| router = APIRouter() | router = APIRouter() | ||||||
|  | @ -22,9 +22,7 @@ router = APIRouter() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @router.get("/", response_model=List[UserModel]) | @router.get("/", response_model=List[UserModel]) | ||||||
| async def get_users(skip: int = 0, | async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_current_user)): | ||||||
|                     limit: int = 50, |  | ||||||
|                     user=Depends(get_current_user)): |  | ||||||
|     if user.role != "admin": |     if user.role != "admin": | ||||||
|         raise HTTPException( |         raise HTTPException( | ||||||
|             status_code=status.HTTP_403_FORBIDDEN, |             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]) | @router.post("/{user_id}/update", response_model=Optional[UserModel]) | ||||||
| async def update_user_role(form_data: UserRoleUpdateForm, | async def update_user_by_id( | ||||||
|                            user=Depends(get_current_user)): |     user_id: str, form_data: UserUpdateForm, session_user=Depends(get_current_user) | ||||||
|     if user.role != "admin": | ): | ||||||
|  |     if session_user.role != "admin": | ||||||
|         raise HTTPException( |         raise HTTPException( | ||||||
|             status_code=status.HTTP_403_FORBIDDEN, |             status_code=status.HTTP_403_FORBIDDEN, | ||||||
|             detail=ERROR_MESSAGES.ACCESS_PROHIBITED, |             detail=ERROR_MESSAGES.ACCESS_PROHIBITED, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     if user.id != form_data.id: |     user = Users.get_user_by_id(user_id) | ||||||
|         return Users.update_user_role_by_id(form_data.id, form_data.role) | 
 | ||||||
|  |     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: |     else: | ||||||
|         raise HTTPException( |         raise HTTPException( | ||||||
|             status_code=status.HTTP_403_FORBIDDEN, |             status_code=status.HTTP_400_BAD_REQUEST, | ||||||
|             detail=ERROR_MESSAGES.ACTION_PROHIBITED, |             detail=ERROR_MESSAGES.USER_NOT_FOUND, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -9,6 +9,7 @@ | ||||||
| 			"version": "0.0.1", | 			"version": "0.0.1", | ||||||
| 			"dependencies": { | 			"dependencies": { | ||||||
| 				"@sveltejs/adapter-node": "^1.3.1", | 				"@sveltejs/adapter-node": "^1.3.1", | ||||||
|  | 				"dayjs": "^1.11.10", | ||||||
| 				"file-saver": "^2.0.5", | 				"file-saver": "^2.0.5", | ||||||
| 				"highlight.js": "^11.9.0", | 				"highlight.js": "^11.9.0", | ||||||
| 				"idb": "^7.1.1", | 				"idb": "^7.1.1", | ||||||
|  | @ -1577,6 +1578,11 @@ | ||||||
| 				"node": ">=4" | 				"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": { | 		"node_modules/debug": { | ||||||
| 			"version": "4.3.4", | 			"version": "4.3.4", | ||||||
| 			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | 			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | ||||||
|  | @ -4940,6 +4946,11 @@ | ||||||
| 			"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", | 			"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", | ||||||
| 			"dev": true | 			"dev": true | ||||||
| 		}, | 		}, | ||||||
|  | 		"dayjs": { | ||||||
|  | 			"version": "1.11.10", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", | ||||||
|  | 			"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" | ||||||
|  | 		}, | ||||||
| 		"debug": { | 		"debug": { | ||||||
| 			"version": "4.3.4", | 			"version": "4.3.4", | ||||||
| 			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | 			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | ||||||
|  |  | ||||||
|  | @ -40,6 +40,7 @@ | ||||||
| 	"type": "module", | 	"type": "module", | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@sveltejs/adapter-node": "^1.3.1", | 		"@sveltejs/adapter-node": "^1.3.1", | ||||||
|  | 		"dayjs": "^1.11.10", | ||||||
| 		"file-saver": "^2.0.5", | 		"file-saver": "^2.0.5", | ||||||
| 		"highlight.js": "^11.9.0", | 		"highlight.js": "^11.9.0", | ||||||
| 		"idb": "^7.1.1", | 		"idb": "^7.1.1", | ||||||
|  |  | ||||||
|  | @ -84,3 +84,43 @@ export const deleteUserById = async (token: string, userId: string) => { | ||||||
| 
 | 
 | ||||||
| 	return res; | 	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'; | 	import { fade, blur } from 'svelte/transition'; | ||||||
| 
 | 
 | ||||||
| 	export let show = true; | 	export let show = true; | ||||||
|  | 	export let size = 'md'; | ||||||
|  | 
 | ||||||
| 	let mounted = false; | 	let mounted = false; | ||||||
| 
 | 
 | ||||||
|  | 	const sizeToWidth = (size) => { | ||||||
|  | 		if (size === 'sm') { | ||||||
|  | 			return 'w-[30rem]'; | ||||||
|  | 		} else { | ||||||
|  | 			return 'w-[40rem]'; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
| 	onMount(() => { | 	onMount(() => { | ||||||
| 		mounted = true; | 		mounted = true; | ||||||
| 	}); | 	}); | ||||||
|  | @ -28,7 +38,9 @@ | ||||||
| 		}} | 		}} | ||||||
| 	> | 	> | ||||||
| 		<div | 		<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 }} | 			transition:fade={{ delay: 100, duration: 200 }} | ||||||
| 			on:click={(e) => { | 			on:click={(e) => { | ||||||
| 				e.stopPropagation(); | 				e.stopPropagation(); | ||||||
|  |  | ||||||
|  | @ -8,11 +8,15 @@ | ||||||
| 
 | 
 | ||||||
| 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; | 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; | ||||||
| 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths'; | 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths'; | ||||||
|  | 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte'; | ||||||
| 
 | 
 | ||||||
| 	let loaded = false; | 	let loaded = false; | ||||||
| 	let users = []; | 	let users = []; | ||||||
| 
 | 
 | ||||||
|  | 	let selectedUser = null; | ||||||
|  | 
 | ||||||
| 	let signUpEnabled = true; | 	let signUpEnabled = true; | ||||||
|  | 	let showEditUserModal = false; | ||||||
| 
 | 
 | ||||||
| 	const updateRoleHandler = async (id, role) => { | 	const updateRoleHandler = async (id, role) => { | ||||||
| 		const res = await updateUserRole(localStorage.token, id, role).catch((error) => { | 		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 deleteUserHandler = async (id) => { | ||||||
| 		const res = await deleteUserById(localStorage.token, id).catch((error) => { | 		const res = await deleteUserById(localStorage.token, id).catch((error) => { | ||||||
| 			toast.error(error); | 			toast.error(error); | ||||||
|  | @ -51,6 +66,17 @@ | ||||||
| 	}); | 	}); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | {#key selectedUser} | ||||||
|  | 	<EditUserModal | ||||||
|  | 		bind:show={showEditUserModal} | ||||||
|  | 		{selectedUser} | ||||||
|  | 		sessionUser={$user} | ||||||
|  | 		on:save={async () => { | ||||||
|  | 			users = await getUsers(localStorage.token); | ||||||
|  | 		}} | ||||||
|  | 	/> | ||||||
|  | {/key} | ||||||
|  | 
 | ||||||
| <div | <div | ||||||
| 	class=" bg-white dark:bg-gray-800 dark:text-gray-100 min-h-screen w-full flex justify-center font-mona" | 	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 | 												}}>{user.role}</button | ||||||
| 											> | 											> | ||||||
| 										</td> | 										</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 | 											<button | ||||||
| 												class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex" | 												class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex" | ||||||
| 												on:click={async () => { | 												on:click={async () => { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek