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:
Alex 2022-08-12 14:25:19 -05:00 committed by GitHub
parent 5b7236f6ad
commit 4b9187928c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 545 additions and 365 deletions

View file

@ -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

View file

@ -48,6 +48,7 @@ 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 ;

View file

@ -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>

View file

@ -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 = '';
@ -54,7 +53,7 @@
}
</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"/>
<h1 class="text-2xl text-immich-primary font-medium">Create new user</h1>
@ -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>

View 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>

View file

@ -1,6 +1,7 @@
<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}) => {
if (!browser && !session.user) {
@ -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;
@ -64,9 +76,27 @@
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>
<svelte:head>
@ -77,12 +107,43 @@
{#if shouldShowCreateUserForm}
<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
<div>
<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
@ -103,12 +164,18 @@
<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>

View file

@ -2,6 +2,7 @@
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) {
@ -12,14 +13,15 @@
status: 302,
redirect: '/photos'
};
} catch (e) {}
} catch (e) {
}
const {data} = await api.userApi.getUserCount();
return {
status: 200,
props: {
isAdminUserExist: data.userCount == 0 ? false : true
isAdminUserExist: data.userCount != 0
}
};
}
@ -28,12 +30,11 @@
<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>
@ -50,7 +51,8 @@
<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>

View file

@ -3,6 +3,7 @@
import type { Load } from '@sveltejs/kit';
import { AlbumResponseDto, api, UserResponseDto } from '@api';
import { browser } from '$app/env';
export const load: Load = async ({fetch, session}) => {
if (!browser && !session.user) {
@ -40,7 +41,6 @@
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[];
@ -98,7 +98,8 @@
<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>