mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	Edit user on the web (#458)
* Added dispatch event for edit user * Fixed import location * solve merge conflict * Fixed issue not admin user can access admin page * Implemented edit user and password reset
This commit is contained in:
		
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,6 +1,9 @@ | ||||
| dev: | ||||
| 	rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans | ||||
|  | ||||
| dev-new: | ||||
| 	rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans | ||||
|  | ||||
| dev-update: | ||||
| 	rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans | ||||
|  | ||||
|   | ||||
| @@ -48,9 +48,10 @@ input:focus-visible { | ||||
|     outline-offset: 0px !important; | ||||
|     outline: none !important; | ||||
| } | ||||
|  | ||||
| @layer utilities { | ||||
|     .immich-form-input { | ||||
| 		@apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm; | ||||
|         @apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm ; | ||||
|     } | ||||
|  | ||||
|     .immich-form-label { | ||||
|   | ||||
| @@ -31,6 +31,9 @@ | ||||
| 				<td class="text-sm px-4 w-1/4 text-ellipsis">{user.lastName}</td> | ||||
| 				<td class="text-sm px-4 w-1/4 text-ellipsis" | ||||
| 					><button | ||||
| 						on:click={() => { | ||||
| 							dispatch('edit-user', { user }); | ||||
| 						}} | ||||
| 						class="bg-immich-primary text-gray-100 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75" | ||||
| 						><PencilOutline size="20" /></button | ||||
| 					></td | ||||
| @@ -40,4 +43,4 @@ | ||||
| 	</tbody> | ||||
| </table> | ||||
|  | ||||
| <button on:click={() => dispatch('createUser')} class="immich-btn-primary">Create user</button> | ||||
| <button on:click={() => dispatch('create-user')} class="immich-btn-primary">Create user</button> | ||||
|   | ||||
| @@ -5,8 +5,8 @@ | ||||
|   let error: string; | ||||
|   let success: string; | ||||
|  | ||||
| 	let password: string = ''; | ||||
| 	let confirmPassowrd: string = ''; | ||||
|   let password = ''; | ||||
|   let confirmPassowrd = ''; | ||||
|  | ||||
|   let canCreateUser = false; | ||||
|  | ||||
| @@ -22,7 +22,6 @@ | ||||
|   const dispatch = createEventDispatcher(); | ||||
|  | ||||
|   async function registerUser(event: SubmitEvent) { | ||||
| 		console.log('registerUser'); | ||||
|     if (canCreateUser) { | ||||
|       error = ''; | ||||
|  | ||||
| @@ -35,7 +34,7 @@ | ||||
|       const firstName = form.get('firstName'); | ||||
|       const lastName = form.get('lastName'); | ||||
|  | ||||
| 			const { status } = await api.userApi.createUser({ | ||||
|       const {status} = await api.userApi.createUser({ | ||||
|         email: String(email), | ||||
|         password: String(password), | ||||
|         firstName: String(firstName), | ||||
| @@ -54,9 +53,9 @@ | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8"> | ||||
| <div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8"> | ||||
|     <div class="flex flex-col place-items-center place-content-center gap-4 px-4"> | ||||
| 		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" /> | ||||
|         <img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo"/> | ||||
|         <h1 class="text-2xl text-immich-primary font-medium">Create new user</h1> | ||||
|         <p class="text-sm border rounded-md p-4 font-mono text-gray-600"> | ||||
|             Please provide your user with the password, they will have to change it on their first sign | ||||
| @@ -67,7 +66,7 @@ | ||||
|     <form on:submit|preventDefault={registerUser} autocomplete="off"> | ||||
|         <div class="m-4 flex flex-col gap-2"> | ||||
|             <label class="immich-form-label" for="email">Email</label> | ||||
| 			<input class="immich-form-input" id="email" name="email" type="email" required /> | ||||
|             <input class="immich-form-input" id="email" name="email" type="email" required/> | ||||
|         </div> | ||||
|  | ||||
|         <div class="m-4 flex flex-col gap-2"> | ||||
| @@ -96,12 +95,12 @@ | ||||
|  | ||||
|         <div class="m-4 flex flex-col gap-2"> | ||||
|             <label class="immich-form-label" for="firstName">First Name</label> | ||||
| 			<input class="immich-form-input" id="firstName" name="firstName" type="text" required /> | ||||
|             <input class="immich-form-input" id="firstName" name="firstName" type="text" required/> | ||||
|         </div> | ||||
|  | ||||
|         <div class="m-4 flex flex-col gap-2"> | ||||
|             <label class="immich-form-label" for="lastName">Last Name</label> | ||||
| 			<input class="immich-form-input" id="lastName" name="lastName" type="text" required /> | ||||
|             <input class="immich-form-input" id="lastName" name="lastName" type="text" required/> | ||||
|         </div> | ||||
|  | ||||
|         {#if error} | ||||
| @@ -114,8 +113,9 @@ | ||||
|         <div class="flex w-full"> | ||||
|             <button | ||||
|                     type="submit" | ||||
| 				class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full" | ||||
| 				>Create</button | ||||
|                     class="m-4 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium" | ||||
|             >Create | ||||
|             </button | ||||
|             > | ||||
|         </div> | ||||
|     </form> | ||||
|   | ||||
							
								
								
									
										103
									
								
								web/src/lib/components/forms/edit-user-form.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								web/src/lib/components/forms/edit-user-form.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| <script lang="ts"> | ||||
|   import { api, UserResponseDto } from '@api'; | ||||
|   import { createEventDispatcher } from 'svelte'; | ||||
|   import AccountEditOutline from 'svelte-material-icons/AccountEditOutline.svelte'; | ||||
|  | ||||
|   export let user: UserResponseDto; | ||||
|  | ||||
|   let error: string; | ||||
|   let success: string; | ||||
|  | ||||
|   const dispatch = createEventDispatcher(); | ||||
|  | ||||
|   // eslint-disable-next-line no-undef | ||||
|   const editUser = async (event: SubmitEvent) => { | ||||
|  | ||||
|     const formElement = event.target as HTMLFormElement; | ||||
|     const form = new FormData(formElement); | ||||
|  | ||||
|     const firstName = form.get('firstName'); | ||||
|     const lastName = form.get('lastName'); | ||||
|  | ||||
|  | ||||
|     const {status} = await api.userApi.updateUser({ | ||||
|       id: user.id, | ||||
|       firstName: firstName.toString(), | ||||
|       lastName: lastName.toString() | ||||
|     }).catch(e => console.log("Error updating user ", e)); | ||||
|  | ||||
|     if (status == 200) { | ||||
|       dispatch('edit-success'); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const resetPassword = async () => { | ||||
|     const defaultPassword = 'password' | ||||
|  | ||||
|     const {status} = await api.userApi.updateUser({ | ||||
|       id: user.id, | ||||
|       password: defaultPassword, | ||||
|       shouldChangePassword: true, | ||||
|  | ||||
|     }).catch(e => console.log("Error updating user ", e)); | ||||
|  | ||||
|     if (status == 200) { | ||||
|       dispatch('reset-password-success'); | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8"> | ||||
|     <div class="flex flex-col place-items-center place-content-center gap-4 px-4"> | ||||
|         <!--        <img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo"/>--> | ||||
|         <AccountEditOutline size="4em" color="#4250affe"/> | ||||
|         <h1 class="text-2xl text-immich-primary font-medium">Edit user</h1> | ||||
|     </div> | ||||
|  | ||||
|     <form on:submit|preventDefault={editUser} autocomplete="off"> | ||||
|         <div class="m-4 flex flex-col gap-2"> | ||||
|             <label class="immich-form-label" for="email">Email | ||||
|                 (cannot change)</label> | ||||
|             <input class="immich-form-input disabled:bg-gray-200 hover:cursor-not-allowed" | ||||
|                    id="email" name="email" | ||||
|                    type="email" disabled | ||||
|                    bind:value={user.email}/> | ||||
|         </div> | ||||
|  | ||||
|  | ||||
|         <div class="m-4 flex flex-col gap-2"> | ||||
|             <label class="immich-form-label" for="firstName">First Name</label> | ||||
|             <input class="immich-form-input" id="firstName" name="firstName" type="text" required | ||||
|                    bind:value={user.firstName}/> | ||||
|         </div> | ||||
|  | ||||
|         <div class="m-4 flex flex-col gap-2"> | ||||
|             <label class="immich-form-label" for="lastName">Last Name</label> | ||||
|             <input class="immich-form-input" id="lastName" name="lastName" type="text" required | ||||
|                    bind:value={user.lastName}/> | ||||
|         </div> | ||||
|  | ||||
|  | ||||
|         {#if error} | ||||
|             <p class="text-red-400 ml-4 text-sm">{error}</p> | ||||
|         {/if} | ||||
|  | ||||
|         {#if success} | ||||
|             <p class="text-immich-primary ml-4 text-sm">{success}</p> | ||||
|         {/if} | ||||
|         <div class="flex w-full px-4 gap-4 mt-8"> | ||||
|             <button on:click={resetPassword} | ||||
|                     class="flex-1 transition-colors bg-[#F9DEDC] hover:bg-red-50 text-[#410E0B] px-6 py-3 rounded-full w-full font-medium" | ||||
|  | ||||
|             >Reset password | ||||
|             </button | ||||
|             > | ||||
|             <button | ||||
|                     type="submit" | ||||
|                     class="flex-1 transition-colors bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium" | ||||
|             >Confirm | ||||
|             </button | ||||
|             > | ||||
|         </div> | ||||
|     </form> | ||||
| </div> | ||||
| @@ -1,8 +1,9 @@ | ||||
| <script context="module" lang="ts"> | ||||
|   import type { Load } from '@sveltejs/kit'; | ||||
|   import { api, UserResponseDto } from '@api'; | ||||
|   import { browser } from '$app/env'; | ||||
|  | ||||
| 	export const load: Load = async ({ fetch, session }) => { | ||||
|   export const load: Load = async ({fetch, session}) => { | ||||
|     if (!browser && !session.user) { | ||||
|       return { | ||||
|         status: 302, | ||||
| @@ -11,10 +12,17 @@ | ||||
|     } | ||||
|  | ||||
|     try { | ||||
| 			const [user, allUsers] = await Promise.all([ | ||||
| 				fetch('/data/user/get-my-user-info').then((r) => r.json()), | ||||
| 				fetch('/data/user/get-all-users?isAll=false').then((r) => r.json()) | ||||
| 			]); | ||||
|  | ||||
|  | ||||
|       const user: UserResponseDto = await fetch('/data/user/get-my-user-info').then((r) => r.json()); | ||||
|       const allUsers: UserResponseDto[] = await fetch<UserResponseDto[]>('/data/user/get-all-users?isAll=false').then((r) => r.json()); | ||||
|  | ||||
|       if (!user.isAdmin) { | ||||
|         return { | ||||
|           status: 302, | ||||
|           redirect: '/photos' | ||||
|         }; | ||||
|       } | ||||
|  | ||||
|       return { | ||||
|         status: 200, | ||||
| @@ -35,7 +43,6 @@ | ||||
| <script lang="ts"> | ||||
|   import { onMount } from 'svelte'; | ||||
|  | ||||
| 	import type { ImmichUser } from '$lib/models/immich-user'; | ||||
|   import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection'; | ||||
|   import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte'; | ||||
|   import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte'; | ||||
| @@ -43,15 +50,20 @@ | ||||
|   import UserManagement from '$lib/components/admin-page/user-management.svelte'; | ||||
|   import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; | ||||
|   import CreateUserForm from '$lib/components/forms/create-user-form.svelte'; | ||||
|   import EditUserForm from '$lib/components/forms/edit-user-form.svelte'; | ||||
|   import StatusBox from '$lib/components/shared-components/status-box.svelte'; | ||||
| 	import { browser } from '$app/env'; | ||||
|  | ||||
|  | ||||
|   let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT; | ||||
|  | ||||
| 	export let user: ImmichUser; | ||||
|   export let user: UserResponseDto; | ||||
|   export let allUsers: UserResponseDto[]; | ||||
|  | ||||
| 	let shouldShowCreateUserForm: boolean; | ||||
|   let editUser: UserResponseDto; | ||||
|  | ||||
|   let shouldShowEditUserForm = false; | ||||
|   let shouldShowCreateUserForm = false; | ||||
|   let shouldShowInfoPanel = false; | ||||
|  | ||||
|   const onButtonClicked = (buttonType: CustomEvent) => { | ||||
|     selectedAction = buttonType.detail['actionType'] as AdminSideBarSelection; | ||||
| @@ -62,27 +74,76 @@ | ||||
|   }); | ||||
|  | ||||
|   const onUserCreated = async () => { | ||||
| 		const { data } = await api.userApi.getAllUsers(false); | ||||
|     const {data} = await api.userApi.getAllUsers(false); | ||||
|     allUsers = data; | ||||
|  | ||||
|     shouldShowCreateUserForm = false; | ||||
|   }; | ||||
|  | ||||
|   const editUserHandler = async (event: CustomEvent) => { | ||||
|     const {user} = event.detail; | ||||
|     editUser = user; | ||||
|     shouldShowEditUserForm = true; | ||||
|   }; | ||||
|  | ||||
|   const onEditUserSuccess = async () => { | ||||
|     const {data} = await api.userApi.getAllUsers(false); | ||||
|     allUsers = data; | ||||
|     shouldShowEditUserForm = false; | ||||
|   } | ||||
|  | ||||
|   const onEditPasswordSuccess = async () => { | ||||
|     const {data} = await api.userApi.getAllUsers(false); | ||||
|     allUsers = data; | ||||
|     shouldShowEditUserForm = false; | ||||
|     shouldShowInfoPanel = true; | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <svelte:head> | ||||
|     <title>Administration - Immich</title> | ||||
| </svelte:head> | ||||
|  | ||||
| <NavigationBar {user} /> | ||||
| <NavigationBar {user}/> | ||||
|  | ||||
| {#if shouldShowCreateUserForm} | ||||
|     <FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}> | ||||
| 		<div> | ||||
| 			<CreateUserForm on:user-created={onUserCreated} /> | ||||
|         <CreateUserForm on:user-created={onUserCreated}/> | ||||
|     </FullScreenModal> | ||||
| {/if} | ||||
|  | ||||
| {#if shouldShowEditUserForm} | ||||
|     <FullScreenModal on:clickOutside={() => (shouldShowEditUserForm = false)}> | ||||
|         <EditUserForm user={editUser} on:edit-success={onEditUserSuccess} | ||||
|                       on:reset-password-success={onEditPasswordSuccess}/> | ||||
|     </FullScreenModal> | ||||
| {/if} | ||||
|  | ||||
| {#if shouldShowInfoPanel} | ||||
|     <FullScreenModal on:clickOutside={() => (shouldShowInfoPanel = false)}> | ||||
|  | ||||
|         <div class="border bg-white shadow-sm w-[500px] rounded-3xl p-8 text-sm"> | ||||
|             <h1 class="font-bold text-immich-primary text-lg mb-4">Password reset success</h1> | ||||
|  | ||||
|             <p> | ||||
|                 The user's password has been reset to the default <code | ||||
|                     class="font-bold bg-gray-200 px-2 py-1 rounded-md text-immich-primary">password</code> | ||||
|                 <br> | ||||
|                 Please inform the user, and they will need to change the password at the next log-on. | ||||
|             </p> | ||||
|  | ||||
|             <div class="flex w-full"> | ||||
|                 <button | ||||
|                         on:click={() => shouldShowInfoPanel = false} | ||||
|                         class="mt-6 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium" | ||||
|                 >Done | ||||
|                 </button | ||||
|                 > | ||||
|             </div> | ||||
|         </div> | ||||
|     </FullScreenModal> | ||||
| {/if} | ||||
|  | ||||
|  | ||||
| <section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen"> | ||||
|     <section id="admin-sidebar" class="pt-8 pr-6 flex flex-col"> | ||||
|         <SideBarButton | ||||
| @@ -94,21 +155,27 @@ | ||||
|         /> | ||||
|  | ||||
|         <div class="mb-6 mt-auto"> | ||||
| 			<StatusBox /> | ||||
|             <StatusBox/> | ||||
|         </div> | ||||
|     </section> | ||||
|     <section class="overflow-y-auto relative"> | ||||
|         <div id="setting-title" class="pt-10 fixed w-full z-50 bg-immich-bg"> | ||||
|             <h1 class="text-lg ml-8 mb-4 text-immich-primary font-medium">{selectedAction}</h1> | ||||
| 			<hr /> | ||||
|             <hr/> | ||||
|         </div> | ||||
|  | ||||
|  | ||||
|         <section id="setting-content" class="relative pt-[85px] flex place-content-center"> | ||||
|             <section class="w-[800px] pt-4"> | ||||
|                 {#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT} | ||||
| 					<UserManagement {allUsers} on:createUser={() => (shouldShowCreateUserForm = true)} /> | ||||
|                     <UserManagement | ||||
|                             {allUsers} | ||||
|                             on:create-user={() => (shouldShowCreateUserForm = true)} | ||||
|                             on:edit-user={editUserHandler} | ||||
|                     /> | ||||
|                 {/if} | ||||
|             </section> | ||||
|         </section> | ||||
|     </section> | ||||
|  | ||||
| </section> | ||||
|   | ||||
| @@ -2,24 +2,26 @@ | ||||
|   export const prerender = false; | ||||
|   import type { Load } from '@sveltejs/kit'; | ||||
|   import { api } from '@api'; | ||||
|   import { browser } from '$app/env'; | ||||
|  | ||||
|   export const load: Load = async () => { | ||||
|     if (browser) { | ||||
|       try { | ||||
| 				const { data: user } = await api.userApi.getMyUserInfo(); | ||||
|         const {data: user} = await api.userApi.getMyUserInfo(); | ||||
|  | ||||
|         return { | ||||
|           status: 302, | ||||
|           redirect: '/photos' | ||||
|         }; | ||||
| 			} catch (e) {} | ||||
|       } catch (e) { | ||||
|       } | ||||
|  | ||||
| 			const { data } = await api.userApi.getUserCount(); | ||||
|       const {data} = await api.userApi.getUserCount(); | ||||
|  | ||||
|       return { | ||||
|         status: 200, | ||||
|         props: { | ||||
| 					isAdminUserExist: data.userCount == 0 ? false : true | ||||
|           isAdminUserExist: data.userCount != 0 | ||||
|         } | ||||
|       }; | ||||
|     } | ||||
| @@ -28,29 +30,29 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
|   import { goto } from '$app/navigation'; | ||||
| 	import { browser } from '$app/env'; | ||||
|  | ||||
|   export let isAdminUserExist: boolean; | ||||
|  | ||||
|   async function onGettingStartedClicked() { | ||||
| 		isAdminUserExist ? goto('/auth/login') : goto('/auth/register'); | ||||
|     isAdminUserExist ? await goto('/auth/login') : await goto('/auth/register'); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <svelte:head> | ||||
|     <title>Welcome 🎉 - Immich</title> | ||||
| 	<meta name="description" content="Immich Web Interface" /> | ||||
|     <meta name="description" content="Immich Web Interface"/> | ||||
| </svelte:head> | ||||
|  | ||||
| <section class="h-screen w-screen flex place-items-center place-content-center"> | ||||
|     <div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]"> | ||||
|         <div class="flex place-items-center place-content-center "> | ||||
| 			<img class="text-center" src="immich-logo.svg" height="200" width="200" alt="immich-logo" /> | ||||
|             <img class="text-center" src="immich-logo.svg" height="200" width="200" alt="immich-logo"/> | ||||
|         </div> | ||||
|         <h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1> | ||||
|         <button | ||||
|                 class="border px-4 py-2 rounded-md bg-immich-primary hover:bg-immich-primary/75 text-white font-bold w-[200px]" | ||||
| 			on:click={onGettingStartedClicked}>Getting Started</button | ||||
|                 on:click={onGettingStartedClicked}>Getting Started | ||||
|         </button | ||||
|         > | ||||
|     </div> | ||||
| </section> | ||||
|   | ||||
| @@ -3,8 +3,9 @@ | ||||
|  | ||||
|   import type { Load } from '@sveltejs/kit'; | ||||
|   import { AlbumResponseDto, api, UserResponseDto } from '@api'; | ||||
|   import { browser } from '$app/env'; | ||||
|  | ||||
| 	export const load: Load = async ({ fetch, session }) => { | ||||
|   export const load: Load = async ({fetch, session}) => { | ||||
|     if (!browser && !session.user) { | ||||
|       return { | ||||
|         status: 302, | ||||
| @@ -40,14 +41,13 @@ | ||||
|   import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte'; | ||||
|   import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte'; | ||||
|   import { goto } from '$app/navigation'; | ||||
| 	import { browser } from '$app/env'; | ||||
|  | ||||
|   export let user: UserResponseDto; | ||||
|   export let sharedAlbums: AlbumResponseDto[]; | ||||
|  | ||||
|   const createSharedAlbum = async () => { | ||||
|     try { | ||||
| 			const { data: newAlbum } = await api.albumApi.createAlbum({ | ||||
|       const {data: newAlbum} = await api.albumApi.createAlbum({ | ||||
|         albumName: 'Untitled' | ||||
|       }); | ||||
|  | ||||
| @@ -63,11 +63,11 @@ | ||||
| </svelte:head> | ||||
|  | ||||
| <section> | ||||
| 	<NavigationBar {user} on:uploadClicked={() => {}} /> | ||||
|     <NavigationBar {user} on:uploadClicked={() => {}}/> | ||||
| </section> | ||||
|  | ||||
| <section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg"> | ||||
| 	<SideBar /> | ||||
|     <SideBar/> | ||||
|  | ||||
|     <section class="overflow-y-auto relative"> | ||||
|         <section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg"> | ||||
| @@ -83,7 +83,7 @@ | ||||
|                             class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700" | ||||
|                     > | ||||
| 						<span> | ||||
| 							<PlusBoxOutline size="18" /> | ||||
| 							<PlusBoxOutline size="18"/> | ||||
| 						</span> | ||||
|                         <p>Create shared album</p> | ||||
|                     </button> | ||||
| @@ -91,14 +91,15 @@ | ||||
|             </div> | ||||
|  | ||||
|             <div class="my-4"> | ||||
| 				<hr /> | ||||
|                 <hr/> | ||||
|             </div> | ||||
|  | ||||
|             <!-- Share Album List --> | ||||
|             <div class="w-full flex flex-col place-items-center"> | ||||
|                 {#each sharedAlbums as album} | ||||
|                     <a sveltekit:prefetch href={`albums/${album.id}`}> | ||||
| 						<SharedAlbumListTile {album} {user} /></a | ||||
|                         <SharedAlbumListTile {album} {user}/> | ||||
|                     </a | ||||
|                     > | ||||
|                 {/each} | ||||
|             </div> | ||||
| @@ -108,7 +109,7 @@ | ||||
|                 <div | ||||
|                         class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center" | ||||
|                 > | ||||
| 					<img src="/empty-2.svg" alt="Empty shared album" width="500" /> | ||||
|                     <img src="/empty-2.svg" alt="Empty shared album" width="500"/> | ||||
|                     <p class="text-center text-immich-text-gray-500"> | ||||
|                         Create a shared album to share photos and videos with people in your network | ||||
|                     </p> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user