|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import { useEffect, useRef, useState } from 'react'; |
| 3 | +import { useCallback, useEffect, useRef, useState } from 'react'; |
| 4 | +import { useTranslations } from 'next-intl'; |
4 | 5 | import { toast } from 'sonner'; |
5 | 6 |
|
6 | 7 | export type AntiCheatViolation = { |
7 | 8 | type: 'copy' | 'context-menu' | 'tab-switch' | 'paste'; |
8 | 9 | timestamp: Date; |
9 | 10 | }; |
10 | 11 |
|
| 12 | +const messageKey: Record<AntiCheatViolation['type'], string> = { |
| 13 | + copy: 'copy', |
| 14 | + paste: 'paste', |
| 15 | + 'context-menu': 'contextMenu', |
| 16 | + 'tab-switch': 'tabSwitch', |
| 17 | +}; |
| 18 | + |
11 | 19 | export function useAntiCheat(isActive: boolean = true) { |
| 20 | + const t = useTranslations('quiz.antiCheat'); |
12 | 21 | const [violations, setViolations] = useState<AntiCheatViolation[]>([]); |
13 | 22 | const [isTabActive, setIsTabActive] = useState(true); |
14 | 23 | const [showWarning, setShowWarning] = useState(false); |
15 | 24 | const warningTimeoutRef = useRef<NodeJS.Timeout | null>(null); |
16 | 25 |
|
17 | | - const addViolation = (type: AntiCheatViolation['type']) => { |
18 | | - if (!isActive) return; |
19 | | - |
20 | | - const violation: AntiCheatViolation = { |
21 | | - type, |
22 | | - timestamp: new Date(), |
23 | | - }; |
| 26 | + const addViolation = useCallback( |
| 27 | + (type: AntiCheatViolation['type']) => { |
| 28 | + if (!isActive) return; |
24 | 29 |
|
25 | | - setViolations(prev => [...prev, violation]); |
26 | | - setShowWarning(true); |
| 30 | + const violation: AntiCheatViolation = { |
| 31 | + type, |
| 32 | + timestamp: new Date(), |
| 33 | + }; |
27 | 34 |
|
28 | | - const messages = { |
29 | | - copy: '⚠️ Копіювання заборонено під час квізу', |
30 | | - paste: '⚠️ Вставка заборонена під час квізу', |
31 | | - 'context-menu': '⚠️ Контекстне меню заборонено під час квізу', |
32 | | - 'tab-switch': '⚠️ Перехід на іншу вкладку зафіксовано', |
33 | | - }; |
| 35 | + setViolations(prev => [...prev, violation]); |
| 36 | + setShowWarning(true); |
34 | 37 |
|
35 | | - toast.warning(messages[type], { |
36 | | - duration: 3000, |
37 | | - }); |
| 38 | + toast.warning(t(messageKey[type]), { |
| 39 | + duration: 3000, |
| 40 | + }); |
38 | 41 |
|
39 | | - if (warningTimeoutRef.current) { |
40 | | - clearTimeout(warningTimeoutRef.current); |
41 | | - } |
42 | | - warningTimeoutRef.current = setTimeout(() => { |
43 | | - setShowWarning(false); |
44 | | - }, 3000); |
45 | | - }; |
| 42 | + if (warningTimeoutRef.current) { |
| 43 | + clearTimeout(warningTimeoutRef.current); |
| 44 | + } |
| 45 | + warningTimeoutRef.current = setTimeout(() => { |
| 46 | + setShowWarning(false); |
| 47 | + }, 3000); |
| 48 | + }, |
| 49 | + [isActive, t] |
| 50 | + ); |
46 | 51 |
|
47 | 52 | useEffect(() => { |
48 | 53 | if (!isActive) return; |
@@ -86,7 +91,7 @@ export function useAntiCheat(isActive: boolean = true) { |
86 | 91 | clearTimeout(warningTimeoutRef.current); |
87 | 92 | } |
88 | 93 | }; |
89 | | - }, [isActive]); |
| 94 | + }, [isActive, addViolation]); |
90 | 95 |
|
91 | 96 | const resetViolations = () => { |
92 | 97 | setViolations([]); |
|
0 commit comments