import React, { useCallback, useState } from 'react'; import { useI18n } from '../../../application/i18n/I18nProvider'; import type { AppLockSystemUnlockStatus } from '../../../application/state/useAppLockState'; import { APP_LOCK_TIMEOUT_OPTIONS_MINUTES, type AppLockSettings, type AppLockSettingsChangeError, type AppLockTimeoutMinutes, } from '../../../domain/appLock'; import { Button } from '../../ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../../ui/dialog'; import { Input } from '../../ui/input'; import { Label } from '../../ui/label'; import { Select, SettingCard, SettingHint, SettingRow, SectionHeader, Toggle } from '../settings-ui'; type AppLockDialogMode = 'setup' | 'change' | 'disable' | null; interface AppLockSettingsSectionProps { appLockSettings: AppLockSettings; setAppLockTimeoutMinutes: (timeoutMinutes: AppLockTimeoutMinutes) => void; requestAppLockDisable: ( currentPassword: string, ) => Promise; requestAppLockPasswordChange: (input: { currentPassword?: string; nextPassword: string; }) => Promise; appLockSystemUnlockStatus?: AppLockSystemUnlockStatus; setAppLockSystemUnlockEnabled?: (input: { enabled: boolean; currentPassword?: string; autoPromptEnabled?: boolean; }) => Promise< AppLockSettings | { ok: false; error: 'empty-current' | 'incorrect' | 'locked' | 'unsupported' | 'unavailable' | 'cancelled' | 'failed' } >; } export const AppLockSettingsSection: React.FC = ({ appLockSettings, setAppLockTimeoutMinutes, requestAppLockDisable, requestAppLockPasswordChange, appLockSystemUnlockStatus, setAppLockSystemUnlockEnabled, }) => { const { t } = useI18n(); const [dialogMode, setDialogMode] = useState(null); const [currentPassword, setCurrentPassword] = useState(''); const [disablePassword, setDisablePassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(null); const [isDisabling, setIsDisabling] = useState(false); const [isSavingPassword, setIsSavingPassword] = useState(false); const [isSavingSystemUnlock, setIsSavingSystemUnlock] = useState(false); const hasPassword = Boolean(appLockSettings.passwordVerifier); const isDialogBusy = isDisabling || isSavingPassword; const showSystemUnlock = Boolean( hasPassword && appLockSystemUnlockStatus?.supported && appLockSystemUnlockStatus.label && (appLockSystemUnlockStatus.available || appLockSettings.systemUnlockEnabled), ); const timeoutOptions = APP_LOCK_TIMEOUT_OPTIONS_MINUTES.map((minutes) => ({ value: String(minutes), label: t(`settings.appLock.timeout.${minutes}`), })); const mapChangeError = useCallback(( changeError: AppLockSettingsChangeError | 'locked' | 'unsupported' | 'unavailable' | 'failed', ): string => { switch (changeError) { case 'empty-current': return t('settings.appLock.validation.currentRequired'); case 'empty-next': return t('settings.appLock.validation.newRequired'); case 'incorrect': return t('settings.appLock.validation.incorrect'); case 'locked': return t('settings.appLock.systemUnlock.locked'); case 'unsupported': case 'unavailable': case 'failed': return t('settings.appLock.systemUnlock.unavailable'); } }, [t]); const resetDialog = useCallback(() => { setDialogMode(null); setCurrentPassword(''); setDisablePassword(''); setNewPassword(''); setConfirmPassword(''); setError(null); }, []); const openDialog = useCallback((mode: Exclude) => { setError(null); setDialogMode(mode); }, []); const handleTimeoutChange = useCallback((value: string) => { const timeoutMinutes = Number(value) as AppLockTimeoutMinutes; if (!APP_LOCK_TIMEOUT_OPTIONS_MINUTES.includes(timeoutMinutes)) return; setAppLockTimeoutMinutes(timeoutMinutes); }, [setAppLockTimeoutMinutes]); const handleDisable = useCallback(async () => { setError(null); setIsDisabling(true); try { const result = await requestAppLockDisable(disablePassword); if ('ok' in result && result.ok === false) { setError(mapChangeError(result.error)); return; } resetDialog(); } finally { setIsDisabling(false); } }, [disablePassword, mapChangeError, requestAppLockDisable, resetDialog]); const handleSavePassword = useCallback(async () => { setError(null); if (newPassword.length === 0) { setError(t('settings.appLock.validation.newRequired')); return; } if (confirmPassword.length === 0) { setError(t('settings.appLock.validation.confirmRequired')); return; } if (newPassword !== confirmPassword) { setError(t('settings.appLock.validation.mismatch')); return; } setIsSavingPassword(true); try { const result = await requestAppLockPasswordChange({ currentPassword: hasPassword ? currentPassword : undefined, nextPassword: newPassword, }); if ('ok' in result && result.ok === false) { setError(mapChangeError(result.error)); return; } resetDialog(); } finally { setIsSavingPassword(false); } }, [ confirmPassword, currentPassword, hasPassword, mapChangeError, newPassword, requestAppLockPasswordChange, resetDialog, t, ]); const handleSystemUnlockChange = useCallback(async (enabled: boolean) => { if (!setAppLockSystemUnlockEnabled || !appLockSystemUnlockStatus?.label) return; setError(null); setIsSavingSystemUnlock(true); try { const result = await setAppLockSystemUnlockEnabled({ enabled, autoPromptEnabled: enabled && appLockSettings.systemUnlockEnabled ? appLockSettings.systemUnlockAutoPromptEnabled : false, }); if ('ok' in result && result.ok === false) { if (result.error === 'cancelled') return; setError(mapChangeError(result.error)); } } finally { setIsSavingSystemUnlock(false); } }, [ appLockSettings.systemUnlockAutoPromptEnabled, appLockSettings.systemUnlockEnabled, appLockSystemUnlockStatus?.label, mapChangeError, setAppLockSystemUnlockEnabled, ]); const handleAutoPromptChange = useCallback(async (autoPromptEnabled: boolean) => { if (!setAppLockSystemUnlockEnabled || !appLockSystemUnlockStatus?.label) return; if (!appLockSettings.systemUnlockEnabled) return; setError(null); setIsSavingSystemUnlock(true); try { const result = await setAppLockSystemUnlockEnabled({ enabled: true, autoPromptEnabled }); if ('ok' in result && result.ok === false) { if (result.error === 'cancelled') return; setError(mapChangeError(result.error)); } } finally { setIsSavingSystemUnlock(false); } }, [ appLockSettings.systemUnlockEnabled, appLockSystemUnlockStatus?.label, mapChangeError, setAppLockSystemUnlockEnabled, ]); const dialogTitle = dialogMode === 'disable' ? t('settings.appLock.disableTitle') : dialogMode === 'change' ? t('settings.appLock.changePasswordTitle') : t('settings.appLock.setupPasswordTitle'); const dialogDescription = dialogMode === 'disable' ? t('settings.appLock.disableDescription') : dialogMode === 'change' ? t('settings.appLock.changePasswordDescription') : t('settings.appLock.setupPasswordDescription'); return ( <> {!hasPassword ? ( ) : ( <> {t(appLockSettings.enabled ? 'common.enabled' : 'common.disabled')} { setDisablePassword(event.target.value); setError(null); }} /> )} {dialogMode === 'change' && (
{ setCurrentPassword(event.target.value); setError(null); }} />
)} {dialogMode !== 'disable' && ( <>
{ setNewPassword(event.target.value); setError(null); }} />
{ setConfirmPassword(event.target.value); setError(null); }} />
)} {error &&

{error}

} ); };