mirror of
https://github.com/immich-app/immich.git
synced 2025-08-11 09:16:31 -04:00
feat(web): reset pin code (#20766)
This commit is contained in:
parent
1d4d8e7a9a
commit
cfbc24579d
@ -912,6 +912,7 @@
|
||||
"failed_to_load_notifications": "Failed to load notifications",
|
||||
"failed_to_load_people": "Failed to load people",
|
||||
"failed_to_remove_product_key": "Failed to remove product key",
|
||||
"failed_to_reset_pin_code": "Failed to reset PIN code",
|
||||
"failed_to_stack_assets": "Failed to stack assets",
|
||||
"failed_to_unstack_assets": "Failed to un-stack assets",
|
||||
"failed_to_update_notification_status": "Failed to update notification status",
|
||||
@ -1056,6 +1057,7 @@
|
||||
"folder_not_found": "Folder not found",
|
||||
"folders": "Folders",
|
||||
"folders_feature_description": "Browsing the folder view for the photos and videos on the file system",
|
||||
"forgot_pin_code_question": "Forgot your PIN?",
|
||||
"forward": "Forward",
|
||||
"gcast_enabled": "Google Cast",
|
||||
"gcast_enabled_description": "This feature loads external resources from Google in order to work.",
|
||||
@ -1599,6 +1601,9 @@
|
||||
"reset_password": "Reset password",
|
||||
"reset_people_visibility": "Reset people visibility",
|
||||
"reset_pin_code": "Reset PIN code",
|
||||
"reset_pin_code_description": "If you forgot your PIN code, you can contact the server administrator to reset it",
|
||||
"reset_pin_code_success": "Successfully reset PIN code",
|
||||
"reset_pin_code_with_password": "You can always reset your PIN code with your password",
|
||||
"reset_sqlite": "Reset SQLite Database",
|
||||
"reset_sqlite_confirmation": "Are you sure you want to reset the SQLite database? You will need to log out and log in again to resync the data",
|
||||
"reset_sqlite_success": "Successfully reset the SQLite database",
|
||||
|
@ -6,7 +6,7 @@
|
||||
import PinCodeInput from '$lib/components/user-settings-page/PinCodeInput.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { changePinCode } from '@immich/sdk';
|
||||
import { Button } from '@immich/ui';
|
||||
import { Button, Heading, Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
@ -16,11 +16,11 @@
|
||||
let isLoading = $state(false);
|
||||
let canSubmit = $derived(currentPinCode.length === 6 && confirmPinCode.length === 6 && newPinCode === confirmPinCode);
|
||||
|
||||
interface Props {
|
||||
onChanged?: () => void;
|
||||
}
|
||||
type Props = {
|
||||
onForgot: () => void;
|
||||
};
|
||||
|
||||
let { onChanged }: Props = $props();
|
||||
let { onForgot }: Props = $props();
|
||||
|
||||
const handleSubmit = async (event: Event) => {
|
||||
event.preventDefault();
|
||||
@ -38,8 +38,6 @@
|
||||
message: $t('pin_code_changed_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
|
||||
onChanged?.();
|
||||
} catch (error) {
|
||||
handleError(error, $t('unable_to_change_pin_code'));
|
||||
} finally {
|
||||
@ -58,12 +56,13 @@
|
||||
<div in:fade={{ duration: 200 }}>
|
||||
<form autocomplete="off" onsubmit={handleSubmit} class="mt-6">
|
||||
<div class="flex flex-col gap-6 place-items-center place-content-center">
|
||||
<p class="text-dark">{$t('change_pin_code')}</p>
|
||||
<Heading>{$t('change_pin_code')}</Heading>
|
||||
<PinCodeInput label={$t('current_pin_code')} bind:value={currentPinCode} tabindexStart={1} pinLength={6} />
|
||||
|
||||
<PinCodeInput label={$t('new_pin_code')} bind:value={newPinCode} tabindexStart={7} pinLength={6} />
|
||||
|
||||
<PinCodeInput label={$t('confirm_new_pin_code')} bind:value={confirmPinCode} tabindexStart={13} pinLength={6} />
|
||||
<button type="button" onclick={onForgot}>
|
||||
<Text color="muted" class="underline" size="small">{$t('forgot_pin_code_question')}</Text>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
|
@ -6,7 +6,7 @@
|
||||
import PinCodeInput from '$lib/components/user-settings-page/PinCodeInput.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { setupPinCode } from '@immich/sdk';
|
||||
import { Button } from '@immich/ui';
|
||||
import { Button, Heading } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
@ -54,10 +54,9 @@
|
||||
<form autocomplete="off" onsubmit={handleSubmit}>
|
||||
<div class="flex flex-col gap-6 place-items-center place-content-center">
|
||||
{#if showLabel}
|
||||
<p class="text-dark">{$t('setup_pin_code')}</p>
|
||||
<Heading>{$t('setup_pin_code')}</Heading>
|
||||
{/if}
|
||||
<PinCodeInput label={$t('new_pin_code')} bind:value={newPinCode} tabindexStart={1} pinLength={6} />
|
||||
|
||||
<PinCodeInput label={$t('confirm_new_pin_code')} bind:value={confirmPinCode} tabindexStart={7} pinLength={6} />
|
||||
</div>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Label } from '@immich/ui';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
@ -115,7 +116,7 @@
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
{#if label}
|
||||
<label class="text-xs text-dark" for={pinCodeInputElements[0]?.id}>{label.toUpperCase()}</label>
|
||||
<Label for={pinCodeInputElements[0]?.id}>{label}</Label>
|
||||
{/if}
|
||||
<div class="flex gap-2">
|
||||
{#each { length: pinLength } as _, index (index)}
|
||||
|
@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import PinCodeChangeForm from '$lib/components/user-settings-page/PinCodeChangeForm.svelte';
|
||||
import PinCodeCreateForm from '$lib/components/user-settings-page/PinCodeCreateForm.svelte';
|
||||
import PinCodeResetModal from '$lib/modals/PinCodeResetModal.svelte';
|
||||
import { getAuthStatus } from '@immich/sdk';
|
||||
import { modalManager } from '@immich/ui';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
@ -11,15 +13,22 @@
|
||||
const { pinCode } = await getAuthStatus();
|
||||
hasPinCode = pinCode;
|
||||
});
|
||||
|
||||
const handleResetPINCode = async () => {
|
||||
const success = await modalManager.show(PinCodeResetModal, {});
|
||||
if (success) {
|
||||
hasPinCode = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="my-4">
|
||||
<section>
|
||||
{#if hasPinCode}
|
||||
<div in:fade={{ duration: 200 }} class="mt-6">
|
||||
<PinCodeChangeForm />
|
||||
<div in:fade={{ duration: 200 }}>
|
||||
<PinCodeChangeForm onForgot={handleResetPINCode} />
|
||||
</div>
|
||||
{:else}
|
||||
<div in:fade={{ duration: 200 }} class="mt-6">
|
||||
<div in:fade={{ duration: 200 }}>
|
||||
<PinCodeCreateForm onCreated={() => (hasPinCode = true)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
81
web/src/lib/modals/PinCodeResetModal.svelte
Normal file
81
web/src/lib/modals/PinCodeResetModal.svelte
Normal file
@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType,
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { resetPinCode } from '@immich/sdk';
|
||||
import {
|
||||
Button,
|
||||
Field,
|
||||
HelperText,
|
||||
HStack,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@immich/ui';
|
||||
import { mdiLockReset } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
onClose: (success?: true) => void;
|
||||
};
|
||||
|
||||
let { onClose }: Props = $props();
|
||||
|
||||
let passwordLoginEnabled = $derived($featureFlags.passwordLogin);
|
||||
let password = $state('');
|
||||
|
||||
const handleReset = async () => {
|
||||
try {
|
||||
await resetPinCode({ pinCodeResetDto: { password } });
|
||||
notificationController.show({ message: $t('pin_code_reset_successfully'), type: NotificationType.Info });
|
||||
onClose(true);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.failed_to_reset_pin_code'));
|
||||
}
|
||||
};
|
||||
|
||||
const onsubmit = async (event: Event) => {
|
||||
event.preventDefault();
|
||||
await handleReset();
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal title={$t('reset_pin_code')} icon={mdiLockReset} size="small" {onClose}>
|
||||
<ModalBody>
|
||||
<form {onsubmit} autocomplete="off" id="reset-pin-form">
|
||||
<Stack gap={4}>
|
||||
<div>{$t('reset_pin_code_description')}</div>
|
||||
{#if passwordLoginEnabled}
|
||||
<hr class="my-2 h-px w-full border-0 bg-gray-200 dark:bg-gray-600" />
|
||||
<section>
|
||||
<Field label={$t('confirm_password')} required>
|
||||
<PasswordInput bind:value={password} autocomplete="current-password" />
|
||||
<HelperText>
|
||||
<Text color="muted">{$t('reset_pin_code_with_password')}</Text>
|
||||
</HelperText>
|
||||
</Field>
|
||||
</section>
|
||||
{/if}
|
||||
</Stack>
|
||||
</form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{#if passwordLoginEnabled}
|
||||
<HStack fullWidth>
|
||||
<Button fullWidth shape="round" color="secondary" onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
<Button type="submit" form="reset-pin-form" fullWidth shape="round" color="danger" disabled={!password}>
|
||||
{$t('reset')}
|
||||
</Button>
|
||||
</HStack>
|
||||
{:else}
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('close')}</Button>
|
||||
{/if}
|
||||
</ModalFooter>
|
||||
</Modal>
|
Loading…
x
Reference in New Issue
Block a user