mirror of
https://github.com/immich-app/immich.git
synced 2025-05-30 19:54:52 -04: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:
parent
2d2966caa0
commit
a60da1ccab
@ -5,10 +5,8 @@
|
|||||||
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
|
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { createUserAdmin } from '@immich/sdk';
|
import { createUserAdmin } from '@immich/sdk';
|
||||||
|
import { Alert, Button, Field, HelperText, Input, PasswordInput, Stack, Switch } from '@immich/ui';
|
||||||
import { t } from 'svelte-i18n';
|
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 {
|
interface Props {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -17,137 +15,114 @@
|
|||||||
oauthEnabled?: boolean;
|
oauthEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { onClose, onSubmit, onCancel, oauthEnabled = false }: Props = $props();
|
let { onClose, onSubmit: onDone, onCancel, oauthEnabled = false }: Props = $props();
|
||||||
|
|
||||||
let error = $state('');
|
let error = $state('');
|
||||||
let success = $state('');
|
let success = $state(false);
|
||||||
|
|
||||||
let email = $state('');
|
let email = $state('');
|
||||||
let password = $state('');
|
let password = $state('');
|
||||||
let confirmPassword = $state('');
|
let passwordConfirm = $state('');
|
||||||
let name = $state('');
|
let name = $state('');
|
||||||
let shouldChangePassword = $state(true);
|
let shouldChangePassword = $state(true);
|
||||||
let notify = $state(true);
|
let notify = $state(true);
|
||||||
|
|
||||||
let canCreateUser = $state(false);
|
let quotaSize: string | undefined = $state();
|
||||||
let quotaSize: number | undefined = $state();
|
|
||||||
let isCreatingUser = $state(false);
|
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(
|
let quotaSizeWarning = $derived(
|
||||||
quotaSizeInBytes && userInteraction.serverInfo && quotaSizeInBytes > userInteraction.serverInfo.diskSizeRaw,
|
quotaSizeInBytes && userInteraction.serverInfo && quotaSizeInBytes > userInteraction.serverInfo.diskSizeRaw,
|
||||||
);
|
);
|
||||||
|
|
||||||
$effect(() => {
|
const passwordMismatch = $derived(password !== passwordConfirm && passwordConfirm.length > 0);
|
||||||
if (password !== confirmPassword && confirmPassword.length > 0) {
|
const passwordMismatchMessage = $derived(passwordMismatch ? $t('password_does_not_match') : '');
|
||||||
error = $t('password_does_not_match');
|
const valid = $derived(!passwordMismatch && !isCreatingUser);
|
||||||
canCreateUser = false;
|
|
||||||
} else {
|
|
||||||
error = '';
|
|
||||||
canCreateUser = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function registerUser() {
|
const onSubmit = async (event: Event) => {
|
||||||
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) => {
|
|
||||||
event.preventDefault();
|
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>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title={$t('create_new_user')} showLogo {onClose}>
|
<form onsubmit={onSubmit} autocomplete="off" id="create-new-user-form">
|
||||||
<form {onsubmit} autocomplete="off" id="create-new-user-form">
|
<FullScreenModal title={$t('create_new_user')} showLogo {onClose}>
|
||||||
<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>
|
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
<p class="text-sm text-red-400">{error}</p>
|
<Alert color="danger" size="small" title={error} closable />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if success}
|
{#if success}
|
||||||
<p class="text-sm text-immich-primary">{success}</p>
|
<p class="text-sm text-immich-primary">{$t('new_user_created')}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
|
||||||
|
|
||||||
{#snippet stickyBottom()}
|
<Stack gap={4}>
|
||||||
<Button color="gray" fullwidth onclick={onCancel}>{$t('cancel')}</Button>
|
<Field label={$t('email')} required>
|
||||||
<Button type="submit" disabled={isCreatingUser} fullwidth form="create-new-user-form">{$t('create')}</Button>
|
<Input bind:value={email} type="email" />
|
||||||
{/snippet}
|
</Field>
|
||||||
</FullScreenModal>
|
|
||||||
|
{#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>
|
</script>
|
||||||
|
|
||||||
<section class="min-w-screen flex min-h-screen items-center justify-center">
|
<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">
|
<CardHeader class="mt-6">
|
||||||
<VStack>
|
<VStack>
|
||||||
<Logo variant="icon" size="giant" />
|
<Logo variant="icon" size="giant" />
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { AssetTypeEnum, type SmartSearchDto, type MetadataSearchDto } from '@immich/sdk';
|
||||||
import SearchPeopleSection from './search-people-section.svelte';
|
import SearchPeopleSection from './search-people-section.svelte';
|
||||||
import SearchLocationSection from './search-location-section.svelte';
|
import SearchLocationSection from './search-location-section.svelte';
|
||||||
@ -163,7 +163,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#snippet stickyBottom()}
|
{#snippet stickyBottom()}
|
||||||
<Button type="reset" color="gray" fullwidth form={formId}>{$t('clear_all')}</Button>
|
<Button shape="round" size="large" type="reset" color="secondary" fullWidth form={formId}>{$t('clear_all')}</Button>
|
||||||
<Button type="submit" fullwidth form={formId}>{$t('search')}</Button>
|
<Button shape="round" size="large" type="submit" fullWidth form={formId}>{$t('search')}</Button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialogue.svelte';
|
import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialogue.svelte';
|
||||||
import RestoreDialogue from '$lib/components/admin-page/restore-dialogue.svelte';
|
import RestoreDialogue from '$lib/components/admin-page/restore-dialogue.svelte';
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
|
||||||
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.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 EditUserForm from '$lib/components/forms/edit-user-form.svelte';
|
||||||
@ -15,12 +12,13 @@
|
|||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { serverConfig, featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
import { copyToClipboard } from '$lib/utils';
|
import { copyToClipboard } from '$lib/utils';
|
||||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||||
import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
|
import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
|
||||||
|
import { Button, Code, IconButton, Text } from '@immich/ui';
|
||||||
import { mdiContentCopy, mdiDeleteRestore, mdiInfinity, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
|
import { mdiContentCopy, mdiDeleteRestore, mdiInfinity, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@ -161,22 +159,21 @@
|
|||||||
>
|
>
|
||||||
{#snippet promptSnippet()}
|
{#snippet promptSnippet()}
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<p>{$t('admin.user_password_has_been_reset')}</p>
|
<Text>{$t('admin.user_password_has_been_reset')}</Text>
|
||||||
|
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2 items-center">
|
||||||
<code
|
<Code color="primary">{newPassword}</Code>
|
||||||
class="rounded-md bg-gray-200 px-2 py-1 font-bold text-immich-primary dark:text-immich-dark-primary dark:bg-gray-700"
|
<IconButton
|
||||||
>
|
icon={mdiContentCopy}
|
||||||
{newPassword}
|
shape="round"
|
||||||
</code>
|
color="secondary"
|
||||||
<LinkButton onclick={() => copyToClipboard(newPassword)} title={$t('copy_password')}>
|
variant="ghost"
|
||||||
<div class="flex place-items-center gap-2 text-sm">
|
onclick={() => copyToClipboard(newPassword)}
|
||||||
<Icon path={mdiContentCopy} size="18" />
|
title={$t('copy_password')}
|
||||||
</div>
|
/>
|
||||||
</LinkButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>{$t('admin.user_password_reset_description')}</p>
|
<Text>{$t('admin.user_password_reset_description')}</Text>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
@ -222,31 +219,31 @@
|
|||||||
class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-4/12 lg:w-3/12 xl:w-2/12 text-ellipsis break-all text-sm"
|
class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-4/12 lg:w-3/12 xl:w-2/12 text-ellipsis break-all text-sm"
|
||||||
>
|
>
|
||||||
{#if !immichUser.deletedAt}
|
{#if !immichUser.deletedAt}
|
||||||
<CircleIconButton
|
<IconButton
|
||||||
|
shape="round"
|
||||||
|
size="large"
|
||||||
icon={mdiPencilOutline}
|
icon={mdiPencilOutline}
|
||||||
title={$t('edit_user')}
|
title={$t('edit_user')}
|
||||||
color="primary"
|
|
||||||
size="16"
|
|
||||||
onclick={() => editUserHandler(immichUser)}
|
onclick={() => editUserHandler(immichUser)}
|
||||||
/>
|
/>
|
||||||
{#if immichUser.id !== $user.id}
|
{#if immichUser.id !== $user.id}
|
||||||
<CircleIconButton
|
<IconButton
|
||||||
|
shape="round"
|
||||||
|
size="large"
|
||||||
icon={mdiTrashCanOutline}
|
icon={mdiTrashCanOutline}
|
||||||
title={$t('delete_user')}
|
title={$t('delete_user')}
|
||||||
color="primary"
|
|
||||||
size="16"
|
|
||||||
onclick={() => deleteUserHandler(immichUser)}
|
onclick={() => deleteUserHandler(immichUser)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if immichUser.deletedAt && immichUser.status === UserStatus.Deleted}
|
{#if immichUser.deletedAt && immichUser.status === UserStatus.Deleted}
|
||||||
<CircleIconButton
|
<IconButton
|
||||||
|
shape="round"
|
||||||
|
size="large"
|
||||||
icon={mdiDeleteRestore}
|
icon={mdiDeleteRestore}
|
||||||
title={$t('admin.user_restore_scheduled_removal', {
|
title={$t('admin.user_restore_scheduled_removal', {
|
||||||
values: { date: getDeleteDate(immichUser.deletedAt) },
|
values: { date: getDeleteDate(immichUser.deletedAt) },
|
||||||
})}
|
})}
|
||||||
color="primary"
|
|
||||||
size="16"
|
|
||||||
onclick={() => restoreUserHandler(immichUser)}
|
onclick={() => restoreUserHandler(immichUser)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
@ -256,8 +253,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<Button shape="round" onclick={() => (shouldShowCreateUserForm = true)}>{$t('create_user')}</Button>
|
||||||
<Button size="sm" onclick={() => (shouldShowCreateUserForm = true)}>{$t('create_user')}</Button>
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
|
@ -51,9 +51,7 @@
|
|||||||
|
|
||||||
<Field label={$t('confirm_password')} required>
|
<Field label={$t('confirm_password')} required>
|
||||||
<PasswordInput bind:value={passwordConfirm} autocomplete="new-password" />
|
<PasswordInput bind:value={passwordConfirm} autocomplete="new-password" />
|
||||||
{#if errorMessage}
|
<HelperText color="danger">{errorMessage}</HelperText>
|
||||||
<HelperText color="danger">{errorMessage}</HelperText>
|
|
||||||
{/if}
|
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<div class="my-5 flex w-full">
|
<div class="my-5 flex w-full">
|
||||||
|
@ -131,7 +131,7 @@
|
|||||||
<div class="inline-flex w-full items-center justify-center mt-4">
|
<div class="inline-flex w-full items-center justify-center mt-4">
|
||||||
<hr class="my-4 h-px w-3/4 border-0 bg-gray-200 dark:bg-gray-600" />
|
<hr class="my-4 h-px w-3/4 border-0 bg-gray-200 dark:bg-gray-600" />
|
||||||
<span
|
<span
|
||||||
class="absolute left-1/2 -translate-x-1/2 bg-white px-3 font-medium text-gray-900 dark:bg-immich-dark-gray dark:text-white"
|
class="absolute left-1/2 -translate-x-1/2 bg-gray-50 px-3 font-medium text-gray-900 dark:bg-neutral-900 dark:text-white"
|
||||||
>
|
>
|
||||||
{$t('or').toUpperCase()}
|
{$t('or').toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
|
@ -2,7 +2,11 @@ import plugin from 'tailwindcss/plugin';
|
|||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ['./src/**/*.{html,js,svelte,ts}', './node_modules/@immich/ui/dist/**/*.{svelte,js}'],
|
content: [
|
||||||
|
'./src/**/*.{html,js,svelte,ts}',
|
||||||
|
'./node_modules/@immich/ui/dist/**/*.{svelte,js}',
|
||||||
|
'../../ui/src/**/*.{html,js,svelte,ts}',
|
||||||
|
],
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user