mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
feat(web): improve /auth pages (#1969)
* feat(web): improve /auth pages * invalidate load functions after login * handle login server errors more graceful * add loading state to oauth button
This commit is contained in:
parent
04955a4123
commit
87d84b922f
@ -1,14 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { AppRoute } from '$lib/constants';
|
||||||
import { api } from '@api';
|
import { api } from '@api';
|
||||||
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
|
||||||
|
|
||||||
let error: string;
|
let error: string;
|
||||||
let success: string;
|
|
||||||
|
|
||||||
let password = '';
|
let password = '';
|
||||||
let confirmPassowrd = '';
|
let confirmPassowrd = '';
|
||||||
|
|
||||||
let canRegister = false;
|
let canRegister = false;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
@ -21,13 +18,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function registerAdmin(event: SubmitEvent) {
|
async function registerAdmin(event: SubmitEvent & { currentTarget: HTMLFormElement }) {
|
||||||
if (canRegister) {
|
if (canRegister) {
|
||||||
error = '';
|
error = '';
|
||||||
|
|
||||||
const formElement = event.target as HTMLFormElement;
|
const form = new FormData(event.currentTarget);
|
||||||
|
|
||||||
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');
|
||||||
@ -42,7 +37,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (status === 201) {
|
if (status === 201) {
|
||||||
goto('/auth/login');
|
goto(AppRoute.AUTH_LOGIN);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
error = 'Error create admin account';
|
error = 'Error create admin account';
|
||||||
@ -52,81 +47,74 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<form on:submit|preventDefault={registerAdmin} method="post" class="flex flex-col gap-5 mt-5">
|
||||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
<div class="flex flex-col gap-2">
|
||||||
>
|
<label class="immich-form-label" for="email">Admin Email</label>
|
||||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
<input
|
||||||
<ImmichLogo class="text-center" height="100" width="100" />
|
class="immich-form-input"
|
||||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
id="email"
|
||||||
Admin Registration
|
name="email"
|
||||||
</h1>
|
type="email"
|
||||||
<p
|
autocomplete="email"
|
||||||
class="text-sm border rounded-md p-4 font-mono text-gray-600 dark:border-immich-dark-bg dark:text-gray-300"
|
required
|
||||||
>
|
/>
|
||||||
Since you are the first user on the system, you will be assigned as the Admin and are
|
|
||||||
responsible for administrative tasks, and additional users will be created by you.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form on:submit|preventDefault={registerAdmin} method="post" action="" autocomplete="off">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="m-4 flex flex-col gap-2">
|
<label class="immich-form-label" for="password">Admin Password</label>
|
||||||
<label class="immich-form-label" for="email">Admin Email</label>
|
<input
|
||||||
<input class="immich-form-input" id="email" name="email" type="email" required />
|
class="immich-form-input"
|
||||||
</div>
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
required
|
||||||
|
bind:value={password}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="m-4 flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="immich-form-label" for="password">Admin Password</label>
|
<label class="immich-form-label" for="confirmPassword">Confirm Admin Password</label>
|
||||||
<input
|
<input
|
||||||
class="immich-form-input"
|
class="immich-form-input"
|
||||||
id="password"
|
id="confirmPassword"
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
required
|
autocomplete="new-password"
|
||||||
bind:value={password}
|
required
|
||||||
/>
|
bind:value={confirmPassowrd}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="m-4 flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="immich-form-label" for="confirmPassword">Confirm Admin Password</label>
|
<label class="immich-form-label" for="firstName">First Name</label>
|
||||||
<input
|
<input
|
||||||
class="immich-form-input"
|
class="immich-form-input"
|
||||||
id="confirmPassword"
|
id="firstName"
|
||||||
name="password"
|
name="firstName"
|
||||||
type="password"
|
type="text"
|
||||||
required
|
autocomplete="given-name"
|
||||||
bind:value={confirmPassowrd}
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-4 flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="immich-form-label" for="firstName">First Name</label>
|
<label class="immich-form-label" for="lastName">Last Name</label>
|
||||||
<input class="immich-form-input" id="firstName" name="firstName" type="text" required />
|
<input
|
||||||
</div>
|
class="immich-form-input"
|
||||||
|
id="lastName"
|
||||||
|
name="lastName"
|
||||||
|
type="text"
|
||||||
|
autocomplete="family-name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="m-4 flex flex-col gap-2">
|
{#if error}
|
||||||
<label class="immich-form-label" for="lastName">Last Name</label>
|
<p class="text-red-400">{error}</p>
|
||||||
<input class="immich-form-input" id="lastName" name="lastName" type="text" required />
|
{/if}
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if error}
|
<div class="my-5 flex w-full">
|
||||||
<p class="text-red-400 ml-4">{error}</p>
|
<button type="submit" class="immich-btn-primary-big">Sign Up</button>
|
||||||
{/if}
|
</div>
|
||||||
|
</form>
|
||||||
{#if success}
|
|
||||||
<div>
|
|
||||||
<p>Admin account has been registered</p>
|
|
||||||
<p>
|
|
||||||
<a href="/auth/login">Login</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="flex w-full">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="m-4 p-2 bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-4 text-white rounded-md shadow-md w-full"
|
|
||||||
>Sign Up</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, UserResponseDto } from '@api';
|
import { api, UserResponseDto } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
|
||||||
|
|
||||||
export let user: UserResponseDto;
|
export let user: UserResponseDto;
|
||||||
let error: string;
|
let error: string;
|
||||||
@ -44,61 +43,41 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<form on:submit|preventDefault={changePassword} method="post" class="flex flex-col gap-5 mt-5">
|
||||||
class="border bg-gray-50 dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
<div class="flex flex-col gap-2">
|
||||||
>
|
<label class="immich-form-label" for="password">New Password</label>
|
||||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
<input
|
||||||
<ImmichLogo class="text-center" height="100" width="100" />
|
class="immich-form-input"
|
||||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
id="password"
|
||||||
Change Password
|
name="password"
|
||||||
</h1>
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
<p
|
required
|
||||||
class="text-sm border rounded-3xl p-6 text-gray-600 dark:border-immich-dark-bg dark:text-gray-300 bg-immich-bg dark:bg-gray-900"
|
bind:value={password}
|
||||||
>
|
/>
|
||||||
Hi {user.firstName}
|
|
||||||
{user.lastName} ({user.email}),
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
This is either the first time you are signing into the system or a request has been made to change
|
|
||||||
your password. Please enter the new password below.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form on:submit|preventDefault={changePassword} method="post" autocomplete="off">
|
<div class="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="password">New Password</label>
|
<input
|
||||||
<input
|
class="immich-form-input"
|
||||||
class="immich-form-input"
|
id="confirmPassword"
|
||||||
id="password"
|
name="password"
|
||||||
name="password"
|
type="password"
|
||||||
type="password"
|
autocomplete="current-password"
|
||||||
required
|
required
|
||||||
bind:value={password}
|
bind:value={confirmPassowrd}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-4 flex flex-col gap-2">
|
{#if error}
|
||||||
<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
|
<p class="text-red-400 text-sm">{error}</p>
|
||||||
<input
|
{/if}
|
||||||
class="immich-form-input"
|
|
||||||
id="confirmPassword"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
bind:value={confirmPassowrd}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if error}
|
{#if success}
|
||||||
<p class="text-red-400 ml-4 text-sm">{error}</p>
|
<p class="text-immich-primary text-sm">{success}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="my-5 flex w-full">
|
||||||
{#if success}
|
<button type="submit" class="immich-btn-primary-big">Change Password</button>
|
||||||
<p class="text-immich-primary ml-4 text-sm">{success}</p>
|
</div>
|
||||||
{/if}
|
</form>
|
||||||
<div class="flex w-full">
|
|
||||||
<button type="submit" class="immich-btn-primary-big m-4">Change Password</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
import { loginPageMessage } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { api, oauth, OAuthConfigResponseDto } from '@api';
|
import { api, oauth, OAuthConfigResponseDto } from '@api';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
|
||||||
|
|
||||||
let error: string;
|
let error: string;
|
||||||
let email = '';
|
let email = '';
|
||||||
let password = '';
|
let password = '';
|
||||||
let oauthError: string;
|
let oauthError: string;
|
||||||
let authConfig: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: false };
|
export let authConfig: OAuthConfigResponseDto;
|
||||||
let loading = true;
|
let loading = false;
|
||||||
|
let oauthLoading = true;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (oauth.isCallback(window.location)) {
|
if (oauth.isCallback(window.location)) {
|
||||||
try {
|
try {
|
||||||
loading = true;
|
|
||||||
await oauth.login(window.location);
|
await oauth.login(window.location);
|
||||||
dispatch('success');
|
dispatch('success');
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error [login-form] [oauth.callback]', e);
|
console.error('Error [login-form] [oauth.callback]', e);
|
||||||
oauthError = 'Unable to complete OAuth login';
|
oauthError = 'Unable to complete OAuth login';
|
||||||
loading = false;
|
} finally {
|
||||||
|
oauthLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +38,7 @@
|
|||||||
const { enabled, url, autoLaunch } = authConfig;
|
const { enabled, url, autoLaunch } = authConfig;
|
||||||
|
|
||||||
if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) {
|
if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) {
|
||||||
await goto('/auth/login?autoLaunch=0', { replaceState: true });
|
await goto(`${AppRoute.AUTH_LOGIN}?autoLaunch=0`, { replaceState: true });
|
||||||
await goto(url);
|
await goto(url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@
|
|||||||
handleError(error, 'Unable to connect!');
|
handleError(error, 'Unable to connect!');
|
||||||
}
|
}
|
||||||
|
|
||||||
loading = false;
|
oauthLoading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
@ -75,100 +75,89 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
{#if authConfig.passwordLoginEnabled}
|
||||||
class="border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-8 shadow-sm w-full max-w-lg rounded-3xl"
|
<form on:submit|preventDefault={login} class="flex flex-col gap-5 mt-5">
|
||||||
>
|
{#if error}
|
||||||
<div class="flex flex-col place-items-center place-content-center gap-4 py-4">
|
<p class="text-red-400" transition:fade>
|
||||||
<ImmichLogo class="text-center h-24 w-24" />
|
{error}
|
||||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">Login</h1>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if loginPageMessage}
|
|
||||||
<p
|
|
||||||
class="text-sm border rounded-xl p-4 text-immich-primary dark:text-immich-dark-primary font-medium bg-immich-primary/5 dark:border-immich-dark-bg w-full border-immich-primary border-2"
|
|
||||||
>
|
|
||||||
{@html loginPageMessage}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if authConfig.passwordLoginEnabled}
|
|
||||||
<form on:submit|preventDefault={login} class="flex flex-col gap-5 mt-5">
|
|
||||||
{#if error}
|
|
||||||
<p class="text-red-400" transition:fade>
|
|
||||||
{error}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label class="immich-form-label" for="email">Email</label>
|
|
||||||
<input
|
|
||||||
class="immich-form-input"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
bind:value={email}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label class="immich-form-label" for="password">Password</label>
|
|
||||||
<input
|
|
||||||
class="immich-form-input"
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
bind:value={password}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-5 flex w-full">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="immich-btn-primary-big inline-flex items-center h-14"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{#if loading}
|
|
||||||
<LoadingSpinner />
|
|
||||||
{:else}
|
|
||||||
Login
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if authConfig.enabled}
|
|
||||||
{#if authConfig.passwordLoginEnabled}
|
|
||||||
<div class="inline-flex items-center justify-center w-full">
|
|
||||||
<hr class="w-3/4 h-px my-4 bg-gray-200 border-0 dark:bg-gray-600" />
|
|
||||||
<span
|
|
||||||
class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 left-1/2 dark:text-white bg-white dark:bg-immich-dark-gray"
|
|
||||||
>
|
|
||||||
or
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="my-5 flex flex-col gap-5">
|
|
||||||
{#if oauthError}
|
<div class="flex flex-col gap-2">
|
||||||
<p class="text-red-400" transition:fade>{oauthError}</p>
|
<label class="immich-form-label" for="email">Email</label>
|
||||||
{/if}
|
<input
|
||||||
<a href={authConfig.url} class="flex w-full">
|
class="immich-form-input"
|
||||||
<button
|
id="email"
|
||||||
type="button"
|
name="email"
|
||||||
disabled={loading}
|
type="email"
|
||||||
class={authConfig.passwordLoginEnabled
|
autocomplete="email"
|
||||||
? 'immich-btn-secondary-big'
|
bind:value={email}
|
||||||
: 'immich-btn-primary-big'}
|
required
|
||||||
>
|
/>
|
||||||
{authConfig.buttonText || 'Login with OAuth'}
|
</div>
|
||||||
</button>
|
|
||||||
</a>
|
<div class="flex flex-col gap-2">
|
||||||
|
<label class="immich-form-label" for="password">Password</label>
|
||||||
|
<input
|
||||||
|
class="immich-form-input"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
bind:value={password}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-5 flex w-full">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="immich-btn-primary-big inline-flex items-center h-14"
|
||||||
|
disabled={loading || oauthLoading}
|
||||||
|
>
|
||||||
|
{#if loading}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{:else}
|
||||||
|
Login
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if authConfig.enabled}
|
||||||
|
{#if authConfig.passwordLoginEnabled}
|
||||||
|
<div class="inline-flex items-center justify-center w-full">
|
||||||
|
<hr class="w-3/4 h-px my-4 bg-gray-200 border-0 dark:bg-gray-600" />
|
||||||
|
<span
|
||||||
|
class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 left-1/2 dark:text-white bg-white dark:bg-immich-dark-gray"
|
||||||
|
>
|
||||||
|
or
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="my-5 flex flex-col gap-5">
|
||||||
|
{#if oauthError}
|
||||||
|
<p class="text-red-400" transition:fade>{oauthError}</p>
|
||||||
|
{/if}
|
||||||
|
<a href={authConfig.url} class="flex w-full">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={loading || oauthLoading}
|
||||||
|
class={'inline-flex items-center h-14 ' + authConfig.passwordLoginEnabled
|
||||||
|
? 'immich-btn-secondary-big'
|
||||||
|
: 'immich-btn-primary-big'}
|
||||||
|
>
|
||||||
|
{#if oauthLoading}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{:else}
|
||||||
|
{authConfig.buttonText || 'Login with OAuth'}
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if !authConfig.enabled && !authConfig.passwordLoginEnabled}
|
{#if !authConfig.enabled && !authConfig.passwordLoginEnabled}
|
||||||
<p class="text-center dark:text-immich-dark-fg p-4">Login has been disabled.</p>
|
<p class="text-center dark:text-immich-dark-fg p-4">Login has been disabled.</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import ImmichLogo from './immich-logo.svelte';
|
||||||
|
|
||||||
|
export let title: string;
|
||||||
|
export let showMessage = $$slots.message;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="min-h-screen w-screen flex place-items-center place-content-center p-4">
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-4 border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-8 shadow-sm w-full max-w-lg rounded-3xl"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col place-items-center place-content-center gap-4 py-4">
|
||||||
|
<ImmichLogo class="h-24 w-24" />
|
||||||
|
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if showMessage}
|
||||||
|
<div
|
||||||
|
class="text-sm border rounded-xl p-4 text-immich-primary dark:text-immich-dark-primary font-medium bg-immich-primary/5 dark:border-immich-dark-bg w-full border-immich-primary border-2"
|
||||||
|
>
|
||||||
|
<slot name="message" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -14,5 +14,8 @@ export enum AppRoute {
|
|||||||
SHARING = '/sharing',
|
SHARING = '/sharing',
|
||||||
SEARCH = '/search',
|
SEARCH = '/search',
|
||||||
|
|
||||||
AUTH_LOGIN = '/auth/login'
|
AUTH_LOGIN = '/auth/login',
|
||||||
|
AUTH_LOGOUT = '/auth/logout',
|
||||||
|
AUTH_REGISTER = '/auth/register',
|
||||||
|
AUTH_CHANGE_PASSWORD = '/auth/change-password'
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
export const prerender = false;
|
import { AppRoute } from '$lib/constants';
|
||||||
|
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async ({ locals: { api } }) => {
|
export const load = (async ({ locals: { user } }) => {
|
||||||
try {
|
if (!user) {
|
||||||
const { data: userInfo } = await api.userApi.getMyUserInfo();
|
throw redirect(302, AppRoute.AUTH_LOGIN);
|
||||||
|
} else if (!user.shouldChangePassword) {
|
||||||
if (userInfo.shouldChangePassword) {
|
throw redirect(302, AppRoute.PHOTOS);
|
||||||
return {
|
|
||||||
user: userInfo,
|
|
||||||
meta: {
|
|
||||||
title: 'Change Password'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw redirect(302, '/photos');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw redirect(302, '/auth/login');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
meta: {
|
||||||
|
title: 'Change Password'
|
||||||
|
}
|
||||||
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
|
|
||||||
import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
|
import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
|
||||||
|
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
|
||||||
|
import { AppRoute } from '$lib/constants';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
const onSuccessHandler = async () => {
|
const onSuccessHandler = async () => {
|
||||||
await fetch('auth/logout', { method: 'POST' });
|
await fetch(AppRoute.AUTH_LOGOUT, { method: 'POST' });
|
||||||
|
|
||||||
goto('/auth/login');
|
goto(AppRoute.AUTH_LOGIN);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="h-screen w-screen flex place-items-center place-content-center">
|
<FullscreenContainer title={data.meta.title}>
|
||||||
<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
|
<p slot="message">
|
||||||
<ChangePasswordForm user={data.user} on:success={onSuccessHandler} />
|
Hi {data.user.firstName}
|
||||||
</div>
|
{data.user.lastName} ({data.user.email}),
|
||||||
</section>
|
<br />
|
||||||
|
<br />
|
||||||
|
This is either the first time you are signing into the system or a request has been made to change
|
||||||
|
your password. Please enter the new password below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ChangePasswordForm user={data.user} on:success={onSuccessHandler} />
|
||||||
|
</FullscreenContainer>
|
||||||
|
@ -1,14 +1,32 @@
|
|||||||
|
import { AppRoute } from '$lib/constants';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import type { OAuthConfigResponseDto } from '@api';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async ({ locals: { api } }) => {
|
export const load = (async ({ locals: { api } }) => {
|
||||||
const { data } = await api.userApi.getUserCount(true);
|
const { data } = await api.userApi.getUserCount(true);
|
||||||
if (data.userCount === 0) {
|
if (data.userCount === 0) {
|
||||||
// Admin not registered
|
// Admin not registered
|
||||||
throw redirect(302, '/auth/register');
|
throw redirect(302, AppRoute.AUTH_REGISTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
let authConfig: OAuthConfigResponseDto = {
|
||||||
|
passwordLoginEnabled: true,
|
||||||
|
enabled: false
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: Figure out how to get correct redirect URI server-side.
|
||||||
|
const { data } = await api.oauthApi.generateConfig({ redirectUri: '/' });
|
||||||
|
data.url = undefined;
|
||||||
|
|
||||||
|
authConfig = data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[ERROR] login/+page.server.ts:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
authConfig,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Login'
|
title: 'Login'
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
|
|
||||||
import LoginForm from '$lib/components/forms/login-form.svelte';
|
import LoginForm from '$lib/components/forms/login-form.svelte';
|
||||||
|
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
|
||||||
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import { loginPageMessage } from '$lib/constants';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section
|
<FullscreenContainer title={data.meta.title} showMessage={!!loginPageMessage}>
|
||||||
class="min-h-screen w-screen flex place-items-center place-content-center p-4"
|
<p slot="message">
|
||||||
transition:fade={{ duration: 100 }}
|
{@html loginPageMessage}
|
||||||
>
|
</p>
|
||||||
|
|
||||||
<LoginForm
|
<LoginForm
|
||||||
on:success={() => goto('/photos')}
|
authConfig={data.authConfig}
|
||||||
on:first-login={() => goto('/auth/change-password')}
|
on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })}
|
||||||
|
on:first-login={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)}
|
||||||
/>
|
/>
|
||||||
</section>
|
</FullscreenContainer>
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
|
import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
|
||||||
|
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="h-screen w-screen flex place-items-center place-content-center">
|
<FullscreenContainer title={data.meta.title}>
|
||||||
|
<p slot="message">
|
||||||
|
Since you are the first user on the system, you will be assigned as the Admin and are
|
||||||
|
responsible for administrative tasks, and additional users will be created by you.
|
||||||
|
</p>
|
||||||
|
|
||||||
<AdminRegistrationForm />
|
<AdminRegistrationForm />
|
||||||
</section>
|
</FullscreenContainer>
|
||||||
|
Loading…
Reference in New Issue
Block a user