fix and update

This commit is contained in:
lendry
2026-07-02 00:10:22 +03:00
parent bcdfbc3861
commit 4306d0ce37
11 changed files with 375 additions and 41 deletions

View File

@@ -23,8 +23,11 @@ import { useToast } from '@/components/id/toast-provider';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { PinInput } from '@/components/ui/pin-input';
import { isPinInputComplete } from '@/lib/pin-input';
import { OtpInput } from '@/components/ui/otp-input';
import { ActiveDevice, ActiveSession, apiFetch, AUTH_SESSION_KEY, getApiErrorMessage, SignInEvent, TotpSetupResponse, TotpStatusResponse } from '@/lib/api';
import { markPinIdleWatch } from '@/lib/pin-idle-storage';
import { formatUserAgentLabel } from '@/lib/device-client';
import { cn } from '@/lib/utils';
import { QRCodeSVG } from 'qrcode.react';
@@ -51,8 +54,8 @@ const deviceIcons: Record<string, LucideIcon> = {
type DialogMode = 'pin' | 'password' | 'backupEmail' | 'backupPhone' | 'totpSetup' | 'totpDisable' | null;
const dialogConfig: Record<Exclude<DialogMode, null | 'totpSetup' | 'totpDisable'>, { title: string; placeholder: string; type: string }> = {
pin: { title: 'Установить PIN-код', placeholder: 'PIN из 4-6 цифр', type: 'password' },
const dialogConfig: Record<Exclude<DialogMode, null | 'totpSetup' | 'totpDisable'>, { title: string; placeholder: string; type?: string }> = {
pin: { title: 'Установить PIN-код', placeholder: 'PIN из 4-6 цифр' },
password: { title: 'Установить пароль', placeholder: 'Новый пароль (мин. 8 символов)', type: 'password' },
backupEmail: { title: 'Резервная почта', placeholder: 'backup@example.com', type: 'email' },
backupPhone: { title: 'Резервный телефон', placeholder: '+79991234567', type: 'tel' }
@@ -336,6 +339,7 @@ export default function SecurityPage() {
try {
if (dialogMode === 'pin') {
await apiFetch(`/security/users/${user.id}/pin/setup`, { method: 'POST', body: JSON.stringify({ pin: dialogValue.trim() }) }, token);
markPinIdleWatch(user.id);
showToast('PIN-код установлен');
} else if (dialogMode === 'password') {
await apiFetch(`/profile/users/${user.id}/password`, { method: 'POST', body: JSON.stringify({ password: dialogValue }) }, token);
@@ -694,15 +698,29 @@ export default function SecurityPage() {
<>
<div className="flex items-center gap-3 rounded-2xl bg-[#f4f5f8] px-4">
<ShieldCheck className="h-5 w-5 text-[#667085]" />
<Input
className="border-0 bg-transparent focus:ring-0"
type={dialogConfig[dialogMode].type}
placeholder={dialogConfig[dialogMode].placeholder}
value={dialogValue}
onChange={(event) => setDialogValue(event.target.value)}
/>
{dialogMode === 'pin' ? (
<PinInput
className="border-0 bg-transparent focus:ring-0"
placeholder={dialogConfig.pin.placeholder}
value={dialogValue}
onChange={setDialogValue}
maxLength={6}
/>
) : (
<Input
className="border-0 bg-transparent focus:ring-0"
type={dialogMode === 'password' ? 'password' : 'text'}
placeholder={dialogConfig[dialogMode].placeholder}
value={dialogValue}
onChange={(event) => setDialogValue(event.target.value)}
/>
)}
</div>
<Button className="mt-5 w-full" onClick={submitDialog} disabled={isDialogSaving || !dialogValue.trim()}>
<Button
className="mt-5 w-full"
onClick={submitDialog}
disabled={isDialogSaving || (dialogMode === 'pin' ? !isPinInputComplete(dialogValue) : !dialogValue.trim())}
>
{isDialogSaving ? 'Сохраняем...' : 'Сохранить'}
</Button>
</>