PageSourceSearch

https://10comm.com/crm/js/screens/login.js?v=2026.09.29.0828

js 10comm.com collected 2026-10-02 11:21:03 UTC 27,148 bytes, 994 lines download raw bytes

1import { showMessageToUser } from '../ajax.js';
2
3const PASSWORD_EYE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696C3.423 7.51 7.36 4.5 12 4.5s8.577 3.01 9.938 7.152a1 1 0 0 1 0 .696C20.577 16.49 16.64 19.5 12 19.5s-8.577-3.01-9.938-7.152"/><path d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7"/></svg>`;
4const PASSWORD_EYE_OFF_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.733 5.076A10.744 10.744 0 0 1 12 5c4.636 0 8.573 3.01 9.938 7.152a1 1 0 0 1 0 .696 10.1 10.1 0 0 1-1.479 2.658"/><path d="M6.61 6.61A10.61 10.61 0 0 0 2.062 11.652a1 1 0 0 0 0 .696C3.423 16.49 7.36 19.5 12 19.5c1.735 0 3.37-.422 4.808-1.168"/><path d="M9.9 9.9a3.5 3.5 0 0 0 4.2 4.2"/><path d="M14.12 14.12 9.88 9.88"/><path d="M3 3l18 18"/></svg>`;
5
6let loginSubmitInFlight = false;
7let lastAutoSubmittedOtp = '';
8const AUTH_MODE_PASSWORD = 1;
9const AUTH_MODE_SMS_OTP = 2;
10
11function normalizeAuthMode(value) {
12	return Number(value) === AUTH_MODE_SMS_OTP || String(value ?? '').trim().toLowerCase() === 'sms_otp'
13		? AUTH_MODE_SMS_OTP
14		: AUTH_MODE_PASSWORD;
15}
16
17const loginState = {
18	resolvedEmail: '',
19	authMode: 0,
20	challengeId: 0,
21};
22
23const forgotPasswordState = {
24	active: false,
25	step: 'none',
26	email: '',
27	challengeId: 0,
28};
29
30function qs(sel, root = document) {
31	return root.querySelector(sel);
32}
33
34function showEl(el) {
35	if (!el) return;
36	el.classList.remove('hidden');
37}
38
39function hideEl(el) {
40	if (!el) return;
41	el.classList.add('hidden');
42}
43
44function showLoginMessage(message, type = 'error') {
45	const text = String(message || '').trim();
46	if (!text) {
47		return;
48	}
49
50	showMessageToUser(text, type);
51	window.scrollTo(0, 0);
52}
53
54function showOtpSentMessage(isResend = false) {
55	showLoginMessage(
56		isResend
57			? 'שלחנו קוד אימות חדש ב-SMS. ייתכן שייקח מספר שניות עד שהקוד יגיע.'
58			: 'שלחנו קוד אימות ב-SMS. ייתכן שייקח מספר שניות עד שהקוד יגיע.',
59		'success'
60	);
61}
62
63function showForgotPasswordOtpSentMessage() {
64	showLoginMessage('אם החשבון קיים במערכת, נשלח קוד אימות לנייד המשויך אליו. ייתכן שייקח מספר שניות עד שהקוד יגיע.', 'success');
65}
66
67function setBusy(isBusy) {
68	const loading = qs('#LoadingDiv');
69	const cover = qs('#cover');
70
71	if (loading) {
72		loading.classList.toggle('is-active', !!isBusy);
73	}
74	if (cover) {
75		cover.classList.toggle('is-active', !!isBusy);
76	}
77}
78
79function getUserNameValue() {
80	const el = qs('#Inp_LogIn_UserName');
81	return el ? String(el.value || '') : '';
82}
83
84function getPasswordValue() {
85	const el = qs('#Inp_LogIn_Password');
86	return el ? String(el.value || '') : '';
87}
88
89function getOtpValue() {
90	const el = qs('#Inp_LogIn_OtpCode');
91	return el ? String(el.value || '') : '';
92}
93
94function getRestorePhoneValue() {
95	const el = qs('#Inp_LogIn_restorePassword');
96	return el ? String(el.value || '') : '';
97}
98
99function getForgotUserNameValue() {
100	const el = qs('#Inp_ForgotPassword_UserName');
101	return el ? String(el.value || '') : '';
102}
103
104function getForgotOtpValue() {
105	const el = qs('#Inp_ForgotPassword_OtpCode');
106	return el ? String(el.value || '') : '';
107}
108
109function getForgotNewPasswordValue() {
110	const el = qs('#Inp_ForgotPassword_NewPassword');
111	return el ? String(el.value || '') : '';
112}
113
114function getForgotConfirmPasswordValue() {
115	const el = qs('#Inp_ForgotPassword_ConfirmPassword');
116	return el ? String(el.value || '') : '';
117}
118
119function setLoginPasswordVisibility(visible) {
120	const passwordInput = qs('#Inp_LogIn_Password');
121	const toggleBtn = qs('#loginPasswordToggle');
122	if (!passwordInput || !toggleBtn) {
123		return;
124	}
125
126	passwordInput.type = visible ? 'text' : 'password';
127	toggleBtn.innerHTML = visible ? PASSWORD_EYE_OFF_SVG : PASSWORD_EYE_SVG;
128	toggleBtn.setAttribute('aria-label', visible ? 'הסתר סיסמה' : 'הצג סיסמה');
129	toggleBtn.setAttribute('title', visible ? 'הסתר סיסמה' : 'הצג סיסמה');
130	toggleBtn.setAttribute('aria-pressed', visible ? 'true' : 'false');
131	toggleBtn.dataset.visible = visible ? '1' : '0';
132}
133
134function setTitle(text) {
135	const el = qs('#loginTitle');
136	if (el) {
137		el.textContent = String(text || '').trim();
138	}
139}
140
141function setSubtitle(text, isOtpSubtitle = false) {
142	const el = qs('#loginSubtitle');
143	if (el) {
144		el.textContent = String(text || '').trim();
145		el.classList.toggle('login-subtitle-otp', !!isOtpSubtitle);
146	}
147}
148
149function setConnectButtonText(text) {
150	const btn = qs('#connect');
151	if (btn) {
152		btn.textContent = text;
153	}
154}
155
156function renderOtpHelpText(el, maskedPhone) {
157	if (!el) {
158		return;
159	}
160
161	if (!maskedPhone) {
162		el.textContent = 'שלחנו קוד אימות ב-SMS';
163		return;
164	}
165
166	const match = String(maskedPhone).match(/^(.*-)(\d{4})$/);
167	el.textContent = 'שלחנו קוד אימות אל ';
168
169	if (!match) {
170		el.append(document.createTextNode(maskedPhone));
171		return;
172	}
173
174	el.append(document.createTextNode(match[1]));
175	const lastDigits = document.createElement('span');
176	lastDigits.className = 'login-otp-last-digits';
177	lastDigits.textContent = match[2];
178	el.append(lastDigits);
179}
180
181function hideForgotPasswordElements() {
182	hideEl(qs('#forgotUserNameField'));
183	hideEl(qs('#forgotOtpField'));
184	hideEl(qs('#forgotNewPasswordField'));
185	hideEl(qs('#forgotConfirmPasswordField'));
186	hideEl(qs('#forgotPasswordPrimaryAction'));
187	hideEl(qs('#forgotPasswordSecondaryActions'));
188	hideEl(qs('#forgotPasswordBackActions'));
189}
190
191function clearForgotPasswordInputs({ keepEmail = false, keepOtp = false } = {}) {
192	const emailInput = qs('#Inp_ForgotPassword_UserName');
193	const otpInput = qs('#Inp_ForgotPassword_OtpCode');
194	const newPasswordInput = qs('#Inp_ForgotPassword_NewPassword');
195	const confirmPasswordInput = qs('#Inp_ForgotPassword_ConfirmPassword');
196
197	if (emailInput && !keepEmail) {
198		emailInput.value = '';
199	}
200	if (otpInput && !keepOtp) {
201		otpInput.value = '';
202	}
203	if (newPasswordInput) {
204		newPasswordInput.value = '';
205	}
206	if (confirmPasswordInput) {
207		confirmPasswordInput.value = '';
208	}
209}
210
211function enterForgotPasswordRequestMode(prefillEmail = '') {
212	forgotPasswordState.active = true;
213	forgotPasswordState.step = 'request';
214	forgotPasswordState.email = String(prefillEmail || '').trim();
215	forgotPasswordState.challengeId = 0;
216
217	hideEl(qs('#userName'));
218	hideEl(qs('#password'));
219	hideEl(qs('#otpField'));
220	hideEl(qs('#otpHelpText'));
221	hideEl(qs('#connect'));
222	hideEl(qs('#otpActions'));
223	hideEl(qs('#loginSupportActions'));
224	hideForgotPasswordElements();
225
226	showEl(qs('#forgotUserNameField'));
227	showEl(qs('#forgotPasswordPrimaryAction'));
228	showEl(qs('#forgotPasswordBackActions'));
229
230	setTitle('איפוס סיסמה');
231	setSubtitle('הזן שם משתמש, ואם החשבון קיים במערכת נשלח קוד אימות לנייד המשויך אליו');
232
233	const primaryBtn = qs('#forgotPasswordPrimaryAction');
234	if (primaryBtn) {
235		primaryBtn.textContent = 'שלח קוד אימות';
236	}
237
238	clearForgotPasswordInputs({ keepEmail: true });
239	const emailInput = qs('#Inp_ForgotPassword_UserName');
240	if (emailInput) {
241		emailInput.value = forgotPasswordState.email;
242		emailInput.focus();
243	}
244}
245
246function enterForgotPasswordOtpMode(email, challengeId) {
247	forgotPasswordState.active = true;
248	forgotPasswordState.step = 'otp';
249	forgotPasswordState.email = String(email || '').trim();
250	forgotPasswordState.challengeId = Number(challengeId || 0);
251
252	hideForgotPasswordElements();
253	showEl(qs('#forgotOtpField'));
254	showEl(qs('#forgotPasswordPrimaryAction'));
255	showEl(qs('#forgotPasswordSecondaryActions'));
256	showEl(qs('#forgotPasswordBackActions'));
257
258	setTitle('אימות קוד');
259	setSubtitle('אם החשבון קיים במערכת, נשלח קוד אימות לנייד המשויך אליו');
260
261	const primaryBtn = qs('#forgotPasswordPrimaryAction');
262	if (primaryBtn) {
263		primaryBtn.textContent = 'אמת קוד';
264	}
265
266	clearForgotPasswordInputs({ keepEmail: true });
267	const emailInput = qs('#Inp_ForgotPassword_UserName');
268	if (emailInput) {
269		emailInput.value = forgotPasswordState.email;
270	}
271	const otpInput = qs('#Inp_ForgotPassword_OtpCode');
272	if (otpInput) {
273		otpInput.focus();
274	}
275}
276
277function enterForgotPasswordNewPasswordMode() {
278	forgotPasswordState.active = true;
279	forgotPasswordState.step = 'password';
280
281	hideForgotPasswordElements();
282	showEl(qs('#forgotNewPasswordField'));
283	showEl(qs('#forgotConfirmPasswordField'));
284	showEl(qs('#forgotPasswordPrimaryAction'));
285	showEl(qs('#forgotPasswordBackActions'));
286
287	setTitle('הגדרת סיסמה חדשה');
288	setSubtitle('הזינו סיסמה חדשה ואשרו אותה כדי להשלים את האיפוס');
289
290	const primaryBtn = qs('#forgotPasswordPrimaryAction');
291	if (primaryBtn) {
292		primaryBtn.textContent = 'שמור סיסמה חדשה';
293	}
294
295	clearForgotPasswordInputs({ keepEmail: true });
296	const passwordInput = qs('#Inp_ForgotPassword_NewPassword');
297	if (passwordInput) {
298		passwordInput.focus();
299	}
300}
301
302function enterForgotPasswordSuccessMode() {
303	forgotPasswordState.active = true;
304	forgotPasswordState.step = 'success';
305	forgotPasswordState.challengeId = 0;
306
307	hideForgotPasswordElements();
308	showEl(qs('#forgotPasswordPrimaryAction'));
309
310	setTitle('הסיסמה עודכנה');
311	setSubtitle('הסיסמה עודכנה בהצלחה. ניתן להתחבר למערכת.');
312
313	const primaryBtn = qs('#forgotPasswordPrimaryAction');
314	if (primaryBtn) {
315		primaryBtn.textContent = 'חזרה להתחברות';
316		primaryBtn.focus();
317	}
318}
319
320function exitForgotPasswordMode() {
321	const emailToRestore = forgotPasswordState.email || getForgotUserNameValue().trim();
322	forgotPasswordState.active = false;
323	forgotPasswordState.step = 'none';
324	forgotPasswordState.email = '';
325	forgotPasswordState.challengeId = 0;
326
327	hideForgotPasswordElements();
328	showEl(qs('#loginSupportActions'));
329	resetAuthFlow({ clearPassword: false });
330
331	const loginUserInput = qs('#Inp_LogIn_UserName');
332	if (loginUserInput && emailToRestore) {
333		loginUserInput.value = emailToRestore;
334	}
335	if (loginUserInput) {
336		loginUserInput.focus();
337	}
338}
339
340function resetAuthFlow({ clearPassword = true } = {}) {
341	loginState.resolvedEmail = '';
342	loginState.authMode = 0;
343	loginState.challengeId = 0;
344	lastAutoSubmittedOtp = '';
345
346	setTitle('לכניסה למערכת');
347	showEl(qs('#userName'));
348	showEl(qs('#password'));
349	showEl(qs('#connect'));
350	showEl(qs('#loginSupportActions'));
351	hideEl(qs('#otpField'));
352	hideEl(qs('#otpHelpText'));
353	hideEl(qs('#otpActions'));
354	hideForgotPasswordElements();
355	setSubtitle('הזינו שם משתמש וסיסמה כדי להמשיך');
356	setConnectButtonText('התחבר למערכת');
357
358	const otpInput = qs('#Inp_LogIn_OtpCode');
359	if (otpInput) {
360		otpInput.value = '';
361	}
362
363	const otpHelp = qs('#otpHelpText');
364	if (otpHelp) {
365		otpHelp.textContent = '';
366	}
367
368	if (clearPassword) {
369		const passInput = qs('#Inp_LogIn_Password');
370		if (passInput) {
371			passInput.value = '';
372		}
373	}
374}
375
376function setPasswordMode(email) {
377	loginState.resolvedEmail = email;
378	loginState.authMode = AUTH_MODE_PASSWORD;
379	loginState.challengeId = 0;
380	lastAutoSubmittedOtp = '';
381
382	setTitle('לכניסה למערכת');
383	showEl(qs('#userName'));
384	showEl(qs('#password'));
385	showEl(qs('#connect'));
386	showEl(qs('#loginSupportActions'));
387	hideEl(qs('#otpField'));
388	hideEl(qs('#otpHelpText'));
389	hideEl(qs('#otpActions'));
390	hideForgotPasswordElements();
391	setSubtitle('הזינו סיסמה והתחברו');
392	setConnectButtonText('התחבר למערכת');
393
394	const otpInput = qs('#Inp_LogIn_OtpCode');
395	if (otpInput) {
396		otpInput.value = '';
397	}
398
399	const passInput = qs('#Inp_LogIn_Password');
400	if (passInput) {
401		passInput.focus();
402	}
403}
404
405function enterSmsMode(email, otpResult) {
406	loginState.resolvedEmail = email;
407	loginState.authMode = AUTH_MODE_SMS_OTP;
408	loginState.challengeId = Number(otpResult?.ChallengeId || 0);
409	lastAutoSubmittedOtp = '';
410
411	setTitle('לכניסה למערכת');
412	showEl(qs('#userName'));
413	showEl(qs('#password'));
414	showEl(qs('#otpField'));
415	showEl(qs('#otpHelpText'));
416	showEl(qs('#otpActions'));
417	showEl(qs('#connect'));
418	hideEl(qs('#loginSupportActions'));
419	hideForgotPasswordElements();
420	setConnectButtonText('אמת והתחבר');
421
422	const maskedPhone = String(otpResult?.MaskedPhone || '').trim();
423	setSubtitle('הסיסמה אומתה. הזינו קוד אימות שנשלח ב-SMS', true);
424	const otpHelp = qs('#otpHelpText');
425	if (otpHelp) {
426		renderOtpHelpText(otpHelp, maskedPhone);
427	}
428
429	const otpInput = qs('#Inp_LogIn_OtpCode');
430	if (otpInput) {
431		otpInput.value = '';
432		otpInput.focus();
433	}
434}
435
436async function postJson(url, body) {
437	const res = await fetch(url, {
438		method: 'POST',
439		headers: { 'Content-Type': 'application/json; charset=utf-8' },
440		credentials: 'same-origin',
441		body: JSON.stringify(body || {}),
442	});
443
444	const data = await res.json().catch(() => ({}));
445	if (!res.ok || !data || Number(data.isSuccess || 0) !== 1) {
446		const msg = String(data?.message || data?.error || `HTTP ${res.status}` || 'שגיאה בשרת').trim();
447		throw new Error(msg || 'שגיאה בשרת');
448	}
449
450	return data;
451}
452
453async function resolveAuthMode(email) {
454	if (loginState.resolvedEmail === email && loginState.authMode) {
455		return { AuthMode: loginState.authMode };
456	}
457
458	const out = await postJson('/crm/api/login/ResolveAuthMode.php', {
459		UserName: email,
460	});
461	const authMode = normalizeAuthMode(out?.result?.AuthMode);
462
463	loginState.resolvedEmail = email;
464	loginState.authMode = authMode;
465	loginState.challengeId = 0;
466
467	return { AuthMode: authMode };
468}
469
470async function requestOtp(email, password) {
471	const out = await postJson('/crm/api/login/RequestOtp.php', {
472		UserName: email,
473		password,
474	});
475	return out.result || {};
476}
477
478async function requestPasswordResetOtp(email) {
479	const out = await postJson('/crm/api/login/RequestPasswordResetOtp.php', {
480		UserName: email,
481	});
482	return out.result || {};
483}
484
485async function verifyOtp(challengeId, otpCode) {
486	const out = await postJson('/crm/api/login/VerifyOtp.php', {
487		ChallengeId: challengeId,
488		OtpCode: otpCode,
489	});
490	return out.result || {};
491}
492
493async function verifyPasswordResetOtp(challengeId, otpCode) {
494	const out = await postJson('/crm/api/login/VerifyPasswordResetOtp.php', {
495		ChallengeId: challengeId,
496		OtpCode: otpCode,
497	});
498	return out.result || {};
499}
500
501async function savePasswordReset(password, passwordConfirm) {
502	const out = await postJson('/crm/api/login/SavePasswordReset.php', {
503		Password: password,
504		PasswordConfirm: passwordConfirm,
505	});
506	return out.result || {};
507}
508
509async function loginWithPassword(email, password) {
510	const out = await postJson('/crm/api/login/MakeLogin.php', {
511		UserName: email,
512		password,
513	});
514	return out.result || {};
515}
516
517function consumeSafeReturnUrl() {
518	let stored = '';
519	try {
520		stored = sessionStorage.getItem('crm_return_url') || '';
521	} catch (_) {
522		return '';
523	}
524	try {
525		sessionStorage.removeItem('crm_return_url');
526	} catch (_) {}
527	if (!stored) return '';
528	try {
529		const u = new URL(stored, window.location.origin);
530		if (u.origin !== window.location.origin) return '';
531		const p = u.pathname;
532		const allowed = p.startsWith('/crm/') || p === '/super-sales' || p.startsWith('/super-sales/');
533		if (!allowed) return '';
534		if (p.toLowerCase().endsWith('/crm/login.php')) return '';
535		return u.href;
536	} catch (_) {
537		return '';
538	}
539}
540
541function normalizeLoginDefaultRoute(route) {
542	const normalized = String(route || '').trim().toLowerCase();
543	return ['leads', 'events', 'invitations'].includes(normalized) ? normalized : 'leads';
544}
545
546function redirectAfterLogin(legacyResult) {
547	const returnUrl = consumeSafeReturnUrl();
548	if (returnUrl) {
549		window.location.href = returnUrl;
550		return;
551	}
552
553	const idx = Number(legacyResult?.index || 0);
554	const type = Number(legacyResult?.type || 0);
555	const businessId = legacyResult?.businessId ? String(legacyResult.businessId) : '';
556
557	if (idx === 1) {
558		window.location.href = '/crm/AddComment.php';
559		return;
560	}
561
562	if (type === 1) {
563		let url = '/crm/';
564		if (businessId) {
565			url = '/crm/?id=' + encodeURIComponent(businessId);
566		}
567		window.location.href = url;
568		return;
569	}
570
571	const defaultRoute = normalizeLoginDefaultRoute(legacyResult?.defaultRoute);
572	let url = '/crm/#' + defaultRoute;
573	if (businessId) {
574		url = '/crm/?id=' + encodeURIComponent(businessId) + '#' + defaultRoute;
575	}
576	window.location.href = url;
577}
578
579async function handleConnectClick(event) {
580	if (event) {
581		event.preventDefault();
582	}
583
584	if (forgotPasswordState.active || loginSubmitInFlight) {
585		return;
586	}
587
588	const userName = getUserNameValue().trim();
589	if (!userName) {
590		showLoginMessage('נא להזין שם משתמש');
591		return;
592	}
593
594	window.scrollTo(0, 0);
595	loginSubmitInFlight = true;
596	setBusy(true);
597
598	try {
599		if (loginState.challengeId > 0 && loginState.resolvedEmail === userName) {
600			const otpCode = getOtpValue().trim();
601			if (!/^\d{6}$/.test(otpCode)) {
602				showLoginMessage('נא להזין קוד אימות בן 6 ספרות');
603				return;
604			}
605
606			const loginResult = await verifyOtp(loginState.challengeId, otpCode);
607			redirectAfterLogin(loginResult);
608			return;
609		}
610
611		const password = getPasswordValue();
612		if (!password) {
613			const passInput = qs('#Inp_LogIn_Password');
614			if (passInput) {
615				passInput.focus();
616			}
617			showLoginMessage('נא להזין סיסמה');
618			return;
619		}
620
621		const loginResult = await loginWithPassword(userName, password);
622		if (Number(loginResult?.otpRequired || 0) === 1 || Number(loginResult?.ChallengeId || 0) > 0) {
623			enterSmsMode(userName, loginResult);
624			showOtpSentMessage(false);
625			return;
626		}
627
628		redirectAfterLogin(loginResult);
629	} catch (error) {
630		const msg = String(error?.message || '').trim();
631		if (msg && msg !== 'USER_NOT_CONNECT') {
632			showLoginMessage(msg);
633		}
634	} finally {
635		loginSubmitInFlight = false;
636		setBusy(false);
637	}
638}
639
640function handleLoginOtpInput() {
641	if (forgotPasswordState.active || loginSubmitInFlight || loginState.authMode !== AUTH_MODE_SMS_OTP || loginState.challengeId <= 0) {
642		return;
643	}
644
645	const userName = getUserNameValue().trim();
646	const otpCode = getOtpValue().trim();
647	if (loginState.resolvedEmail !== userName || !/^\d{6}$/.test(otpCode) || otpCode === lastAutoSubmittedOtp) {
648		return;
649	}
650
651	lastAutoSubmittedOtp = otpCode;
652	handleConnectClick();
653}
654
655async function handleForgotPasswordPrimaryAction(event) {
656	if (event) {
657		event.preventDefault();
658	}
659
660	if (!forgotPasswordState.active || loginSubmitInFlight) {
661		return;
662	}
663
664	if (forgotPasswordState.step === 'success') {
665		exitForgotPasswordMode();
666		return;
667	}
668
669	loginSubmitInFlight = true;
670	setBusy(true);
671
672	try {
673		if (forgotPasswordState.step === 'request') {
674			const email = getForgotUserNameValue().trim();
675			if (!email) {
676				showLoginMessage('נא להזין שם משתמש');
677				return;
678			}
679
680			const result = await requestPasswordResetOtp(email);
681			enterForgotPasswordOtpMode(email, Number(result?.ChallengeId || 0));
682			showForgotPasswordOtpSentMessage();
683			return;
684		}
685
686		if (forgotPasswordState.step === 'otp') {
687			const otpCode = getForgotOtpValue().trim();
688			if (!/^\d{6}$/.test(otpCode)) {
689				showLoginMessage('נא להזין קוד אימות בן 6 ספרות');
690				return;
691			}
692
693			await verifyPasswordResetOtp(forgotPasswordState.challengeId, otpCode);
694			enterForgotPasswordNewPasswordMode();
695			return;
696		}
697
698		if (forgotPasswordState.step === 'password') {
699			await savePasswordReset(getForgotNewPasswordValue(), getForgotConfirmPasswordValue());
700			enterForgotPasswordSuccessMode();
701			showLoginMessage('הסיסמה עודכנה בהצלחה. ניתן להתחבר למערכת.', 'success');
702		}
703	} catch (error) {
704		const msg = String(error?.message || '').trim();
705		showLoginMessage(msg || 'שגיאה בתהליך איפוס הסיסמה');
706	} finally {
707		loginSubmitInFlight = false;
708		setBusy(false);
709	}
710}
711
712async function handleForgotPasswordResend(event) {
713	if (event) {
714		event.preventDefault();
715	}
716
717	if (!forgotPasswordState.active || forgotPasswordState.step !== 'otp' || loginSubmitInFlight) {
718		return;
719	}
720
721	const email = forgotPasswordState.email || getForgotUserNameValue().trim();
722	if (!email) {
723		showLoginMessage('נא להזין שם משתמש');
724		enterForgotPasswordRequestMode('');
725		return;
726	}
727
728	loginSubmitInFlight = true;
729	setBusy(true);
730
731	try {
732		const result = await requestPasswordResetOtp(email);
733		enterForgotPasswordOtpMode(email, Number(result?.ChallengeId || 0));
734		showForgotPasswordOtpSentMessage();
735	} catch (error) {
736		const msg = String(error?.message || '').trim();
737		showLoginMessage(msg || 'שגיאה בשליחת קוד האימות');
738	} finally {
739		loginSubmitInFlight = false;
740		setBusy(false);
741	}
742}
743
744async function sendNewLink() {
745	const userName = getUserNameValue();
746	const password = getPasswordValue();
747
748	setBusy(true);
749	try {
750		const res = await fetch('/crm/api/SendNewLink.php', {
751			method: 'POST',
752			headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
753			body: new URLSearchParams({ email: userName, password }).toString(),
754			credentials: 'same-origin'
755		});
756
757		const text = await res.text();
758		if (String(text).trim() !== '0') {
759			hideEl(qs('#sendNewLink'));
760			showEl(qs('#sendNewLink_message'));
761		}
762	} catch (e) {
763	} finally {
764		setBusy(false);
765	}
766}
767
768async function restorePassword() {
769	const tel_manager = getRestorePhoneValue();
770	const showProgress = qs('#showProgress');
771
772	showEl(showProgress);
773
774	try {
775		const res = await fetch('/crm/api/RestorePassword.php', {
776			method: 'POST',
777			headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
778			body: new URLSearchParams({ tel_manager }).toString(),
779			credentials: 'same-origin'
780		});
781
782		const data = await res.json();
783		if (data && data.bool === true) {
784			const el = qs('#Inp_LogIn_restorePassword');
785			if (el) el.value = '';
786		}
787	} catch (e) {
788	} finally {
789		hideEl(showProgress);
790	}
791}
792
793function bindLoginHandlers() {
794	const btnConnect = qs('#connect');
795	const btnSendNewLink = qs('#sendNewLink');
796	const btnRestoreToggle = qs('#RestoreMyPassword_div');
797	const btnRestore = qs('#restorePassword');
798	const btnResendOtp = qs('#resendOtp');
799	const btnChangeUser = qs('#changeUser');
800	const btnForgotPasswordLink = qs('#forgotPasswordLink');
801	const btnForgotPasswordPrimaryAction = qs('#forgotPasswordPrimaryAction');
802	const btnForgotPasswordResend = qs('#forgotPasswordResendOtp');
803	const btnForgotPasswordBack = qs('#forgotPasswordBackToLogin');
804
805	if (btnConnect && btnConnect.dataset.bound === '1') {
806		return;
807	}
808
809	const winWidth = window.innerWidth || 0;
810	if (winWidth < 600) {
811		const topBar = qs('#topBar');
812		if (topBar) topBar.remove();
813	}
814
815	const inpUser = qs('#Inp_LogIn_UserName');
816	const inpOtp = qs('#Inp_LogIn_OtpCode');
817	const inpRestore = qs('#Inp_LogIn_restorePassword');
818	if (inpUser && inpRestore) {
819		inpUser.addEventListener('blur', () => {
820			inpRestore.value = inpUser.value;
821		});
822	}
823	if (inpUser) {
824		inpUser.addEventListener('input', () => {
825			const currentValue = String(inpUser.value || '').trim();
826			if (currentValue !== loginState.resolvedEmail) {
827				resetAuthFlow({ clearPassword: false });
828			}
829		});
830	}
831
832	const inpPass = qs('#Inp_LogIn_Password');
833	const btnPasswordToggle = qs('#loginPasswordToggle');
834	if (inpPass) {
835		inpPass.addEventListener('input', () => {
836			if (loginState.challengeId > 0) {
837				resetAuthFlow({ clearPassword: false });
838			}
839		});
840
841		const hebrewReg = /^(?=.*[א-ת])/;
842		inpPass.addEventListener('keypress', () => {
843			if (hebrewReg.test(String(inpPass.value || ''))) {
844				showLoginMessage('הסיסמה שהזנת מכילה אותיות בעברית, אנא הזן מחדש.');
845			}
846		});
847	}
848	if (inpOtp) {
849		inpOtp.addEventListener('input', handleLoginOtpInput);
850	}
851
852	if (btnPasswordToggle) {
853		setLoginPasswordVisibility(btnPasswordToggle.dataset.visible === '1');
854		btnPasswordToggle.addEventListener('click', () => {
855			setLoginPasswordVisibility(btnPasswordToggle.dataset.visible !== '1');
856			if (inpPass) {
857				inpPass.focus();
858			}
859		});
860	}
861
862	if (btnSendNewLink) {
863		btnSendNewLink.addEventListener('click', (e) => {
864			e.preventDefault();
865			sendNewLink();
866		});
867	}
868
869	if (btnRestoreToggle) {
870		btnRestoreToggle.addEventListener('click', (e) => {
871			e.preventDefault();
872			const box = qs('#RestoreMyPassword');
873			if (box) {
874				showEl(box);
875			}
876			if (inpRestore) {
877				inpRestore.focus();
878			}
879		});
880	}
881
882	if (btnRestore) {
883		btnRestore.addEventListener('click', (e) => {
884			e.preventDefault();
885			restorePassword();
886		});
887	}
888
889	if (btnResendOtp) {
890		btnResendOtp.addEventListener('click', async (event) => {
891			event.preventDefault();
892
893			if (loginSubmitInFlight) {
894				return;
895			}
896
897			const userName = getUserNameValue().trim();
898			const password = getPasswordValue();
899			if (!userName) {
900				showLoginMessage('נא להזין שם משתמש');
901				return;
902			}
903			if (!password) {
904				showLoginMessage('נא להזין סיסמה');
905				return;
906			}
907
908			loginSubmitInFlight = true;
909			setBusy(true);
910			try {
911				const otpResult = await requestOtp(userName, password);
912				enterSmsMode(userName, otpResult);
913				showOtpSentMessage(true);
914			} catch (error) {
915				const msg = String(error?.message || '').trim();
916				if (msg) {
917					showLoginMessage(msg);
918				}
919			} finally {
920				loginSubmitInFlight = false;
921				setBusy(false);
922			}
923		});
924	}
925
926	if (btnChangeUser) {
927		btnChangeUser.addEventListener('click', (event) => {
928			event.preventDefault();
929			resetAuthFlow();
930			const inp = qs('#Inp_LogIn_UserName');
931			if (inp) {
932				inp.focus();
933			}
934		});
935	}
936
937	if (btnForgotPasswordLink) {
938		btnForgotPasswordLink.addEventListener('click', (event) => {
939			event.preventDefault();
940			enterForgotPasswordRequestMode(getUserNameValue().trim());
941		});
942	}
943
944	if (btnForgotPasswordPrimaryAction) {
945		btnForgotPasswordPrimaryAction.addEventListener('click', handleForgotPasswordPrimaryAction);
946	}
947
948	if (btnForgotPasswordResend) {
949		btnForgotPasswordResend.addEventListener('click', handleForgotPasswordResend);
950	}
951
952	if (btnForgotPasswordBack) {
953		btnForgotPasswordBack.addEventListener('click', (event) => {
954			event.preventDefault();
955			exitForgotPasswordMode();
956		});
957	}
958
959	if (btnConnect) {
960		btnConnect.dataset.bound = '1';
961		btnConnect.addEventListener('click', handleConnectClick);
962	}
963
964	document.addEventListener('keypress', (e) => {
965		const target = e.target;
966		if (!target || target.tagName !== 'INPUT') return;
967		if (e.which !== 13 && e.key !== 'Enter') return;
968
969		const id = target.getAttribute('id') || '';
970		if (id === 'Inp_LogIn_restorePassword') {
971			const b = qs('#restorePassword');
972			if (b) b.click();
973			e.preventDefault();
974			return false;
975		}
976
977		if (forgotPasswordState.active) {
978			const b = qs('#forgotPasswordPrimaryAction');
979			if (b) b.click();
980			e.preventDefault();
981			return false;
982		}
983
984		const b = qs('#connect');
985		if (b) b.click();
986		e.preventDefault();
987		return false;
988	});
989}
990
991resetAuthFlow({ clearPassword: false });
992bindLoginHandlers();
993
994window.SendNewLink = sendNewLink;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.