Files
IdP/apps/frontend/components/id/pin-lock-modal.tsx
lendry f00f3d411d fix
2026-07-10 12:21:44 +03:00

118 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import * as React from 'react';
import { createPortal } from 'react-dom';
import { DoorOpen, KeyRound } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { PinInput } from '@/components/ui/pin-input';
import { isPinInputComplete } from '@/lib/pin-input';
interface PinLockModalProps {
open: boolean;
isSubmitting: boolean;
error?: string | null;
onSubmit: (pin: string) => Promise<void>;
onLogout?: () => void;
}
export function PinLockModal({ open, isSubmitting, error, onSubmit, onLogout }: PinLockModalProps) {
const [pin, setPin] = React.useState('');
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
React.useEffect(() => {
if (open) {
setPin('');
}
}, [open]);
React.useEffect(() => {
if (!open || typeof document === 'undefined') return;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = previousOverflow;
};
}, [open]);
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
await onSubmit(pin);
}
if (!open || !mounted) return null;
return createPortal(
<div className="fixed inset-0 z-[6000]">
<div className="absolute inset-0 bg-black/50 backdrop-blur-[1px]" aria-hidden />
<div
role="dialog"
aria-modal="true"
aria-labelledby="pin-lock-title"
className="fixed left-1/2 w-[min(92vw,520px)] -translate-x-1/2 rounded-[28px] bg-white p-6 shadow-2xl"
style={{ top: 'max(1.5rem, 10vh)' }}
>
{onLogout ? (
<button
type="button"
onClick={onLogout}
disabled={isSubmitting}
title="Выйти из аккаунта"
aria-label="Выйти из аккаунта"
className="absolute left-5 top-5 flex h-9 w-9 items-center justify-center rounded-full text-[#6b6f7b] transition hover:bg-[#f4f5f8] hover:text-[#111] disabled:opacity-50"
>
<DoorOpen className="h-4 w-4" />
</button>
) : null}
<div className="mb-5 flex flex-col gap-1">
<div className="mx-auto mb-2 flex h-14 w-14 items-center justify-center rounded-full bg-[#f4f5f8]">
<KeyRound className="h-7 w-7 text-[#111]" />
</div>
<h2 id="pin-lock-title" className="text-center text-xl font-semibold">
Введите PIN-код
</h2>
<p className="text-center text-sm text-[#6b6f7b]">
Сессия заблокирована из-за бездействия. Подтвердите PIN, чтобы продолжить работу.
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<PinInput
autoFocus
className="h-[58px] text-center text-lg tracking-[0.4em]"
placeholder="PIN"
value={pin}
onChange={setPin}
required
minLength={4}
maxLength={6}
/>
{error ? <p className="text-center text-sm text-red-600">{error}</p> : null}
<Button type="submit" size="lg" className="w-full rounded-[18px]" disabled={isSubmitting || !isPinInputComplete(pin)}>
{isSubmitting ? 'Проверяем...' : 'Разблокировать'}
</Button>
{onLogout ? (
<Button
type="button"
variant="ghost"
size="lg"
className="w-full rounded-[18px] text-[#6b6f7b]"
disabled={isSubmitting}
onClick={onLogout}
>
<DoorOpen className="mr-2 h-4 w-4" />
Выйти из аккаунта
</Button>
) : null}
</form>
</div>
</div>,
document.body
);
}