0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-28 00:59:18 -05: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:
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: 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

View file

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

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">{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>

View file

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

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

View file

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

View file

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