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:
 | 
					dev:
 | 
				
			||||||
	rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
 | 
						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:
 | 
					dev-update:
 | 
				
			||||||
	rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
 | 
						rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,85 +6,86 @@
 | 
				
			|||||||
@tailwind utilities;
 | 
					@tailwind utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:root {
 | 
					:root {
 | 
				
			||||||
	font-family: 'Work Sans', sans-serif;
 | 
					    font-family: 'Work Sans', sans-serif;
 | 
				
			||||||
	/* --immich-icon-button-hover-color: #d3d3d3; */
 | 
					    /* --immich-icon-button-hover-color: #d3d3d3; */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html {
 | 
					html {
 | 
				
			||||||
	height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
	width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html::-webkit-scrollbar {
 | 
					html::-webkit-scrollbar {
 | 
				
			||||||
	width: 8px;
 | 
					    width: 8px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Track */
 | 
					/* Track */
 | 
				
			||||||
html::-webkit-scrollbar-track {
 | 
					html::-webkit-scrollbar-track {
 | 
				
			||||||
	background: #f1f1f1;
 | 
					    background: #f1f1f1;
 | 
				
			||||||
	border-radius: 16px;
 | 
					    border-radius: 16px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Handle */
 | 
					/* Handle */
 | 
				
			||||||
html::-webkit-scrollbar-thumb {
 | 
					html::-webkit-scrollbar-thumb {
 | 
				
			||||||
	background: rgba(85, 86, 87, 0.408);
 | 
					    background: rgba(85, 86, 87, 0.408);
 | 
				
			||||||
	border-radius: 16px;
 | 
					    border-radius: 16px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Handle on hover */
 | 
					/* Handle on hover */
 | 
				
			||||||
html::-webkit-scrollbar-thumb:hover {
 | 
					html::-webkit-scrollbar-thumb:hover {
 | 
				
			||||||
	background: #4250afad;
 | 
					    background: #4250afad;
 | 
				
			||||||
	border-radius: 16px;
 | 
					    border-radius: 16px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
	/* min-height: 100vh; */
 | 
					    /* min-height: 100vh; */
 | 
				
			||||||
	margin: 0;
 | 
					    margin: 0;
 | 
				
			||||||
	background-color: #f6f8fe;
 | 
					    background-color: #f6f8fe;
 | 
				
			||||||
	color: #5f6368;
 | 
					    color: #5f6368;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input:focus-visible {
 | 
					input:focus-visible {
 | 
				
			||||||
	outline-offset: 0px !important;
 | 
					    outline-offset: 0px !important;
 | 
				
			||||||
	outline: none !important;
 | 
					    outline: none !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@layer utilities {
 | 
					@layer utilities {
 | 
				
			||||||
	.immich-form-input {
 | 
					    .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 {
 | 
					    .immich-form-label {
 | 
				
			||||||
		@apply font-medium text-sm text-gray-500;
 | 
					        @apply font-medium text-sm text-gray-500;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	.immich-btn-primary {
 | 
					    .immich-btn-primary {
 | 
				
			||||||
		@apply bg-immich-primary text-gray-100 border rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary hover:shadow-lg text-sm font-medium;
 | 
					        @apply bg-immich-primary text-gray-100 border rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary hover:shadow-lg text-sm font-medium;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	.immich-text-button {
 | 
					    .immich-text-button {
 | 
				
			||||||
		@apply flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium;
 | 
					        @apply flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* width */
 | 
					    /* width */
 | 
				
			||||||
	.immich-scrollbar::-webkit-scrollbar {
 | 
					    .immich-scrollbar::-webkit-scrollbar {
 | 
				
			||||||
		width: 8px;
 | 
					        width: 8px;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Track */
 | 
					    /* Track */
 | 
				
			||||||
	.immich-scrollbar::-webkit-scrollbar-track {
 | 
					    .immich-scrollbar::-webkit-scrollbar-track {
 | 
				
			||||||
		background: #f1f1f1;
 | 
					        background: #f1f1f1;
 | 
				
			||||||
		border-radius: 16px;
 | 
					        border-radius: 16px;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Handle */
 | 
					    /* Handle */
 | 
				
			||||||
	.immich-scrollbar::-webkit-scrollbar-thumb {
 | 
					    .immich-scrollbar::-webkit-scrollbar-thumb {
 | 
				
			||||||
		background: rgba(85, 86, 87, 0.408);
 | 
					        background: rgba(85, 86, 87, 0.408);
 | 
				
			||||||
		border-radius: 16px;
 | 
					        border-radius: 16px;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Handle on hover */
 | 
					    /* Handle on hover */
 | 
				
			||||||
	.immich-scrollbar::-webkit-scrollbar-thumb:hover {
 | 
					    .immich-scrollbar::-webkit-scrollbar-thumb:hover {
 | 
				
			||||||
		background: #4250afad;
 | 
					        background: #4250afad;
 | 
				
			||||||
		border-radius: 16px;
 | 
					        border-radius: 16px;
 | 
				
			||||||
	}
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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">{user.lastName}</td>
 | 
				
			||||||
				<td class="text-sm px-4 w-1/4 text-ellipsis"
 | 
									<td class="text-sm px-4 w-1/4 text-ellipsis"
 | 
				
			||||||
					><button
 | 
										><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"
 | 
											class="bg-immich-primary text-gray-100 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
 | 
				
			||||||
						><PencilOutline size="20" /></button
 | 
											><PencilOutline size="20" /></button
 | 
				
			||||||
					></td
 | 
										></td
 | 
				
			||||||
@@ -40,4 +43,4 @@
 | 
				
			|||||||
	</tbody>
 | 
						</tbody>
 | 
				
			||||||
</table>
 | 
					</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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,122 +1,122 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	import { api } from '@api';
 | 
					  import { api } from '@api';
 | 
				
			||||||
	import { createEventDispatcher } from 'svelte';
 | 
					  import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let error: string;
 | 
					  let error: string;
 | 
				
			||||||
	let success: string;
 | 
					  let success: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let password: string = '';
 | 
					  let password = '';
 | 
				
			||||||
	let confirmPassowrd: string = '';
 | 
					  let confirmPassowrd = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let canCreateUser = false;
 | 
					  let canCreateUser = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	$: {
 | 
					  $: {
 | 
				
			||||||
		if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
 | 
					    if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
 | 
				
			||||||
			error = 'Password does not match';
 | 
					      error = 'Password does not match';
 | 
				
			||||||
			canCreateUser = false;
 | 
					      canCreateUser = false;
 | 
				
			||||||
		} else {
 | 
					    } else {
 | 
				
			||||||
			error = '';
 | 
					      error = '';
 | 
				
			||||||
			canCreateUser = true;
 | 
					      canCreateUser = true;
 | 
				
			||||||
		}
 | 
					    }
 | 
				
			||||||
	}
 | 
					  }
 | 
				
			||||||
	const dispatch = createEventDispatcher();
 | 
					  const dispatch = createEventDispatcher();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async function registerUser(event: SubmitEvent) {
 | 
					  async function registerUser(event: SubmitEvent) {
 | 
				
			||||||
		console.log('registerUser');
 | 
					    if (canCreateUser) {
 | 
				
			||||||
		if (canCreateUser) {
 | 
					      error = '';
 | 
				
			||||||
			error = '';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const formElement = event.target as HTMLFormElement;
 | 
					      const formElement = event.target as HTMLFormElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const form = new FormData(formElement);
 | 
					      const form = new FormData(formElement);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const email = form.get('email');
 | 
					      const email = form.get('email');
 | 
				
			||||||
			const password = form.get('password');
 | 
					      const password = form.get('password');
 | 
				
			||||||
			const firstName = form.get('firstName');
 | 
					      const firstName = form.get('firstName');
 | 
				
			||||||
			const lastName = form.get('lastName');
 | 
					      const lastName = form.get('lastName');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const { status } = await api.userApi.createUser({
 | 
					      const {status} = await api.userApi.createUser({
 | 
				
			||||||
				email: String(email),
 | 
					        email: String(email),
 | 
				
			||||||
				password: String(password),
 | 
					        password: String(password),
 | 
				
			||||||
				firstName: String(firstName),
 | 
					        firstName: String(firstName),
 | 
				
			||||||
				lastName: String(lastName)
 | 
					        lastName: String(lastName)
 | 
				
			||||||
			});
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (status === 201) {
 | 
					      if (status === 201) {
 | 
				
			||||||
				success = 'New user created';
 | 
					        success = 'New user created';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				dispatch('user-created');
 | 
					        dispatch('user-created');
 | 
				
			||||||
				return;
 | 
					        return;
 | 
				
			||||||
			} else {
 | 
					      } else {
 | 
				
			||||||
				error = 'Error create user account';
 | 
					        error = 'Error create user account';
 | 
				
			||||||
			}
 | 
					      }
 | 
				
			||||||
		}
 | 
					    }
 | 
				
			||||||
	}
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</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">
 | 
					    <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>
 | 
					        <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">
 | 
					        <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
 | 
					            Please provide your user with the password, they will have to change it on their first sign
 | 
				
			||||||
			in.
 | 
					            in.
 | 
				
			||||||
		</p>
 | 
					        </p>
 | 
				
			||||||
	</div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<form on:submit|preventDefault={registerUser} autocomplete="off">
 | 
					    <form on:submit|preventDefault={registerUser} autocomplete="off">
 | 
				
			||||||
		<div class="m-4 flex flex-col gap-2">
 | 
					        <div class="m-4 flex flex-col gap-2">
 | 
				
			||||||
			<label class="immich-form-label" for="email">Email</label>
 | 
					            <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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="m-4 flex flex-col gap-2">
 | 
					        <div class="m-4 flex flex-col gap-2">
 | 
				
			||||||
			<label class="immich-form-label" for="password">Password</label>
 | 
					            <label class="immich-form-label" for="password">Password</label>
 | 
				
			||||||
			<input
 | 
					            <input
 | 
				
			||||||
				class="immich-form-input"
 | 
					                    class="immich-form-input"
 | 
				
			||||||
				id="password"
 | 
					                    id="password"
 | 
				
			||||||
				name="password"
 | 
					                    name="password"
 | 
				
			||||||
				type="password"
 | 
					                    type="password"
 | 
				
			||||||
				required
 | 
					                    required
 | 
				
			||||||
				bind:value={password}
 | 
					                    bind:value={password}
 | 
				
			||||||
			/>
 | 
					            />
 | 
				
			||||||
		</div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="m-4 flex flex-col gap-2">
 | 
					        <div class="m-4 flex flex-col gap-2">
 | 
				
			||||||
			<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
 | 
					            <label class="immich-form-label" for="confirmPassword">Confirm Password</label>
 | 
				
			||||||
			<input
 | 
					            <input
 | 
				
			||||||
				class="immich-form-input"
 | 
					                    class="immich-form-input"
 | 
				
			||||||
				id="confirmPassword"
 | 
					                    id="confirmPassword"
 | 
				
			||||||
				name="password"
 | 
					                    name="password"
 | 
				
			||||||
				type="password"
 | 
					                    type="password"
 | 
				
			||||||
				required
 | 
					                    required
 | 
				
			||||||
				bind:value={confirmPassowrd}
 | 
					                    bind:value={confirmPassowrd}
 | 
				
			||||||
			/>
 | 
					            />
 | 
				
			||||||
		</div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="m-4 flex flex-col gap-2">
 | 
					        <div class="m-4 flex flex-col gap-2">
 | 
				
			||||||
			<label class="immich-form-label" for="firstName">First Name</label>
 | 
					            <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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="m-4 flex flex-col gap-2">
 | 
					        <div class="m-4 flex flex-col gap-2">
 | 
				
			||||||
			<label class="immich-form-label" for="lastName">Last Name</label>
 | 
					            <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>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{#if error}
 | 
					        {#if error}
 | 
				
			||||||
			<p class="text-red-400 ml-4 text-sm">{error}</p>
 | 
					            <p class="text-red-400 ml-4 text-sm">{error}</p>
 | 
				
			||||||
		{/if}
 | 
					        {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{#if success}
 | 
					        {#if success}
 | 
				
			||||||
			<p class="text-immich-primary ml-4 text-sm">{success}</p>
 | 
					            <p class="text-immich-primary ml-4 text-sm">{success}</p>
 | 
				
			||||||
		{/if}
 | 
					        {/if}
 | 
				
			||||||
		<div class="flex w-full">
 | 
					        <div class="flex w-full">
 | 
				
			||||||
			<button
 | 
					            <button
 | 
				
			||||||
				type="submit"
 | 
					                    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"
 | 
					                    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
 | 
					            >Create
 | 
				
			||||||
			>
 | 
					            </button
 | 
				
			||||||
		</div>
 | 
					            >
 | 
				
			||||||
	</form>
 | 
					        </div>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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,114 +1,181 @@
 | 
				
			|||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
	import type { Load } from '@sveltejs/kit';
 | 
					  import type { Load } from '@sveltejs/kit';
 | 
				
			||||||
	import { api, UserResponseDto } from '@api';
 | 
					  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) {
 | 
					    if (!browser && !session.user) {
 | 
				
			||||||
			return {
 | 
					      return {
 | 
				
			||||||
				status: 302,
 | 
					        status: 302,
 | 
				
			||||||
				redirect: '/auth/login'
 | 
					        redirect: '/auth/login'
 | 
				
			||||||
			};
 | 
					      };
 | 
				
			||||||
		}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		try {
 | 
					    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())
 | 
					 | 
				
			||||||
			]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return {
 | 
					
 | 
				
			||||||
				status: 200,
 | 
					      const user: UserResponseDto = await fetch('/data/user/get-my-user-info').then((r) => r.json());
 | 
				
			||||||
				props: {
 | 
					      const allUsers: UserResponseDto[] = await fetch<UserResponseDto[]>('/data/user/get-all-users?isAll=false').then((r) => r.json());
 | 
				
			||||||
					user: user,
 | 
					
 | 
				
			||||||
					allUsers: allUsers
 | 
					      if (!user.isAdmin) {
 | 
				
			||||||
				}
 | 
					        return {
 | 
				
			||||||
			};
 | 
					          status: 302,
 | 
				
			||||||
		} catch (e) {
 | 
					          redirect: '/photos'
 | 
				
			||||||
			return {
 | 
					        };
 | 
				
			||||||
				status: 302,
 | 
					      }
 | 
				
			||||||
				redirect: '/auth/login'
 | 
					
 | 
				
			||||||
			};
 | 
					      return {
 | 
				
			||||||
		}
 | 
					        status: 200,
 | 
				
			||||||
	};
 | 
					        props: {
 | 
				
			||||||
 | 
					          user: user,
 | 
				
			||||||
 | 
					          allUsers: allUsers
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        status: 302,
 | 
				
			||||||
 | 
					        redirect: '/auth/login'
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	import { onMount } from 'svelte';
 | 
					  import { onMount } from 'svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	import type { ImmichUser } from '$lib/models/immich-user';
 | 
					  import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
 | 
				
			||||||
	import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
 | 
					  import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
 | 
				
			||||||
	import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
 | 
					  import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
 | 
				
			||||||
	import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
 | 
					  import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
 | 
				
			||||||
	import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
 | 
					  import UserManagement from '$lib/components/admin-page/user-management.svelte';
 | 
				
			||||||
	import UserManagement from '$lib/components/admin-page/user-management.svelte';
 | 
					  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
 | 
				
			||||||
	import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
 | 
					  import CreateUserForm from '$lib/components/forms/create-user-form.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 StatusBox from '$lib/components/shared-components/status-box.svelte';
 | 
				
			||||||
	import { browser } from '$app/env';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let user: ImmichUser;
 | 
					  let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT;
 | 
				
			||||||
	export let allUsers: UserResponseDto[];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let shouldShowCreateUserForm: boolean;
 | 
					  export let user: UserResponseDto;
 | 
				
			||||||
 | 
					  export let allUsers: UserResponseDto[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const onButtonClicked = (buttonType: CustomEvent) => {
 | 
					  let editUser: UserResponseDto;
 | 
				
			||||||
		selectedAction = buttonType.detail['actionType'] as AdminSideBarSelection;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	onMount(() => {
 | 
					  let shouldShowEditUserForm = false;
 | 
				
			||||||
		selectedAction = AdminSideBarSelection.USER_MANAGEMENT;
 | 
					  let shouldShowCreateUserForm = false;
 | 
				
			||||||
	});
 | 
					  let shouldShowInfoPanel = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const onUserCreated = async () => {
 | 
					  const onButtonClicked = (buttonType: CustomEvent) => {
 | 
				
			||||||
		const { data } = await api.userApi.getAllUsers(false);
 | 
					    selectedAction = buttonType.detail['actionType'] as AdminSideBarSelection;
 | 
				
			||||||
		allUsers = data;
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		shouldShowCreateUserForm = false;
 | 
					  onMount(() => {
 | 
				
			||||||
	};
 | 
					    selectedAction = AdminSideBarSelection.USER_MANAGEMENT;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onUserCreated = async () => {
 | 
				
			||||||
 | 
					    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>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:head>
 | 
					<svelte:head>
 | 
				
			||||||
	<title>Administration - Immich</title>
 | 
					    <title>Administration - Immich</title>
 | 
				
			||||||
</svelte:head>
 | 
					</svelte:head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<NavigationBar {user} />
 | 
					<NavigationBar {user}/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if shouldShowCreateUserForm}
 | 
					{#if shouldShowCreateUserForm}
 | 
				
			||||||
	<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
 | 
					    <FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
 | 
				
			||||||
		<div>
 | 
					        <CreateUserForm on:user-created={onUserCreated}/>
 | 
				
			||||||
			<CreateUserForm on:user-created={onUserCreated} />
 | 
					    </FullScreenModal>
 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	</FullScreenModal>
 | 
					 | 
				
			||||||
{/if}
 | 
					{/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 class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen">
 | 
				
			||||||
	<section id="admin-sidebar" class="pt-8 pr-6 flex flex-col">
 | 
					    <section id="admin-sidebar" class="pt-8 pr-6 flex flex-col">
 | 
				
			||||||
		<SideBarButton
 | 
					        <SideBarButton
 | 
				
			||||||
			title="User"
 | 
					                title="User"
 | 
				
			||||||
			logo={AccountMultipleOutline}
 | 
					                logo={AccountMultipleOutline}
 | 
				
			||||||
			actionType={AdminSideBarSelection.USER_MANAGEMENT}
 | 
					                actionType={AdminSideBarSelection.USER_MANAGEMENT}
 | 
				
			||||||
			isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
 | 
					                isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
 | 
				
			||||||
			on:selected={onButtonClicked}
 | 
					                on:selected={onButtonClicked}
 | 
				
			||||||
		/>
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="mb-6 mt-auto">
 | 
					        <div class="mb-6 mt-auto">
 | 
				
			||||||
			<StatusBox />
 | 
					            <StatusBox/>
 | 
				
			||||||
		</div>
 | 
					        </div>
 | 
				
			||||||
	</section>
 | 
					    </section>
 | 
				
			||||||
	<section class="overflow-y-auto relative">
 | 
					    <section class="overflow-y-auto relative">
 | 
				
			||||||
		<div id="setting-title" class="pt-10 fixed w-full z-50 bg-immich-bg">
 | 
					        <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>
 | 
					            <h1 class="text-lg ml-8 mb-4 text-immich-primary font-medium">{selectedAction}</h1>
 | 
				
			||||||
			<hr />
 | 
					            <hr/>
 | 
				
			||||||
		</div>
 | 
					        </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:create-user={() => (shouldShowCreateUserForm = true)}
 | 
				
			||||||
 | 
					                            on:edit-user={editUserHandler}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                {/if}
 | 
				
			||||||
 | 
					            </section>
 | 
				
			||||||
 | 
					        </section>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<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)} />
 | 
					 | 
				
			||||||
				{/if}
 | 
					 | 
				
			||||||
			</section>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
	</section>
 | 
					 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,56 +1,58 @@
 | 
				
			|||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
	export const prerender = false;
 | 
					  export const prerender = false;
 | 
				
			||||||
	import type { Load } from '@sveltejs/kit';
 | 
					  import type { Load } from '@sveltejs/kit';
 | 
				
			||||||
	import { api } from '@api';
 | 
					  import { api } from '@api';
 | 
				
			||||||
 | 
					  import { browser } from '$app/env';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export const load: Load = async () => {
 | 
					  export const load: Load = async () => {
 | 
				
			||||||
		if (browser) {
 | 
					    if (browser) {
 | 
				
			||||||
			try {
 | 
					      try {
 | 
				
			||||||
				const { data: user } = await api.userApi.getMyUserInfo();
 | 
					        const {data: user} = await api.userApi.getMyUserInfo();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return {
 | 
					        return {
 | 
				
			||||||
					status: 302,
 | 
					          status: 302,
 | 
				
			||||||
					redirect: '/photos'
 | 
					          redirect: '/photos'
 | 
				
			||||||
				};
 | 
					        };
 | 
				
			||||||
			} catch (e) {}
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const { data } = await api.userApi.getUserCount();
 | 
					      const {data} = await api.userApi.getUserCount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return {
 | 
					      return {
 | 
				
			||||||
				status: 200,
 | 
					        status: 200,
 | 
				
			||||||
				props: {
 | 
					        props: {
 | 
				
			||||||
					isAdminUserExist: data.userCount == 0 ? false : true
 | 
					          isAdminUserExist: data.userCount != 0
 | 
				
			||||||
				}
 | 
					        }
 | 
				
			||||||
			};
 | 
					      };
 | 
				
			||||||
		}
 | 
					    }
 | 
				
			||||||
	};
 | 
					  };
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	import { goto } from '$app/navigation';
 | 
					  import { goto } from '$app/navigation';
 | 
				
			||||||
	import { browser } from '$app/env';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let isAdminUserExist: boolean;
 | 
					  export let isAdminUserExist: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async function onGettingStartedClicked() {
 | 
					  async function onGettingStartedClicked() {
 | 
				
			||||||
		isAdminUserExist ? goto('/auth/login') : goto('/auth/register');
 | 
					    isAdminUserExist ? await goto('/auth/login') : await goto('/auth/register');
 | 
				
			||||||
	}
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:head>
 | 
					<svelte:head>
 | 
				
			||||||
	<title>Welcome 🎉 - Immich</title>
 | 
					    <title>Welcome 🎉 - Immich</title>
 | 
				
			||||||
	<meta name="description" content="Immich Web Interface" />
 | 
					    <meta name="description" content="Immich Web Interface"/>
 | 
				
			||||||
</svelte:head>
 | 
					</svelte:head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<section class="h-screen w-screen flex place-items-center place-content-center">
 | 
					<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 flex-col place-items-center gap-8 text-center max-w-[350px]">
 | 
				
			||||||
		<div class="flex place-items-center place-content-center ">
 | 
					        <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>
 | 
					        </div>
 | 
				
			||||||
		<h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1>
 | 
					        <h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1>
 | 
				
			||||||
		<button
 | 
					        <button
 | 
				
			||||||
			class="border px-4 py-2 rounded-md bg-immich-primary hover:bg-immich-primary/75 text-white font-bold w-[200px]"
 | 
					                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>
 | 
					        >
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,119 +1,120 @@
 | 
				
			|||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
	export const prerender = false;
 | 
					  export const prerender = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	import type { Load } from '@sveltejs/kit';
 | 
					  import type { Load } from '@sveltejs/kit';
 | 
				
			||||||
	import { AlbumResponseDto, api, UserResponseDto } from '@api';
 | 
					  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) {
 | 
					    if (!browser && !session.user) {
 | 
				
			||||||
			return {
 | 
					      return {
 | 
				
			||||||
				status: 302,
 | 
					        status: 302,
 | 
				
			||||||
				redirect: '/auth/login'
 | 
					        redirect: '/auth/login'
 | 
				
			||||||
			};
 | 
					      };
 | 
				
			||||||
		}
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		try {
 | 
					    try {
 | 
				
			||||||
			const [user, sharedAlbums] = await Promise.all([
 | 
					      const [user, sharedAlbums] = await Promise.all([
 | 
				
			||||||
				fetch('/data/user/get-my-user-info').then((r) => r.json()),
 | 
					        fetch('/data/user/get-my-user-info').then((r) => r.json()),
 | 
				
			||||||
				fetch('/data/album/get-all-albums?isShared=true').then((r) => r.json())
 | 
					        fetch('/data/album/get-all-albums?isShared=true').then((r) => r.json())
 | 
				
			||||||
			]);
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return {
 | 
					      return {
 | 
				
			||||||
				status: 200,
 | 
					        status: 200,
 | 
				
			||||||
				props: {
 | 
					        props: {
 | 
				
			||||||
					user: user,
 | 
					          user: user,
 | 
				
			||||||
					sharedAlbums: sharedAlbums
 | 
					          sharedAlbums: sharedAlbums
 | 
				
			||||||
				}
 | 
					        }
 | 
				
			||||||
			};
 | 
					      };
 | 
				
			||||||
		} catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
			return {
 | 
					      return {
 | 
				
			||||||
				status: 302,
 | 
					        status: 302,
 | 
				
			||||||
				redirect: '/auth/login'
 | 
					        redirect: '/auth/login'
 | 
				
			||||||
			};
 | 
					      };
 | 
				
			||||||
		}
 | 
					    }
 | 
				
			||||||
	};
 | 
					  };
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
 | 
					  import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
 | 
				
			||||||
	import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
 | 
					  import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
 | 
				
			||||||
	import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
 | 
					  import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
 | 
				
			||||||
	import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
 | 
					  import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
 | 
				
			||||||
	import { goto } from '$app/navigation';
 | 
					  import { goto } from '$app/navigation';
 | 
				
			||||||
	import { browser } from '$app/env';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let user: UserResponseDto;
 | 
					  export let user: UserResponseDto;
 | 
				
			||||||
	export let sharedAlbums: AlbumResponseDto[];
 | 
					  export let sharedAlbums: AlbumResponseDto[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const createSharedAlbum = async () => {
 | 
					  const createSharedAlbum = async () => {
 | 
				
			||||||
		try {
 | 
					    try {
 | 
				
			||||||
			const { data: newAlbum } = await api.albumApi.createAlbum({
 | 
					      const {data: newAlbum} = await api.albumApi.createAlbum({
 | 
				
			||||||
				albumName: 'Untitled'
 | 
					        albumName: 'Untitled'
 | 
				
			||||||
			});
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			goto('/albums/' + newAlbum.id);
 | 
					      goto('/albums/' + newAlbum.id);
 | 
				
			||||||
		} catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
			console.log('Error [createAlbum] ', e);
 | 
					      console.log('Error [createAlbum] ', e);
 | 
				
			||||||
		}
 | 
					    }
 | 
				
			||||||
	};
 | 
					  };
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:head>
 | 
					<svelte:head>
 | 
				
			||||||
	<title>Albums - Immich</title>
 | 
					    <title>Albums - Immich</title>
 | 
				
			||||||
</svelte:head>
 | 
					</svelte:head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<section>
 | 
					<section>
 | 
				
			||||||
	<NavigationBar {user} on:uploadClicked={() => {}} />
 | 
					    <NavigationBar {user} on:uploadClicked={() => {}}/>
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
 | 
					<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
 | 
				
			||||||
	<SideBar />
 | 
					    <SideBar/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<section class="overflow-y-auto relative">
 | 
					    <section class="overflow-y-auto relative">
 | 
				
			||||||
		<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
 | 
					        <section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
 | 
				
			||||||
			<!-- Main Section -->
 | 
					            <!-- Main Section -->
 | 
				
			||||||
			<div class="px-4 flex justify-between place-items-center">
 | 
					            <div class="px-4 flex justify-between place-items-center">
 | 
				
			||||||
				<div>
 | 
					                <div>
 | 
				
			||||||
					<p class="font-medium">Sharing</p>
 | 
					                    <p class="font-medium">Sharing</p>
 | 
				
			||||||
				</div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<div>
 | 
					                <div>
 | 
				
			||||||
					<button
 | 
					                    <button
 | 
				
			||||||
						on:click={createSharedAlbum}
 | 
					                            on:click={createSharedAlbum}
 | 
				
			||||||
						class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
 | 
					                            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>
 | 
											<span>
 | 
				
			||||||
							<PlusBoxOutline size="18" />
 | 
												<PlusBoxOutline size="18"/>
 | 
				
			||||||
						</span>
 | 
											</span>
 | 
				
			||||||
						<p>Create shared album</p>
 | 
					                        <p>Create shared album</p>
 | 
				
			||||||
					</button>
 | 
					                    </button>
 | 
				
			||||||
				</div>
 | 
					                </div>
 | 
				
			||||||
			</div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div class="my-4">
 | 
					            <div class="my-4">
 | 
				
			||||||
				<hr />
 | 
					                <hr/>
 | 
				
			||||||
			</div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<!-- Share Album List -->
 | 
					            <!-- Share Album List -->
 | 
				
			||||||
			<div class="w-full flex flex-col place-items-center">
 | 
					            <div class="w-full flex flex-col place-items-center">
 | 
				
			||||||
				{#each sharedAlbums as album}
 | 
					                {#each sharedAlbums as album}
 | 
				
			||||||
					<a sveltekit:prefetch href={`albums/${album.id}`}>
 | 
					                    <a sveltekit:prefetch href={`albums/${album.id}`}>
 | 
				
			||||||
						<SharedAlbumListTile {album} {user} /></a
 | 
					                        <SharedAlbumListTile {album} {user}/>
 | 
				
			||||||
					>
 | 
					                    </a
 | 
				
			||||||
				{/each}
 | 
					                    >
 | 
				
			||||||
			</div>
 | 
					                {/each}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<!-- Empty List -->
 | 
					            <!-- Empty List -->
 | 
				
			||||||
			{#if sharedAlbums.length === 0}
 | 
					            {#if sharedAlbums.length === 0}
 | 
				
			||||||
				<div
 | 
					                <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"
 | 
					                        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">
 | 
					                    <p class="text-center text-immich-text-gray-500">
 | 
				
			||||||
						Create a shared album to share photos and videos with people in your network
 | 
					                        Create a shared album to share photos and videos with people in your network
 | 
				
			||||||
					</p>
 | 
					                    </p>
 | 
				
			||||||
				</div>
 | 
					                </div>
 | 
				
			||||||
			{/if}
 | 
					            {/if}
 | 
				
			||||||
		</section>
 | 
					        </section>
 | 
				
			||||||
	</section>
 | 
					    </section>
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user