1
0
mirror of https://github.com/immich-app/immich.git synced 2025-06-26 05:01:05 +02:00

refactor: migrate create user form to immich ui (#15350)

* refactor: migrate create user form to immich ui

* minor styling tweak

* remove unintentional commit

* revert formating diff

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen
2025-01-15 15:09:19 -05:00
committed by GitHub
parent 2d2966caa0
commit a60da1ccab
7 changed files with 121 additions and 148 deletions

View File

@ -5,10 +5,8 @@
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
import { handleError } from '$lib/utils/handle-error';
import { createUserAdmin } from '@immich/sdk';
import { Alert, Button, Field, HelperText, Input, PasswordInput, Stack, Switch } from '@immich/ui';
import { t } from 'svelte-i18n';
import Button from '../elements/buttons/button.svelte';
import Slider from '../elements/slider.svelte';
import PasswordField from '../shared-components/password-field.svelte';
interface Props {
onClose: () => void;
@ -17,137 +15,114 @@
oauthEnabled?: boolean;
}
let { onClose, onSubmit, onCancel, oauthEnabled = false }: Props = $props();
let { onClose, onSubmit: onDone, onCancel, oauthEnabled = false }: Props = $props();
let error = $state('');
let success = $state('');
let success = $state(false);
let email = $state('');
let password = $state('');
let confirmPassword = $state('');
let passwordConfirm = $state('');
let name = $state('');
let shouldChangePassword = $state(true);
let notify = $state(true);
let canCreateUser = $state(false);
let quotaSize: number | undefined = $state();
let quotaSize: string | undefined = $state();
let isCreatingUser = $state(false);
let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(quotaSize, ByteUnit.GiB) : null);
let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(Number(quotaSize), ByteUnit.GiB) : null);
let quotaSizeWarning = $derived(
quotaSizeInBytes && userInteraction.serverInfo && quotaSizeInBytes > userInteraction.serverInfo.diskSizeRaw,
);
$effect(() => {
if (password !== confirmPassword && confirmPassword.length > 0) {
error = $t('password_does_not_match');
canCreateUser = false;
} else {
error = '';
canCreateUser = true;
}
});
const passwordMismatch = $derived(password !== passwordConfirm && passwordConfirm.length > 0);
const passwordMismatchMessage = $derived(passwordMismatch ? $t('password_does_not_match') : '');
const valid = $derived(!passwordMismatch && !isCreatingUser);
async function registerUser() {
if (canCreateUser && !isCreatingUser) {
isCreatingUser = true;
error = '';
try {
await createUserAdmin({
userAdminCreateDto: {
email,
password,
shouldChangePassword,
name,
quotaSizeInBytes,
notify,
},
});
success = $t('new_user_created');
onSubmit();
return;
} catch (error) {
handleError(error, $t('errors.unable_to_create_user'));
} finally {
isCreatingUser = false;
}
}
}
const onsubmit = async (event: Event) => {
const onSubmit = async (event: Event) => {
event.preventDefault();
await registerUser();
if (!valid) {
return;
}
isCreatingUser = true;
error = '';
try {
await createUserAdmin({
userAdminCreateDto: {
email,
password,
shouldChangePassword,
name,
quotaSizeInBytes,
notify,
},
});
success = true;
onDone();
return;
} catch (error) {
handleError(error, $t('errors.unable_to_create_user'));
} finally {
isCreatingUser = false;
}
};
</script>
<FullScreenModal title={$t('create_new_user')} showLogo {onClose}>
<form {onsubmit} autocomplete="off" id="create-new-user-form">
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">{$t('email')}</label>
<input class="immich-form-input" id="email" bind:value={email} type="email" required />
</div>
{#if $featureFlags.email}
<div class="my-4 flex place-items-center justify-between gap-2">
<label class="text-sm dark:text-immich-dark-fg" for="send-welcome-email">
{$t('admin.send_welcome_email')}
</label>
<Slider id="send-welcome-email" bind:checked={notify} />
</div>
{/if}
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="password">{$t('password')}</label>
<PasswordField id="password" bind:password autocomplete="new-password" required={!oauthEnabled} />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="confirmPassword">{$t('confirm_password')}</label>
<PasswordField
id="confirmPassword"
bind:password={confirmPassword}
autocomplete="new-password"
required={!oauthEnabled}
/>
</div>
<div class="my-4 flex place-items-center justify-between gap-2">
<label class="text-sm dark:text-immich-dark-fg" for="require-password-change">
{$t('admin.require_password_change_on_login')}
</label>
<Slider id="require-password-change" bind:checked={shouldChangePassword} />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">{$t('name')}</label>
<input class="immich-form-input" id="name" bind:value={name} type="text" required />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize">
{$t('admin.quota_size_gib')}
{#if quotaSizeWarning}
<p class="text-red-400 text-sm">{$t('errors.quota_higher_than_disk_size')}</p>
{/if}
</label>
<input class="immich-form-input" id="quotaSize" type="number" min="0" bind:value={quotaSize} />
</div>
<form onsubmit={onSubmit} autocomplete="off" id="create-new-user-form">
<FullScreenModal title={$t('create_new_user')} showLogo {onClose}>
{#if error}
<p class="text-sm text-red-400">{error}</p>
<Alert color="danger" size="small" title={error} closable />
{/if}
{#if success}
<p class="text-sm text-immich-primary">{success}</p>
<p class="text-sm text-immich-primary">{$t('new_user_created')}</p>
{/if}
</form>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={onCancel}>{$t('cancel')}</Button>
<Button type="submit" disabled={isCreatingUser} fullwidth form="create-new-user-form">{$t('create')}</Button>
{/snippet}
</FullScreenModal>
<Stack gap={4}>
<Field label={$t('email')} required>
<Input bind:value={email} type="email" />
</Field>
{#if $featureFlags.email}
<Field label={$t('admin.send_welcome_email')}>
<Switch id="send-welcome-email" bind:checked={notify} class="flex justify-between text-sm" />
</Field>
{/if}
<Field label={$t('password')} required={!oauthEnabled}>
<PasswordInput id="password" bind:value={password} autocomplete="new-password" />
</Field>
<Field label={$t('confirm_password')} required={!oauthEnabled}>
<PasswordInput id="confirmPassword" bind:value={passwordConfirm} autocomplete="new-password" />
<HelperText color="danger">{passwordMismatchMessage}</HelperText>
</Field>
<Field label={$t('admin.require_password_change_on_login')}>
<Switch id="require-password-change" bind:checked={shouldChangePassword} class="flex justify-between text-sm" />
</Field>
<Field label={$t('name')} required>
<Input bind:value={name} />
</Field>
<Field label={$t('admin.quota_size_gib')}>
<Input bind:value={quotaSize} type="number" min="0" />
{#if quotaSizeWarning}
<HelperText color="danger">{$t('errors.quota_higher_than_disk_size')}</HelperText>
{/if}
</Field>
</Stack>
{#snippet stickyBottom()}
<Button color="secondary" fullWidth onclick={onCancel} shape="round">{$t('cancel')}</Button>
<Button type="submit" disabled={!valid} fullWidth shape="round">{$t('create')}</Button>
{/snippet}
</FullScreenModal>
</form>

View File

@ -11,7 +11,7 @@
</script>
<section class="min-w-screen flex min-h-screen items-center justify-center">
<Card color="secondary" class="w-full max-w-xl border m-2">
<Card color="secondary" class="w-full max-w-lg border m-2">
<CardHeader class="mt-6">
<VStack>
<Logo variant="icon" size="giant" />

View File

@ -17,7 +17,7 @@
</script>
<script lang="ts">
import Button from '$lib/components/elements/buttons/button.svelte';
import { Button } from '@immich/ui';
import { AssetTypeEnum, type SmartSearchDto, type MetadataSearchDto } from '@immich/sdk';
import SearchPeopleSection from './search-people-section.svelte';
import SearchLocationSection from './search-location-section.svelte';
@ -163,7 +163,7 @@
</form>
{#snippet stickyBottom()}
<Button type="reset" color="gray" fullwidth form={formId}>{$t('clear_all')}</Button>
<Button type="submit" fullwidth form={formId}>{$t('search')}</Button>
<Button shape="round" size="large" type="reset" color="secondary" fullWidth form={formId}>{$t('clear_all')}</Button>
<Button shape="round" size="large" type="submit" fullWidth form={formId}>{$t('search')}</Button>
{/snippet}
</FullScreenModal>