You've already forked immich
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:
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user