1<!DOCTYPE html> 2<html class="web-pages d-flex justify-content-center" lang="fa"> 3 4<head> 5 <meta charset="utf-8"> 6 <title itemprop="name">Login</title> 7 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 8 <meta name="generator" content="Rodin365"> 9 10 11 <meta name="title" content="Login"><meta name="language" content="fa"><meta property="og:type" content="article"><meta property="og:title" content="Login"><meta name="twitter:title" content="Login"><meta name="twitter:card" content="summary"> 12 13 14 15 16 <link rel="shortcut icon" href="/files/WhatsApp-Image-2026-09-12-at-12.09.15-(2)01cbcb.jpeg" 17 type="image/x-icon"> 18 19 20 21 <link rel="apple-touch-icon" href="/files/WhatsApp-Image-2026-09-12-at-12.09.15-(2)01cbcb.jpeg"> 22 <link rel="stylesheet" href="/assets/frappe/bootstrap-icons/bootstrap-icons.css"> 23 24 <link rel="canonical" href="https://morvarid.pub/login"> 25 26 <link rel="stylesheet" href="/assets/frappe/css/swiper_css.css"> 27 <link href="/assets/frappe/css/aos.css" rel="stylesheet"> 28 <link href="/assets/frappe/css/glightbox.css" rel="stylesheet"> 29 30 <!--
30<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js" async=""></script>
30 --> 31
31<script src="/assets/erpnext/js/swiper_js.js"></script>
31 32
32<script src="/assets/erpnext/js/isotope.js"></script>
32 33
33<script src="/assets/erpnext/js/glightbox.js"></script>
33 34
34<script src="/assets/erpnext/js/imagesloaded.js"></script>
34 35 36<link type="text/css" rel="stylesheet" href="/files/website_theme/theme_rodin_bfc79be1.css"> 37<link type="text/css" rel="stylesheet" href="/assets/erpnext/css/persianDatepicker.bundle.css"> 38<link type="text/css" rel="stylesheet" href="/assets/erpnext/css/farsiDatepicker.bundle.css"> 39<link type="text/css" rel="stylesheet" href="/assets/erpnext/dist/css-rtl/erpnext-web.bundle.RMP35DDA.css"> 40 41 42 43 44<link type="text/css" rel="stylesheet" href="/assets/frappe/dist/css-rtl/login.bundle.JUM5YRMZ.css"> 45 46<style> 47 .rodin-seo-hidden-heading { 48 display: flex !important; 49 height: 0 !important; 50 overflow: hidden !important; 51 margin: 0 !important; 52 padding: 0 !important; 53 } 54 </style> 55<style> 56 @media screen and (max-width:676px) { 57 .header-title{ 58 font-size: 12px; 59 } 60 .page-header-actions-block .dropdown-toggle span{ 61 font-size: 12px !important; 62 } 63 } 64</style>
65<script> 66 window.frappe = {}; 67 frappe.ready_events = []; 68 frappe.ready = function (fn) { 69 frappe.ready_events.push(fn); 70 } 71 window.dev_server = 0; 72 window.socketio_port = 9000; 73 window.show_language_picker = false; 74 </script>
74 75</head> 76 77<body frappe-session-status="logged-out" 78 data-path="login" 79 80 81 class=" w-100"> 82 83 84 85 86 87 88 89 90 91 92 93<div id="page-login" data-path="login"> 94 95<div class="page-content-wrapper"> 96 <!-- breadcrumbs --> 97 <div class="page-breadcrumbs"> 98 99 100 101 </div> 102 103 104 <main class="page-container container my-0"> 105 <div class="page-header-wrapper d-none"> 106 <div class="page-header"> 107 108 </div> 109 110 111 </div> 112 113 <div class="page_content"> 114 115 116 117<div class="rodin365-auth" dir="rtl"> 118 <noscript> 119 <div class="text-center my-5"> 120 <h4>Ø¬Ø§ÙØ§ اسکرÛپت بر رÙÛ Ù Ø±ÙØ±Ú¯Ø± ش٠ا ØºÛØ± ÙØ¹Ø§Ù شد٠است</h4> 121 <p class="text-muted"> 122 Ø¨Ø§ÛØ¯ Ø¬Ø§ÙØ§ اسکرÛپت را ÙØ¹Ø§Ù Ú©ÙÛØ¯ تا Ø¨Ø±ÙØ§Ù ٠ش٠ا کار Ú©ÙØ¯. 123 </p> 124 </div> 125 </noscript> 126 127 <div class="rodin365-auth__shell"> 128 <aside class="rodin365-auth__visual" aria-hidden="false"> 129 <img class="rodin365-auth__visual-img" src="/files/morvarid.png" alt="Auth visual"> 130 <div class="rodin365-auth__visual-top"> 131 <a class="rodin365-auth__back" href="/" aria-label="بازگشت"> 132 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 133 <path d="M5 12h14M13 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 134 </svg> 135 </a> 136 <div class="rodin365-auth__logo-pill"> 137 138<img class="app-logo" src="/files/WhatsApp-Image-2026-09-12-at-12.09.15-_2_-removebg-preview.png" alt="ÙÙÚ¯Ù"> 139 140 </div> 141 </div> 142 </aside> 143 144 <div class="rodin365-auth__panel"> 145 <div class="rodin365-auth__panel-inner"> 146 <section class="for-login"> 147 <div class="login-form-container"> 148 <div class="login-content page-card"> 149 <div class="loader-overlay" id="loginLoader"> 150 <div class="loader-container"> 151 <div class="loader-spinner"></div> 152 <div class="loader-text" dir="rtl">در ØØ§Ù ارسا٠کد...</div> 153 </div> 154 </div> 155 <div class="page-card-head"> 156 <h1 id="login-form-title">ÙØ±Ùد / ثبت ÙØ§Ù </h1> 157 <p id="login-form-subtitle">Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ Ø®ÙØ¯ را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯</p> 158 </div> 159 <form class="form-signin form-login" role="form"> 160 161 162<style> 163 /* ââ Rodin365 Auth (/auth design) ââ */ 164 :root { 165 --rodin365-primary: var(--primary-color); 166 --rodin365-primary-deep: var(--dark-primary); 167 --rodin365-primary-foreground: #FFFFFF; 168 --rodin365-accent: var(--primary-color); 169 --rodin365-accent-soft: var(--primary-light); 170 --rodin365-gold: var(--secondary-color); 171 --rodin365-gold-soft: color-mix(in srgb, var(--secondary-color) 10%, #ffffff); 172 --rodin365-foreground: #1F2A37; 173 --rodin365-muted: #6B7280; 174 --rodin365-muted-soft: #9CA3AF; 175 --rodin365-border: #E8EDF4; 176 --rodin365-border-strong: #D1DBE8; 177 --rodin365-bg: #FFFFFF; 178 --rodin365-secondary: #F8FAFC; 179 --rodin365-radius: 0.75rem; 180 --rodin365-radius-xl: 1rem; 181 --rodin365-radius-2xl: 1.25rem; 182 --rodin365-shadow-card: 0 1px 2px color-mix(in srgb, var(--primary-color) 4%, transparent), 0 4px 16px color-mix(in srgb, var(--primary-color) 6%, transparent); 183 --rodin365-shadow-elevated: 0 12px 40px color-mix(in srgb, var(--primary-color) 12%, transparent); 184 --rodin365-success: #16a34a; 185 --rodin365-destructive: #dc2626; 186 } 187 188 .rodin365-auth { 189 min-height: 100vh; 190 background: var(--rodin365-bg); 191 color: var(--rodin365-foreground); 192 direction: rtl; 193 } 194 195 .rodin365-auth__shell { 196 min-height: 100vh; 197 display: flex; 198 flex-direction: column; 199 } 200 201 @media (min-width: 1024px) { 202 .rodin365-auth__shell { 203 display: grid; 204 grid-template-columns: 1fr 1fr; 205 direction: ltr; 206 } 207 208 .rodin365-auth__visual, 209 .rodin365-auth__panel { 210 direction: rtl; 211 } 212 } 213 214 .rodin365-auth__visual { 215 position: relative; 216 height: 9rem; 217 overflow: hidden; 218 flex-shrink: 0; 219 } 220 221 @media (min-width: 640px) { 222 .rodin365-auth__visual { 223 height: 12rem; 224 } 225 } 226 227 @media (min-width: 1024px) { 228 .rodin365-auth__visual { 229 height: auto; 230 min-height: 100vh; 231 } 232 } 233 234 .rodin365-auth__visual-img { 235 width: 100%; 236 height: 100%; 237 object-fit: cover; 238 display: block; 239 } 240 241 .rodin365-auth__visual-top { 242 position: absolute; 243 inset-inline: 0; 244 top: 0; 245 display: flex; 246 align-items: flex-start; 247 justify-content: space-between; 248 padding: 1rem; 249 z-index: 2; 250 } 251 252 @media (min-width: 1024px) { 253 .rodin365-auth__visual-top { 254 padding: 2rem; 255 } 256 } 257 258 .rodin365-auth__back { 259 display: grid; 260 place-items: center; 261 width: 2.5rem; 262 height: 2.5rem; 263 border: none; 264 border-radius: 999px; 265 background: color-mix(in srgb, var(--rodin365-bg) 85%, transparent); 266 color: var(--rodin365-foreground); 267 backdrop-filter: blur(8px); 268 cursor: pointer; 269 transition: background 0.15s ease; 270 text-decoration: none; 271 } 272 273 .rodin365-auth__back:hover { 274 background: var(--rodin365-bg);
275 color: var(--rodin365-foreground); 276 } 277 278 .rodin365-auth__logo-pill { 279 border-radius: 0.75rem; 280 background: color-mix(in srgb, var(--rodin365-bg) 85%, transparent); 281 backdrop-filter: blur(8px); 282 padding: 0.5rem 0.75rem; 283 } 284 285 .rodin365-auth__logo-pill .app-logo { 286 display: block; 287 height: 1.75rem; 288 width: auto; 289 max-width: 140px; 290 object-fit: contain; 291 margin: 0; 292 } 293 294 @media (min-width: 1024px) { 295 .rodin365-auth__logo-pill .app-logo { 296 height: 2.25rem; 297 max-width: 180px; 298 } 299 } 300 301 .rodin365-auth__panel { 302 flex: 1; 303 display: flex; 304 align-items: flex-start; 305 justify-content: center; 306 padding: 2rem 1rem; 307 background: var(--rodin365-bg); 308 } 309 310 @media (min-width: 640px) { 311 .rodin365-auth__panel { 312 padding-inline: 1.5rem; 313 } 314 } 315 316 @media (min-width: 1024px) { 317 .rodin365-auth__panel { 318 align-items: center; 319 padding-block: 3rem; 320 min-height: 100vh; 321 } 322 } 323 324 .rodin365-auth__panel-inner { 325 width: 100%; 326 max-width: 28rem; 327 position: relative; 328 } 329 330 /* Hide site chrome */ 331 .navbar, 332 .page-header-wrapper, 333 .page-footer, 334 .page-breadcrumbs, 335 header, 336 footer, 337 .web-footer, 338 .footer-padding, 339 .footer-spacer, 340 .navbar-header, 341 .navbar-nav, 342 .bottom-menu-space { 343 display: none !important; 344 visibility: hidden !important; 345 height: 0 !important; 346 overflow: hidden !important; 347 margin: 0 !important; 348 padding: 0 !important; 349 } 350 351 .page-content-wrapper, 352 .page-container { 353 padding: 0 !important; 354 margin: 0 !important; 355 max-width: unset !important; 356 min-height: 100vh !important; 357 background: var(--rodin365-bg) !important; 358 } 359 360 .for-login, 361 .for-forgot, 362 .for-login-with-email-link, 363 .for-signup, 364 .for-email-login { 365 width: 100%; 366 margin: 0; 367 padding: 0; 368 min-height: 0; 369 position: relative; 370 } 371 372 .login-form-container, 373 .otp-container { 374 width: 100%; 375 display: flex; 376 flex-direction: column; 377 align-items: stretch; 378 } 379 380 .otp-container { 381 position: absolute; 382 inset: 0; 383 z-index: 10; 384 display: none !important; 385 visibility: hidden; 386 } 387 388 .otp-container.show-otp { 389 display: flex !important; 390 visibility: visible !important; 391 } 392 393 .otp-container.show-otp ~ .login-form-container { 394 display: none !important; 395 } 396 397 .logo-wrapper, 398 .mobile-logo-wrapper, 399 .for-email-login .app-logo, 400 .for-email-login .logo-wrapper, 401 .for-signup .app-logo, 402 .for-signup .logo-wrapper, 403 .for-forgot .app-logo, 404 .for-forgot .logo-wrapper, 405 .for-login-with-email-link .app-logo, 406 .for-login-with-email-link .logo-wrapper, 407 section:not(.for-login) .page-card-head .app-logo { 408 display: none !important; 409 } 410 411 .for-login .page-card, 412 .for-forgot .page-card, 413 .for-login-with-email-link .page-card, 414 .for-signup .page-card, 415 .for-email-login .page-card, 416 .for-otp-verification .page-card { 417 margin: 0; 418 max-width: 100%; 419 width: 100%; 420 padding: 0; 421 border-radius: 0; 422 background: transparent; 423 box-shadow: none; 424 border: none; 425 position: relative; 426 } 427 428 .for-login .page-card form, 429 .for-forgot .page-card form, 430 .for-login-with-email-link .page-card form, 431 .for-signup .page-card form, 432 .for-email-login .page-card form, 433 .for-otp-verification .page-card form { 434 max-width: 100%; 435 } 436 437 .page-card-head { 438 margin-bottom: 1.25rem; 439 text-align: right; 440 } 441 442 .page-card-head h1, 443 .page-card-head h4 {
444 font-size: 1.5rem; 445 font-weight: 900; 446 line-height: 1.25; 447 margin: 0 0 0.5rem; 448 color: var(--rodin365-foreground); 449 text-align: right; 450 } 451 452 @media (min-width: 640px) { 453 .page-card-head h1, 454 .page-card-head h4 { 455 font-size: 1.875rem; 456 } 457 } 458 459 .page-card-head p, 460 .otp-instructions { 461 font-size: 0.875rem; 462 color: var(--rodin365-muted); 463 margin: 0; 464 text-align: right; 465 line-height: 1.6; 466 } 467 468 .page-card-body { 469 display: flex; 470 flex-direction: column; 471 gap: 0.75rem; 472 } 473 474 .page-card-body > .form-group, 475 .page-card-body > .d-flex, 476 .page-card-actions { 477 margin: 0; 478 } 479 480 .form-group { 481 margin-bottom: 0 !important; 482 } 483 484 /* Hidden wrappers must not reserve flex gap */ 485 .page-card-body > .d-flex:has(#edit-mobile-number), 486 .page-card-body > #counter { 487 display: none !important; 488 } 489 490 #login-label-name, 491 #login-label-email, 492 #login-label-password, 493 .login-label, 494 .rodin365-auth label, 495 .page-card-body label { 496 display: block; 497 font-size: 0.75rem; 498 font-weight: 700; 499 color: var(--rodin365-foreground); 500 margin-bottom: 0.375rem; 501 text-align: right; 502 padding: 0 !important; 503 } 504 505 #edit-mobile-number, 506 #countdown-container, 507 #send-code-again, 508 #staff, 509 #staff-label { 510 display: none !important; 511 } 512 513 #mobile-field-group #login-label-username { 514 display: block !important; 515 } 516 517 .rodin365-auth .form-control, 518 .rodin365-auth #login_mobile, 519 .rodin365-auth #login_name, 520 .rodin365-auth #login_email, 521 .rodin365-auth #login_password, 522 .rodin365-auth #forgot_email, 523 .rodin365-auth #forgot_mobile, 524 .rodin365-auth #login_with_email_link_email { 525 width: 100%; 526 height: 3rem; 527 border-radius: var(--rodin365-radius); 528 border: 1px solid var(--rodin365-border); 529 background: var(--rodin365-bg); 530 padding: 0 1rem; 531 font-size: 0.875rem; 532 color: var(--rodin365-foreground); 533 outline: none; 534 box-shadow: none !important; 535 transition: border-color 0.15s ease, box-shadow 0.15s ease; 536 } 537 538 .rodin365-auth #login_mobile, 539 .rodin365-auth #login_email, 540 .rodin365-auth #login_password, 541 .rodin365-auth #forgot_email, 542 .rodin365-auth #forgot_mobile, 543 .rodin365-auth #login_with_email_link_email { 544 direction: ltr; 545 text-align: left; 546 } 547 548 .rodin365-auth #login_name { 549 direction: rtl; 550 text-align: right; 551 } 552 553 .rodin365-auth .form-control:focus, 554 .rodin365-auth #login_mobile:focus, 555 .rodin365-auth #login_name:focus, 556 .rodin365-auth #login_email:focus, 557 .rodin365-auth #login_password:focus, 558 .rodin365-auth #forgot_email:focus, 559 .rodin365-auth #forgot_mobile:focus, 560 .rodin365-auth #login_with_email_link_email:focus { 561 border-color: var(--rodin365-primary); 562 box-shadow: 0 0 0 2px color-mix(in srgb, var(--rodin365-primary) 25%, transparent) !important; 563 } 564 565 .rodin365-auth .form-control::placeholder, 566 .rodin365-auth #login_mobile::placeholder { 567 color: color-mix(in srgb, var(--rodin365-muted) 70%, transparent); 568 } 569 570 .email-field, 571 .password-field, 572 .mobile-field { 573 position: relative; 574 } 575 576 .email-field .field-icon, 577 .password-field .field-icon { 578 display: none !important; 579 } 580 581 /* Beat login.bundle.scss (.for-login .page-card .page-card-body .toggle-password) */ 582 .for-login .page-card .page-card-body .password-field .toggle-password, 583 .rodin365-auth .password-field .toggle-password, 584 .password-field .toggle-password { 585 position: absolute !important; 586 right: 0.5rem !important; 587 left: auto !important; 588 top: 50% !important; 589 transform: translateY(-50%) !important; 590 z-index: 5 !important; 591 display: grid !important; 592 place-items: center !important; 593 width: 2.25rem !important; 594 height: 2.25rem !important; 595 border-radius: 0.5rem !important; 596 border: none !important; 597 background: transparent !important; 598 color: var(--rodin365-muted) !important; 599 font-size: 0 !important; 600 line-height: 0 !important; 601 cursor: pointer !important; 602 user-select: none !important; 603 margin: 0 !important; 604 padding: 0 !important; 605 pointer-events: auto !important; 606 -webkit-tap-highlight-color: transparent; 607 } 608 609 .for-login .page-card .page-card-body .password-field .toggle-password svg, 610 .rodin365-auth .password-field .toggle-password svg, 611 .password-field .toggle-password svg { 612 width: 1.125rem !important; 613 height: 1.125rem !important; 614 display: block !important; 615 pointer-events: none !important; 616 flex-shrink: 0; 617 } 618 619 .for-login .page-card .page-card-body .password-field .toggle-password .icon-eye-off, 620 .rodin365-auth .password-field .toggle-password .icon-eye-off, 621 .password-field .toggle-password .icon-eye-off { 622 display: none !important; 623 } 624 625 .for-login .page-card .page-card-body .password-field .toggle-password.is-visible .icon-eye, 626 .rodin365-auth .password-field .toggle-password.is-visible .icon-eye, 627 .password-field .toggle-password.is-visible .icon-eye { 628 display: none !important; 629 } 630 631 .for-login .page-card .page-card-body .password-field .toggle-password.is-visible .icon-eye-off, 632 .rodin365-auth .password-field .toggle-password.is-visible .icon-eye-off, 633 .password-field .toggle-password.is-visible .icon-eye-off { 634 display: block !important; 635 } 636 637 .for-login .page-card .page-card-body .password-field .toggle-password:hover, 638 .rodin365-auth .password-field .toggle-password:hover, 639 .password-field .toggle-password:hover { 640 background: var(--rodin365-secondary) !important; 641 color: var(--rodin365-foreground) !important; 642 } 643 644 .for-login .page-card .page-card-body .password-field input, 645 .rodin365-auth #login_password, 646 .rodin365-auth .password-field #login_password { 647 padding-right: 3rem !important; 648 padding-left: 1rem !important; 649 } 650 651 .forgot-password-message { 652 display: none !important; 653 } 654 655 .rodin365-auth__text-action, 656 .toggle-to-otp-login a, 657 .otp-resend-link a, 658 .btn-edit-mobile, 659 .sign-up-message a, 660 .verification-mail a { 661 color: var(--rodin365-primary) !important; 662 font-size: 0.75rem; 663 font-weight: 700; 664 text-decoration: none; 665 background: none; 666 border: none; 667 padding: 0; 668 cursor: pointer; 669 underline-offset: 4px; 670 } 671 672 .rodin365-auth__text-action:hover, 673 .toggle-to-otp-login a:hover, 674 .otp-resend-link a:hover, 675 .btn-edit-mobile:hover, 676 .sign-up-message a:hover { 677 text-decoration: underline; 678 } 679 680 .page-card-actions { 681 display: flex; 682 flex-direction: column; 683 gap: 1.25rem; 684 margin-top: 1.25rem; 685 } 686 687 .btn-register, 688 .btn-password-login,
689 .btn-login-submit, 690 .btn-forgot, 691 .btn-login-with-email-link, 692 .rodin365-auth .btn-primary { 693 display: flex; 694 align-items: center; 695 justify-content: center; 696 gap: 0.5rem; 697 width: 100%; 698 height: 3rem; 699 border: none; 700 border-radius: var(--rodin365-radius); 701 background: var(--rodin365-primary) !important; 702 color: var(--rodin365-primary-foreground) !important; 703 font-size: 0.875rem; 704 font-weight: 700; 705 cursor: pointer; 706 transition: background 0.15s ease, opacity 0.15s ease; 707 padding: 0 1rem; 708 box-shadow: none !important; 709 } 710 711 .btn-register:hover, 712 .btn-password-login:hover, 713 .btn-login-submit:hover:not(:disabled), 714 .btn-forgot:hover, 715 .btn-login-with-email-link:hover, 716 .rodin365-auth .btn-primary:hover { 717 background: var(--rodin365-primary-deep) !important; 718 color: var(--rodin365-primary-foreground) !important; 719 } 720 721 .btn-register:disabled, 722 .btn-password-login:disabled, 723 .btn-login-submit:disabled { 724 opacity: 0.6; 725 cursor: not-allowed; 726 background: var(--rodin365-primary) !important; 727 color: var(--rodin365-primary-foreground) !important; 728 } 729 730 .btn-toggle-password-login { 731 width: auto; 732 height: auto; 733 margin: 0 auto; 734 padding: 0; 735 background: transparent; 736 color: var(--rodin365-primary); 737 border: none; 738 border-radius: 0; 739 font-size: 0.75rem; 740 font-weight: 700; 741 cursor: pointer; 742 } 743 744 .btn-toggle-password-login:hover { 745 background: transparent; 746 color: var(--rodin365-primary); 747 text-decoration: underline; 748 } 749 750 .rodin365-auth__divider { 751 height: 1px; 752 width: 100%; 753 background: var(--rodin365-border); 754 border: none; 755 margin: 0; 756 } 757 758 .toggle-to-otp-login { 759 text-align: center; 760 margin: 0; 761 } 762 763 .toggle-to-otp-login .rodin365-auth__signup-hint { 764 margin-top: 0.5rem; 765 font-size: 0.75rem; 766 color: var(--rodin365-muted); 767 } 768 769 /* OTP */ 770 .otp-instructions { 771 margin-bottom: 0 !important; 772 } 773 774 .mobile-display-container { 775 display: flex; 776 align-items: center; 777 justify-content: center; 778 gap: 0.75rem; 779 width: 100%; 780 background: transparent; 781 border: none; 782 padding: 0; 783 margin: 0 0 0.25rem; 784 } 785 786 .mobile-display-number { 787 direction: ltr; 788 font-size: 0.875rem; 789 font-weight: 700; 790 font-variant-numeric: tabular-nums; 791 color: var(--rodin365-muted); 792 } 793 794 .edit-mobile-btn { 795 display: none; 796 } 797 798 .otp-input-container { 799 display: flex; 800 justify-content: center; 801 gap: 0.5rem; 802 direction: ltr; 803 margin: 0.25rem 0; 804 } 805 806 @media (min-width: 640px) { 807 .otp-input-container { 808 gap: 0.75rem; 809 } 810 } 811 812 .otp-input-wrapper { 813 position: relative; 814 display: inline-block; 815 } 816 817 .otp-input-wrapper::after { 818 display: none !important; 819 } 820 821 .otp-input { 822 width: 2.75rem; 823 height: 3rem; 824 border: 1px solid var(--rodin365-border) !important; 825 border-radius: var(--rodin365-radius); 826 text-align: center; 827 font-size: 1.125rem; 828 font-weight: 700; 829 font-variant-numeric: tabular-nums; 830 outline: none; 831 color: var(--rodin365-foreground); 832 background: var(--rodin365-bg); 833 transition: border-color 0.15s ease, box-shadow 0.15s ease; 834 box-shadow: none !important; 835 } 836 837 @media (min-width: 640px) { 838 .otp-input { 839 width: 3rem; 840 height: 3.5rem; 841 } 842 } 843 844 .otp-input:focus { 845 border-color: var(--rodin365-primary) !important; 846 box-shadow: 0 0 0 2px color-mix(in srgb, var(--rodin365-primary) 25%, transparent) !important; 847 } 848 849 .otp-input.filled { 850 border-color: var(--rodin365-border) !important; 851 color: var(--rodin365-foreground); 852 background: var(--rodin365-bg); 853 } 854 855 .otp-input.otp-invalid, 856 .otp-input.otp-invalid:focus, 857 .otp-input.otp-invalid.filled, 858 .otp-input.otp-invalid.empty { 859 border-color: var(--rodin365-destructive) !important; 860 color: var(--rodin365-destructive) !important; 861 } 862 863 .otp-input.empty:not(:focus) { 864 border-color: var(--rodin365-border) !important; 865 color: transparent; 866 } 867 868 .otp-countdown, 869 .otp-resend-link { 870 text-align: center; 871 margin: 0; 872 font-size: 0.75rem; 873 color: var(--rodin365-muted); 874 } 875 876 .otp-actions { 877 display: flex; 878 flex-direction: column; 879 gap: 1rem; 880 width: 100%; 881 } 882 883 .btn-edit-mobile { 884 width: 100%; 885 text-align: center; 886 padding: 0.25rem; 887 border: none !important; 888 background: transparent !important; 889 color: var(--rodin365-primary) !important; 890 font-size: 0.75rem; 891 font-weight: 700; 892 } 893 894 .terms-text { 895 font-size: 0.75rem; 896 color: var(--rodin365-muted); 897 text-align: center; 898 margin: 0; 899 line-height: 1.5; 900 } 901 902 .sign-up-message { 903 text-align: center; 904 font-size: 0.75rem; 905 color: var(--rodin365-muted); 906 margin-top: 1rem; 907 } 908 909 /* Loader */ 910 .loader-overlay { 911 position: absolute; 912 inset: 0; 913 background: color-mix(in srgb, var(--rodin365-bg) 90%, transparent); 914 display: none; 915 align-items: center; 916 justify-content: center; 917 z-index: 1000; 918 border-radius: var(--rodin365-radius); 919 backdrop-filter: blur(2px); 920 } 921 922 .loader-overlay.active { 923 display: flex; 924 } 925 926 .loader-spinner { 927 width: 2rem; 928 height: 2rem; 929 border: 3px solid var(--rodin365-border); 930 border-top-color: var(--rodin365-primary); 931 border-radius: 50%; 932 animation: rodin365-auth-spin 0.8s linear infinite; 933 } 934 935 @keyframes rodin365-auth-spin { 936 to { transform: rotate(360deg); } 937 } 938 939 .loader-text { 940 margin-top: 0.75rem; 941 color: var(--rodin365-primary); 942 font-size: 0.875rem; 943 font-weight: 700; 944 text-align: center; 945 } 946 947 .loader-container { 948 display: flex; 949 flex-direction: column; 950 align-items: center; 951 justify-content: center; 952 } 953 954 .page-card.loading input, 955 .page-card.loading button, 956 .page-card.loading select, 957 .page-card.loading textarea { 958 pointer-events: none; 959 opacity: 0.6;
960 cursor: not-allowed; 961 } 962 963 .social-logins { 964 margin-top: 1rem; 965 } 966 967 .login-divider { 968 font-size: 0.75rem; 969 color: var(--rodin365-muted); 970 } 971</style> 972 973<div class="page-card-body"> 974 <div class="form-group" id="login-name-field-group" style="display: none;" dir="rtl"> 975 <label id="login-label-name" for="login_name" class="login-label">ÙØ§Ù Ù ÙØ§Ù خاÙÙØ§Ø¯Ú¯Û</label> 976 <input type="text" id="login_name" class="form-control" placeholder="ÙØ§Ù Ù ÙØ§Ù خاÙÙØ§Ø¯Ú¯Û" autocomplete="name" dir="rtl"> 977 </div> 978 <div class="d-flex justify-content-end"> 979 <p id="edit-mobile-number" class="justify-content-end" disabled="false" style="display: none;" 980 onclick="active_number()">ÙÛØ±Ø§ÛØ´ Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ/ اÛÙ ÛÙ</p> 981 </div> 982 <div class="form-group" id="email-field-group" style="display: none;"> 983 <label id="login-label-email" for="login_email" class="login-label">Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ / اÛÙ ÛÙ</label> 984 <div class="email-field" id="email-field"> 985 <input type="text" id="login_email" class="form-control m-0" 986 placeholder="[email protected] ÛØ§ 09*********" 987 autocomplete="username"> 988 <svg class="field-icon email-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" 989 xmlns="http://www.w3.org/2000/svg"> 990 <path 991 d="M2.5 7.65149V15.0757C2.5 15.4374 2.64367 15.7842 2.8994 16.04C3.15513 16.2957 3.50198 16.4394 3.86364 16.4394H16.1364C16.498 16.4394 16.8449 16.2957 17.1006 16.04C17.3563 15.7842 17.5 15.4374 17.5 15.0757V7.65149" 992 stroke="#74808B" stroke-miterlimit="10" stroke-linecap="square" /> 993 <path 994 d="M17.5 7.57572V5.53026C17.5 5.1686 17.3563 4.82176 17.1006 4.56603C16.8449 4.31029 16.498 4.16663 16.1364 4.16663H3.86364C3.50198 4.16663 3.15513 4.31029 2.8994 4.56603C2.64367 4.82176 2.5 5.1686 2.5 5.53026V7.57572L10 10.8333L17.5 7.57572Z" 995 stroke="#74808B" stroke-miterlimit="10" stroke-linecap="square" /> 996 </svg> 997 </div> 998 </div> 999 1000 <div class="form-group" id="password-field-group" style="display: none;"> 1001 <label id="login-label-password" for="login_password" class="login-label">ر٠ز Ø¹Ø¨ÙØ±</label> 1002 <div class="password-field" id="password-field"> 1003 <input type="password" id="login_password" class="form-control m-0" 1004 placeholder="ر٠ز Ø¹Ø¨ÙØ±" autocomplete="current-password"> 1005 <svg class="field-icon password-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" 1006 xmlns="http://www.w3.org/2000/svg"> 1007 <path fill-rule="evenodd" clip-rule="evenodd" 1008 d="M10.0961 1.93768H10.0264C8.94806 1.92763 7.90976 2.34591 7.13951 3.10075C6.36866 3.85619 5.9294 4.88687 5.91833 5.96612L5.91831 5.96612V5.97124V6.52695H4.3125C3.20793 6.52695 2.3125 7.42238 2.3125 8.52695V16.0165C2.3125 17.121 3.20793 18.0165 4.3125 18.0165H15.7356C16.8401 18.0165 17.7356 17.121 17.7356 16.0165V8.52695C17.7356 7.42238 16.8401 6.52695 15.7356 6.52695H14.1297V6.04576C14.1397 4.96742 13.7214 3.92913 12.9666 3.15888C12.2112 2.38803 11.1805 1.94877 10.1012 1.9377V1.93768H10.0961ZM13.1297 6.52695V6.04336V6.03838H13.1297C13.1378 5.22428 12.8222 4.44029 12.2524 3.85881C11.6831 3.27793 10.9067 2.94667 10.0934 2.93768H10.024H10.019V2.93765C9.20491 2.92955 8.42092 3.24512 7.83944 3.81497C7.25856 4.38423 6.9273 5.1607 6.91831 5.9739V6.52695H13.1297ZM4.3125 7.52695C3.76022 7.52695 3.3125 7.97467 3.3125 8.52695V16.0165C3.3125 16.5687 3.76022 17.0165 4.3125 17.0165H15.7356C16.2879 17.0165 16.7356 16.5687 16.7356 16.0165V8.52695C16.7356 7.97467 16.2879 7.52695 15.7356 7.52695H4.3125ZM10.0242 13.2384C10.5581 13.2384 10.9909 12.8056 10.9909 12.2717C10.9909 11.7377 10.5581 11.3049 10.0242 11.3049C9.49023 11.3049 9.05738 11.7377 9.05738 12.2717C9.05738 12.8056 9.49023 13.2384 10.0242 13.2384ZM11.9909 12.2717C11.9909 13.3579 11.1104 14.2384 10.0242 14.2384C8.93794 14.2384 8.05738 13.3579 8.05738 12.2717C8.05738 11.1854 8.93794 10.3049 10.0242 10.3049C11.1104 10.3049 11.9909 11.1854 11.9909 12.2717Z" 1009 fill="#74808B" /> 1010 </svg> 1011 <button 1012 type="button" 1013 class="toggle-password" 1014 data-toggle-target="#login_password" 1015 aria-label="ÙÙ Ø§ÛØ´ ر٠ز Ø¹Ø¨ÙØ±" 1016 aria-pressed="false" 1017 > 1018 <svg class="icon-eye" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 1019 <path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7Z" stroke="currentColor" stroke-width="1.8"/> 1020 <path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" stroke="currentColor" stroke-width="1.8"/> 1021 </svg> 1022 <svg class="icon-eye-off" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 1023 <path d="M3 3l18 18M10.585 10.587A2 2 0 0 0 12 14a2 2 0 0 0 1.414-.586M9.88 4.24A9.05 9.05 0 0 1 12 4c4.478 0 8.268 2.943 9.542 7a10.3 10.3 0 0 1-1.39 2.68M6.228 6.228A10.45 10.45 0 0 0 2.458 12c1.274 4.057 5.065 7 9.542 7a9.74 9.74 0 0 0 4.29-.97" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/> 1024 </svg> 1025 </button> 1026 </div> 1027 </div> 1028 1029 <p class="forgot-password-message d-flex justify-content-end" style="display: none !important;"> 1030 <a href="#forgot">ر٠ز Ø¹Ø¨ÙØ± را ÙØ±Ø§Ù ÙØ´ Ú©Ø±Ø¯Ù Ø§ÛØ¯Ø</a> 1031 </p> 1032 1033 <div class="form-group" id="mobile-field-group"> 1034 1035 1036 <label id="login-label-username" for="login_mobile" class="login-label">Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ</label> 1037 <input type="text" id="login_mobile" class="form-control" placeholder="09*********" autofocus inputmode="tel" dir="ltr"> 1038 1039 </div> 1040 1041 <div class="d-flex justify-content-between" id="counter" dir="rtl"> 1042 <h4 id="countdown-container" class="m-0" style="display: none;"> 1043 <p id="countdown">120 ثاÙÛ٠تا ارسا٠٠جدد کد</p> 1044 </h4>
1045 <h4 id="send-code-again" class="m-0 gap-1 align-items-center" style="display: none;">ارسا٠٠جدد<i 1046 class="bi bi-arrow-clockwise"></i></h4> 1047 </div> 1048</div> 1049 1050 1051<div class="page-card-actions"> 1052 1053 <button id="loginButton" class="btn-register" type="submit"> 1054 ادا٠Ù</button> 1055 1056 1057 <button id="passwordLoginButton" class="btn-password-login" type="submit" style="display: none;"> 1058 ÙØ±Ùد</button> 1059 1060 <hr class="rodin365-auth__divider" id="authModeDivider"> 1061 1062 <button type="button" id="togglePasswordLoginBtn" class="btn-toggle-password-login" onclick="togglePasswordLogin()"> 1063 ÙØ±Ùد با ر٠ز ثابت</button> 1064 1065 <div class="toggle-to-otp-login" id="toggleToOtpLogin" style="display: none;"> 1066 <a href="#" onclick="togglePasswordLogin(); return false;">ÙØ±Ùد با ر٠ز Ûکبار ٠صرÙ</a> 1067 <div class="rodin365-auth__signup-hint"> 1068 ØØ³Ø§Ø¨ Ú©Ø§Ø±Ø¨Ø±Û ÙØ¯Ø§Ø±ÛØ¯Ø 1069 <a href="#" onclick="togglePasswordLogin(); return false;">ثبت ÙØ§Ù </a> 1070 </div> 1071 </div> 1072 1073 1074 <input type="checkbox" class="m-0" id="staff" name="staff" onclick="staff_login()" hidden> 1075 1076 <label class="m-0" id="staff-label" for="staff" style="display: none;">ÙØ±Ùد با ر٠ز ثابت</label> 1077 1078</div> 1079</form> 1080 </div> 1081 </div> 1082 1083 <div class="otp-container"> 1084 <div class="login-content page-card"> 1085 <div class="loader-overlay" id="otpLoader"> 1086 <div class="loader-container"> 1087 <div class="loader-spinner"></div> 1088 <div class="loader-text" dir="rtl">در ØØ§Ù بررسÛ...</div> 1089 </div> 1090 </div> 1091 <div class="page-card-head"> 1092 <h1>تأÛÛØ¯ Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ</h1> 1093 <p class="otp-instructions"> 1094 کد Ù¾ÙØ¬ رÙÙ Û Ø§Ø±Ø³Ø§ÙâØ´Ø¯Ù Ø¨Ù Ø´Ù Ø§Ø±Ù Ø²ÛØ± را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯. 1095 </p> 1096 </div> 1097 <form class="form-signin form-otp-verification" role="form"> 1098 <div class="page-card-body d-flex flex-column align-items-stretch"> 1099 <div class="mobile-display-container"> 1100 <span class="mobile-display-number" id="display_mobile_number" dir="ltr"></span> 1101 </div> 1102 1103 <div class="otp-input-container" dir="ltr"> 1104 <div class="otp-input-wrapper"> 1105 <input type="text" class="otp-input" id="otp_1" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" aria-label="رÙÙ 1 کد تأÛÛØ¯"> 1106 </div> 1107 <div class="otp-input-wrapper"> 1108 <input type="text" class="otp-input" id="otp_2" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رÙÙ 2 کد تأÛÛØ¯"> 1109 </div> 1110 <div class="otp-input-wrapper"> 1111 <input type="text" class="otp-input" id="otp_3" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رÙÙ 3 کد تأÛÛØ¯"> 1112 </div> 1113 <div class="otp-input-wrapper"> 1114 <input type="text" class="otp-input" id="otp_4" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رÙÙ 4 کد تأÛÛØ¯"> 1115 </div> 1116 <div class="otp-input-wrapper"> 1117 <input type="text" class="otp-input" id="otp_5" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="off" aria-label="رÙÙ 5 کد تأÛÛØ¯"> 1118 </div> 1119 </div> 1120 1121 <div class="otp-countdown" id="otp_countdown" style="display: none;"> 1122 <span id="countdown_text" class="fa-num">ارسا٠٠جدد کد تا 2:00</span> 1123 </div> 1124 <div class="otp-resend-link" id="otp_resend_link" style="display: none;"> 1125 <a href="#" onclick="resendOTP(); return false;">ارسا٠٠جدد کد</a> 1126 </div> 1127 1128 1129 </div> 1130 <div class="page-card-actions otp-actions"> 1131 <button type="submit" class="btn-login-submit" id="otp_submit_btn" disabled> 1132 تأÛÛØ¯ ٠ادا٠٠1133 </button> 1134 <button type="button" class="btn-edit-mobile" id="edit_mobile_link" onclick="editMobileNumber()"> 1135 ÙÛØ±Ø§ÛØ´ ش٠ار٠Ù٠را٠1136 </button> 1137 </div> 1138 </form> 1139 </div> 1140 </div><div class="text-center sign-up-message" style="display: none;"> 1141 ØØ³Ø§Ø¨ Ú©Ø§Ø±Ø¨Ø±Û ÙØ¯Ø§Ø±ÛØ¯Ø 1142 <a href="#signup">ثبت ÙØ§Ù </a> 1143 </div></section><section class="for-signup "> 1144 <div class="login-content page-card"><style> 1145 .sign-up-message{ 1146 margin-top: 20px !important; 1147 } 1148 1149 .sign-up-message a{ 1150 color: black !important; 1151 text-decoration: underline !important; 1152 } 1153</style> 1154 1155<form class="form-signin form-signup hide" role="form"> 1156 <div class="page-card-body"> 1157 <div class="form-group"> 1158 <label class="form-label sr-only" for="signup_fullname" >ÙØ§Ù کا٠Ù</label> 1159 <input type="text" id="signup_fullname" class="form-control" dir="rtl" placeholder="ÙØ§Ù کا٠Ù" 1160 required autofocus autocomplete="name"> 1161 </div> 1162 1163 <div class="form-group"> 1164 <label class="form-label sr-only" for="mobile_no">Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ</label> 1165 <input type="text" id="mobile_no" class="form-control text-right" 1166 placeholder="09*********" required autocomplete="username"> 1167 </div> 1168 1169 </div> 1170 <div class="page-card-actions"> 1171 <button class="btn btn-sm btn-primary btn-block btn-signup" 1172 type="submit">ثبت ÙØ§Ù </button> 1173 1174 <p class="text-center sign-up-message"> 1175 <a href="#login">ØØ³Ø§Ø¨ Ú©Ø§Ø±Ø¨Ø±Û Ø¯Ø§Ø±ÛØ¯Ø ÙØ§Ø±Ø¯ شدÙ</a> 1176 </p> 1177 </div> 1178</form></div> 1179 </section> 1180 1181 <section class="for-forgot" style="display: none;"> 1182 1183 <div class="login-content page-card"> 1184 <form class="form-signin form-forgot hide" role="form"> 1185 <div class="page-card-head"> 1186 <h4>ر٠ز Ø¹Ø¨ÙØ± را ÙØ±Ø§Ù ÙØ´ Ú©Ø±Ø¯Ù Ø§ÛØ¯</h4> 1187 </div> 1188 <div class="page-card-body"> 1189 1190 1191 <div class="mobile-field"> 1192 <input type="text" id="forgot_mobile" class="form-control" placeholder="09*********" 1193 required autofocus autocomplete="username"> 1194 </div> 1195 1196 1197 </div> 1198 <div class="page-card-actions"> 1199 <button class="btn btn-sm btn-primary btn-block btn-forgot" type="submit">Ø¨Ø§Ø²ÙØ´Ø§ÙÛ Ø±Ù Ø² Ø¹Ø¨ÙØ±</button> 1200 <p class="text-center verification-mail"> 1201 1202 </p> 1203 <p class="text-center sign-up-message"> 1204 <a href="#login">بازگشت Ø¨Ù ØµÙØÙ ÙØ±Ùد</a> 1205 </p> 1206 </div> 1207 </form> 1208 </div> 1209 </section> 1210 1211 <section class="for-login-with-email-link"> 1212 <div class="login-content page-card"> 1213 <form class="form-signin form-login-with-email-link hide" role="form"> 1214 <div class="page-card-head"> 1215 <h1>با ÙÛÙÚ© اÛÙ ÛÙ ÙØ§Ø±Ø¯ Ø´ÙÛØ¯</h1> 1216 </div> 1217 <div class="page-card-body"> 1218 <div class="email-field"> 1219 <label class="login-label" for="login_with_email_link_email">اÛÙ ÛÙ</label> 1220 <input type="email" id="login_with_email_link_email" class="form-control" 1221 placeholder="آدرس اÛÙ ÛÙ" required autofocus autocomplete="username"> 1222 </div> 1223 </div> 1224 <div class="page-card-actions"> 1225 <button class="btn-login-with-email-link" type="submit">ارسا٠ÙÛÙÚ© ÙØ±Ùد</button> 1226 <p class="text-center sign-up-message"> 1227 <a href="#login">بازگشت Ø¨Ù ØµÙØÙ ÙØ±Ùد</a> 1228 </p> 1229 </div> 1230 </form> 1231 </div> 1232 </section> 1233 </div> 1234 </div> 1235 </div> 1236</div> 1237 1238
1239<script> 1240 window.system_settings = { 1241 allow_easy_login_with_email: 0 1242 }; 1243 window.get_name_at_sign_up = false; 1244</script>
1244 1245</div> 1246 1247 <div class="page-footer"></div> 1248 </main> 1249 1250</div> 1251 1252</div> 1253 1254 1255 1256 <!-- js should be loaded in body! --> 1257
1257<script> 1258 frappe.boot = {'sysdefaults': {'float_precision': 3, 'date_format': 'yyyy-mm-dd', 'time_format': 'HH:mm:ss'}, 'time_zone': {'system': 'Asia/Tehran', 'user': 'Asia/Kolkata'}} 1259 // for backward compatibility of some libs 1260 frappe.sys_defaults = frappe.boot.sysdefaults; 1261 frappe._messages = frappe.boot["__messages"] || {}; 1262 window.__ = frappe._; 1263 </script>
1263 1264
1264<script type="text/javascript" src="/assets/frappe/dist/js/frappe-web.bundle.SI6CYMX2.js"></script>
1264 1265 1266
1266<script type="text/javascript" src="/website_script.js"></script>
1266 1267
1267<script type="text/javascript" src="/assets/frappe/js/portal_field_validation.js?v=20260715b"></script>
1267 1268
1268<script type="text/javascript" src="/assets/frappe/js/person_name_validation.js"></script>
1268 1269
1269<script type="text/javascript" src="/assets/erpnext/dist/js/erpnext-web.bundle.IO24HLWL.js"></script>
1269 1270
1270<script type="text/javascript" src="/assets/frappe/dist/js/components.bundle.UJ2R2EF7.js"></script>
1270 1271
1271<script type="text/javascript" src="/assets/erpnext/js/persian-date.js"></script>
1271 1272
1272<script type="text/javascript" src="/assets/erpnext/js/persian-datepicker.js"></script>
1272 1273
1273<script type="text/javascript" src="/assets/erpnext/js/jalaali.js"></script>
1273 1274
1274<script type="text/javascript" src="/assets/erpnext/js/persian_read_only.js"></script>
1274 1275
1275<script type="text/javascript" src="/assets/erpnext/js/persian_js_class.js"></script>
1275 1276
1276<script type="text/javascript" src="/assets/erpnext/js/beauty_banner.js?v=1.0.1"></script>
1276 1277
1277<script type="text/javascript" src="/assets/erpnext/js/blog_post_swiper.js?v=1.0.0"></script>
1277 1278
1278<script type="text/javascript" src="/assets/erpnext/js/customer_reviews_rodin.js?v=1.0.0"></script>
1278 1279
1279<script type="text/javascript" src="/assets/erpnext/js/engraving_gallery_swiper_section.js?v=1.0.0"></script>
1279 1280
1280<script type="text/javascript" src="/assets/erpnext/js/item_brand_card_group.js?v=1.0.2"></script>
1280 1281
1281<script type="text/javascript" src="/assets/erpnext/js/mega_nav_4.js?v=1.0.6"></script>
1281 1282
1282<script type="text/javascript" src="/assets/erpnext/js/publishers_section_swiper.js?v=1.0.0"></script>
1282 1283
1283<script type="text/javascript" src="/assets/erpnext/js/subscribe.js?v=1.1.0"></script>
1283 1284
1284<script type="text/javascript" src="/assets/erpnext/js/sub_bs.js?v=1.1.0"></script>
1284 1285
1285<script type="text/javascript" src="/assets/erpnext/js/swipper_group.js?v=1.0.1"></script>
1285 1286
1286<script type="text/javascript" src="/assets/erpnext/js/glightbox.js?v=1.0.0"></script>
1286 1287
1287<script type="text/javascript" src="/assets/erpnext/js/isotope.js?v=1.0.0"></script>
1287 1288
1288<script type="text/javascript" src="/assets/erpnext/js/imagesloaded.js?v=1.0.0"></script>
1288 1289
1289<script type="text/javascript" src="/assets/erpnext/js/rodin_slider.js?v=1.0.52"></script>
1289 1290
1290<script type="text/javascript" src="/assets/erpnext/js/pdf/pdf.js"></script>
1290 1291
1291<script type="text/javascript" src="/assets/erpnext/js/pdf/pdf.worker.js"></script>
1291 1292
1292<script type="text/javascript" src="/assets/rodin_services/js/rodin_navbar_cart_sync.js?v=1.1.0"></script>
1292 1293
1293<script type="text/javascript" src="/assets/rodin_services/js/r365_website_session.js?v=1.3"></script>
vendor: 1 bytes, line 1293
1293
1294<script>// Helper functions for loader 1295function showLoader(loaderId, text) { 1296 const loader = document.getElementById(loaderId); 1297 if (loader) { 1298 if (text) { 1299 const textElement = loader.querySelector('.loader-text'); 1300 if (textElement) { 1301 textElement.textContent = text; 1302 } 1303 } 1304 loader.classList.add('active'); 1305 // Add loading class to page-card to disable inputs 1306 const pageCard = loader.closest('.page-card'); 1307 if (pageCard) { 1308 pageCard.classList.add('loading'); 1309 } 1310 } 1311} 1312 1313function hideLoader(loaderId) { 1314 const loader = document.getElementById(loaderId); 1315 if (loader) { 1316 loader.classList.remove('active'); 1317 // Remove loading class from page-card 1318 const pageCard = loader.closest('.page-card'); 1319 if (pageCard) { 1320 pageCard.classList.remove('loading'); 1321 } 1322 } 1323} 1324 1325// login.js 1326 1327window.disable_signup = false; 1328 1329window.login = {}; 1330 1331window.verify = {}; 1332 1333function person_name_validation_message(result) { 1334 if (!result || result.ok) { 1335 return null; 1336 } 1337 if (result.message) { 1338 return result.message; 1339 } 1340 switch (result.reason) { 1341 case "empty": 1342 return 'ÙØ·Ùا ÙØ§Ù Ø®ÙØ¯ را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯'; 1343 case "first": 1344 return 'ÙØ§Ù ÙÙ Û ØªÙØ§Ùد شا٠٠عدد ÛØ§ ستار٠(*) باشد'; 1345 case "last": 1346 return 'ÙØ§Ù خاÙÙØ§Ø¯Ú¯Û ÙÙ Û ØªÙØ§Ùد شا٠٠عدد ÛØ§ ستار٠(*) باشد'; 1347 default: 1348 return 'Please enter a valid name.'; 1349 } 1350} 1351 1352const LOGIN_PENDING_NAME_KEY = "login_pending_full_name"; 1353 1354function setLoginNameFieldVisible(visible) { 1355 const group = document.getElementById("login-name-field-group"); 1356 const input = document.getElementById("login_name"); 1357 if (!group || !input) { 1358 return; 1359 } 1360 group.style.display = visible ? "block" : "none"; 1361 if (visible) { 1362 input.setAttribute("required", "required"); 1363 bindLoginNameInputFilter(); 1364 } else { 1365 input.removeAttribute("required"); 1366 input.value = ""; 1367 } 1368} 1369 1370function isLoginNameRequired() { 1371 return window.get_name_at_sign_up; 1372} 1373 1374function isLoginNameFieldVisible() { 1375 return $("#login-name-field-group").is(":visible"); 1376} 1377 1378function normalizeLoginFullName(rawName) { 1379 let full_name = frappe.utils.xss_sanitise((rawName || "").trim()); 1380 if (!full_name) { 1381 return { ok: false, full_name: "" }; 1382 } 1383 const nameCheck = 1384 window.frappe_validate_person_name && 1385 window.frappe_validate_person_name.checkSignupFullName(full_name); 1386 if (nameCheck && !nameCheck.ok) { 1387 return { ok: false, full_name: "", message: person_name_validation_message(nameCheck) }; 1388 } 1389 if (nameCheck && nameCheck.ok && nameCheck.normalized) { 1390 full_name = frappe.utils.xss_sanitise(nameCheck.normalized); 1391 } 1392 return { ok: true, full_name }; 1393} 1394 1395function storePendingLoginName(fullName) { 1396 if (fullName) { 1397 sessionStorage.setItem(LOGIN_PENDING_NAME_KEY, fullName); 1398 } else { 1399 sessionStorage.removeItem(LOGIN_PENDING_NAME_KEY); 1400 } 1401} 1402 1403function getPendingLoginName() { 1404 return sessionStorage.getItem(LOGIN_PENDING_NAME_KEY) || ""; 1405} 1406 1407function resolveLoginFullName(mobileNo) { 1408 if (!window.get_name_at_sign_up) { 1409 return { ok: true, full_name: mobileNo }; 1410 } 1411 if (!isLoginNameFieldVisible()) { 1412 return { ok: true, full_name: "" }; 1413 } 1414 return normalizeLoginFullName($("#login_name").val()); 1415} 1416 1417function updateLoginNameForMobile() { 1418 if (!window.get_name_at_sign_up) { 1419 return; 1420 } 1421 const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim()); 1422 const mobileNo = check_digits_ascii(mobile); 1423 const regex = /^09\d{9}$/; 1424 if (!regex.test(mobileNo)) { 1425 setLoginNameFieldVisible(true); 1426 return; 1427 } 1428 frappe.call({ 1429 method: "frappe.core.doctype.user.user.check_got_name", 1430 args: { user: mobileNo }, 1431 callback: function (response) { 1432 setLoginNameFieldVisible(!!response.message); 1433 }, 1434 }); 1435} 1436 1437function loginNameCharIsDigit(ch) { 1438 try { 1439 return /\p{Nd}/u.test(ch); 1440 } catch (e) { 1441 var code = ch.charCodeAt(0); 1442 return ( 1443 (code >= 48 && code <= 57) || 1444 (code >= 0x06f0 && code <= 0x06f9) || 1445 (code >= 0x0660 && code <= 0x0669) 1446 ); 1447 } 1448} 1449 1450function loginNameCharIsLetter(ch) { 1451 try { 1452 return /\p{L}/u.test(ch); 1453 } catch (e) { 1454 return /[A-Za-z\u0600-\u06FF]/.test(ch); 1455 } 1456} 1457 1458function filterLoginNameInput(raw) { 1459 var s = raw == null ? "" : String(raw); 1460 var out = ""; 1461 for (var i = 0; i < s.length; i++) { 1462 var ch = s[i]; 1463 if (/\s/u.test(ch) || ch === "\u200c") { 1464 out += ch; 1465 continue; 1466 } 1467 if (loginNameCharIsDigit(ch) || ch === "*") { 1468 continue; 1469 } 1470 if (loginNameCharIsLetter(ch)) { 1471 out += ch; 1472 } 1473 } 1474 return out; 1475} 1476 1477function bindLoginNameInputFilter() { 1478 var el = document.getElementById("login_name"); 1479 if (!el || el.__loginNameInputBound) { 1480 return; 1481 } 1482 el.__loginNameInputBound = true; 1483 1484 function applyFilter() { 1485 var filtered = filterLoginNameInput(el.value); 1486 if (filtered !== el.value) { 1487 var pos = el.selectionStart; 1488 var newPos = 1489 typeof pos === "number" 1490 ? Math.min(filterLoginNameInput(el.value.slice(0, pos)).length, filtered.length) 1491 : filtered.length; 1492 el.value = filtered; 1493 try { 1494 el.setSelectionRange(newPos, newPos); 1495 } catch (e) { 1496 /* ignore */ 1497 } 1498 } 1499 } 1500 1501 el.addEventListener("input", applyFilter); 1502 el.addEventListener("keydown", function (e) { 1503 if (e.ctrlKey || e.metaKey || e.altKey) { 1504 return; 1505 } 1506 if ( 1507 e.key === "Backspace" || 1508 e.key === "Delete" || 1509 e.key === "Tab" || 1510 e.key === "Enter" || 1511 e.key.indexOf("Arrow") === 0 1512 ) { 1513 return; 1514 } 1515 if (e.key && e.key.length === 1 && !filterLoginNameInput(e.key)) { 1516 e.preventDefault(); 1517 } 1518 }); 1519 el.addEventListener("paste", function (e) { 1520 e.preventDefault(); 1521 var pasted = (e.clipboardData || window.clipboardData).getData("text"); 1522 var start = el.selectionStart == null ? el.value.length : el.selectionStart; 1523 var end = el.selectionEnd == null ? start : el.selectionEnd; 1524 var merged = el.value.slice(0, start) + pasted + el.value.slice(end); 1525 el.value = filterLoginNameInput(merged); 1526 var newPos = filterLoginNameInput(el.value.slice(0, start) + pasted).length; 1527 try { 1528 el.setSelectionRange(newPos, newPos); 1529 } catch (err) { 1530 /* ignore */ 1531 } 1532 }); 1533} 1534 1535function initLoginNameField() { 1536 if (!window.get_name_at_sign_up) { 1537 setLoginNameFieldVisible(false); 1538 return; 1539 } 1540 setLoginNameFieldVisible(true); 1541 $("#login_mobile").off("blur.loginName").on("blur.loginName", updateLoginNameForMobile); 1542} 1543 1544login.bind_events = function () { 1545 $(window).on("hashchange", function () { 1546 login.route(); 1547 }); 1548 1549 1550 $(".form-login").on("submit", function (event) { 1551 1552 event.preventDefault(); 1553 const password = $("#login_password").val(); 1554 const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()); 1555 const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim()); 1556 1557 // Check if we're in password login mode 1558 const isPasswordMode = $('#email-field-group').is(':visible') && $('#password-field-group').is(':visible'); 1559 1560 // If in password mode, validate email and password 1561 if (isPasswordMode) { 1562 if (!user) { 1563 frappe.msgprint('ÙØ·Ùا Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ ÛØ§ اÛÙ Û٠را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯'); 1564 return false;
1565 } 1566 if (!password) { 1567 frappe.msgprint('ÙØ·Ùا ر٠ز Ø¹Ø¨ÙØ± را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯'); 1568 return false; 1569 } 1570 // Show loader for password login 1571 showLoader('loginLoader', 'در ØØ§Ù ÙØ±Ùد...'); 1572 1573 // Continue with normal login flow for password mode 1574 var args = {}; 1575 args.cmd = "login"; 1576 args.usr = check_digits_ascii(user); 1577 args.pwd = check_digits_ascii(password); 1578 args.staff = $("#staff").is(":checked"); 1579 args.device = "desktop"; 1580 1581 login.call(args).then(() => { 1582 hideLoader('loginLoader'); 1583 }).catch(() => { 1584 hideLoader('loginLoader'); 1585 }); 1586 return false; 1587 } 1588 1589 // OTP login mode - validate mobile number BEFORE making API call 1590 if (!mobile || mobile.trim() === '') { 1591 frappe.msgprint('ÙØ·Ùا ÛÚ© Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§Û٠٠عتبر ÙØ§Ø±Ø¯ Ù٠اÛÛØ¯'); 1592 return false; 1593 } 1594 1595 var args = {}; 1596 args.mobile_no = check_digits_ascii(mobile); 1597 const regex = /^09\d{9}$/; 1598 const email_regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ 1599 if (!regex.test(args.mobile_no) && !email_regex.test(args.mobile_no)) { 1600 frappe.msgprint('Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§Û٠٠عتبر ÙÛØ³Øª'); 1601 return false; 1602 } 1603 1604 let allow_email_login = window.system_settings && window.system_settings.allow_easy_login_with_email; 1605 if (email_regex.test(args.mobile_no) && !allow_email_login) { 1606 frappe.msgprint('Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§Û٠٠عتبر ÙÛØ³Øª'); 1607 return false; 1608 } 1609 1610 // Show loader 1611 showLoader('loginLoader', 'در ØØ§Ù ارسا٠کد...'); 1612 1613 let button = document.getElementById("loginButton"); 1614 if (button) { 1615 button.disabled = true; 1616 } 1617 1618 args.cmd = "login"; 1619 args.staff = $("#staff").is(":checked"); 1620 args.device = "desktop"; 1621 1622 // Only set usr and pwd if we're in password mode (they should be empty for OTP mode) 1623 // We already checked isPasswordMode above, so if we reach here, we're in OTP mode 1624 args.usr = ""; 1625 args.pwd = ""; 1626 1627 const nameResult = resolveLoginFullName(args.mobile_no); 1628 if (!nameResult.ok) { 1629 hideLoader("loginLoader"); 1630 if (button) { 1631 button.disabled = false; 1632 } 1633 frappe.msgprint(nameResult.message || 'ÙØ·Ùا ÙØ§Ù Ø®ÙØ¯ را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯'); 1634 return false; 1635 } 1636 1637 const full_name = nameResult.full_name; 1638 args.name = full_name; 1639 args.get_name = window.get_name_at_sign_up; 1640 args.full_name = full_name; 1641 storePendingLoginName(full_name); 1642 1643 // Send OTP and show OTP page 1644 args.email = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()); 1645 args.cmd = "frappe.core.doctype.user.user.login_sign_up_mobile"; 1646 args.redirect_to = ""; 1647 1648 frappe.call({ 1649 type: "POST", 1650 method: "frappe.core.doctype.user.user.login_sign_up_mobile", 1651 args: args, 1652 callback: function(r) { 1653 hideLoader('loginLoader'); 1654 if (button) { 1655 button.disabled = false; 1656 } 1657 if (r && r.exc) { 1658 frappe.msgprint(r.exc); 1659 return; 1660 } 1661 showOTPPage(args.mobile_no, full_name); 1662 if (r && r.message) { 1663 if (typeof r.message === 'object' && r.message.tmp_id) { 1664 document.cookie = "tmp_id=" + r.message.tmp_id; 1665 } else if (r.tmp_id) { 1666 document.cookie = "tmp_id=" + r.tmp_id; 1667 } 1668 } 1669 }, 1670 error: function(r) { 1671 hideLoader('loginLoader'); 1672 if (button) { 1673 button.disabled = false; 1674 } 1675 let message = 'خطا در ارسا٠کد. ÙØ·Ùا Ø¯ÙØ¨Ø§Ø±Ù ØªÙØ§Ø´ Ú©ÙÛØ¯.'; 1676 if (r && r.responseJSON && r.responseJSON._server_messages) { 1677 try { 1678 message = JSON.parse(r.responseJSON._server_messages || '[]').map(function(v) { 1679 try { 1680 return JSON.parse(v).message; 1681 } catch (e) { 1682 return v; 1683 } 1684 }).filter(Boolean).join('<br>') || message; 1685 } catch (e) { 1686 // use default message 1687 } 1688 } 1689 frappe.msgprint(message); 1690 } 1691 }); 1692 return false; 1693 }); 1694 1695 $(".form-signup").on("submit", function (event) { 1696 event.preventDefault(); 1697 var args = {}; 1698 args.cmd = "frappe.core.doctype.user.user.sign_up"; 1699 args.email = check_digits_ascii(($("#signup_email").val() || "").trim()); 1700 args.mobile_no = check_digits_ascii(($("#mobile_no").val() || "").trim()); 1701 args.redirect_to = frappe.utils.sanitise_redirect(frappe.utils.get_url_arg("redirect-to")); 1702 args.full_name = frappe.utils.xss_sanitise(($("#signup_fullname").val() || "").trim()); 1703 1704 var signupNameCheck = 1705 window.frappe_validate_person_name && 1706 window.frappe_validate_person_name.checkSignupFullName(args.full_name); 1707 if (signupNameCheck && !signupNameCheck.ok) {
1708 login.set_status(person_name_validation_message(signupNameCheck), "red"); 1709 return false; 1710 } 1711 if (signupNameCheck && signupNameCheck.ok && signupNameCheck.normalized) { 1712 args.full_name = frappe.utils.xss_sanitise(signupNameCheck.normalized); 1713 } 1714 1715 if (!args.mobile_no) { 1716 1717 if (!args.email || !validate_email(args.email) || !args.full_name) { 1718 login.set_status('اÛÙ ÛÙ Ù ÙØ§Ù ٠عتبر Ù ÙØ±Ø¯ ÙÛØ§Ø² است', 'red'); 1719 return false; 1720 } 1721 login.call(args); 1722 } 1723 else { 1724 if (!args.mobile_no || !args.full_name) { 1725 login.set_status('Valid mobile and name required', 'red'); 1726 return false; 1727 } 1728 const regex = /^09\d{9}$/; 1729 if (!regex.test(args.mobile_no)) { 1730 login.set_status('Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§Û٠٠عتبر ÙÛØ³Øª', 'red'); 1731 return false; 1732 } 1733 login.call(args); 1734 window.location.href = "/login#login"; 1735 1736 } 1737 1738 1739 return false; 1740 }); 1741 1742 $(".form-forgot").on("submit", function (event) { 1743 event.preventDefault(); 1744 var args = {}; 1745 args.cmd = "frappe.core.doctype.user.user.reset_password"; 1746 args.user = ($("#forgot_email").val() || "").trim(); 1747 args.mobile_no = ($("#forgot_mobile").val() || "").trim(); 1748 if (!args.mobile_no) { 1749 if (!args.user) { 1750 login.set_status('Ø´ÙØ§Ø³Ù ÙØ±Ùد ٠عتبر Ù ÙØ±Ø¯ ÙÛØ§Ø² است.', 'red'); 1751 return false; 1752 } 1753 login.call(args); 1754 return false; 1755 } 1756 else { 1757 if (!args.mobile_no) { 1758 1759 1760 1761 login.set_status('Valid mobile and name required', 'red'); 1762 return false; 1763 1764 1765 } 1766 const regex = /^09\d{9}$/; 1767 if (!regex.test(args.mobile_no)) { 1768 login.set_status('Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§Û٠٠عتبر ÙÛØ³Øª', 'red'); 1769 return false; 1770 } 1771 login.call(args); 1772 window.location.href = "/login#login"; 1773 1774 } 1775 1776 }); 1777 1778 $(".form-login-with-email-link").on("submit", function (event) { 1779 event.preventDefault(); 1780 var args = {}; 1781 args.cmd = "frappe.www.login.send_login_link"; 1782 args.email = ($("#login_with_email_link_email").val() || "").trim(); 1783 if (!args.email) { 1784 login.set_status('Ø´ÙØ§Ø³Ù ÙØ±Ùد ٠عتبر Ù ÙØ±Ø¯ ÙÛØ§Ø² است.', 'red'); 1785 return false; 1786 } 1787 login.call(args).then(() => { 1788 login.set_status('ÙÛÙÚ© ÙØ±Ùد ب٠اÛÙ Û٠ش٠ا ارسا٠شد', 'blue'); 1789 $("#login_with_email_link_email").val(""); 1790 }).catch(() => { 1791 login.set_status('ارسا٠ÙÛÙÚ© ÙØ±Ùد', 'blue'); 1792 }); 1793 1794 return false; 1795 }); 1796 1797 $(document) 1798 .off("click.rodinTogglePassword", ".toggle-password") 1799 .on("click.rodinTogglePassword", ".toggle-password", function (e) { 1800 e.preventDefault(); 1801 e.stopPropagation(); 1802 var $btn = $(this); 1803 var target = 1804 $btn.attr("data-toggle-target") || 1805 $btn.attr("toggle") || 1806 "#login_password"; 1807 var $input = $(target); 1808 if (!$input.length) { 1809 $input = $btn.closest(".password-field").find('input[type="password"], input[type="text"]').first(); 1810 } 1811 if (!$input.length) return false; 1812 1813 var show = $input.attr("type") === "password"; 1814 $input.attr("type", show ? "text" : "password"); 1815 $btn 1816 .toggleClass("is-visible", show) 1817 .attr("aria-pressed", show ? "true" : "false") 1818 .attr( 1819 "aria-label", 1820 show ? 'Ù¾ÙÙØ§Ù کرد٠ر٠ز Ø¹Ø¨ÙØ±' : 'ÙÙ Ø§ÛØ´ ر٠ز Ø¹Ø¨ÙØ±' 1821 ); 1822 return false; 1823 }); 1824 1825 1826} 1827 1828 1829login.route = function () { 1830 var route = window.location.hash.slice(1); 1831 if (!route) route = "login"; 1832 route = route.replaceAll("-", "_"); 1833 login[route](); 1834} 1835 1836login.reset_sections = function (hide) { 1837 if (hide || hide === undefined) { 1838 $("section.for-login").toggle(false); 1839 $("section.for-email-login").toggle(false); 1840 $("section.for-forgot").toggle(false); 1841 $("section.for-login-with-email-link").toggle(false); 1842 $("section.for-signup").toggle(false); 1843 } 1844 $('section:not(.signup-disabled) .indicator').each(function () { 1845 $(this).removeClass().addClass('indicator').addClass('blue') 1846 .text($(this).attr('data-text')); 1847 }); 1848} 1849 1850login.login = function () { 1851 login.reset_sections(); 1852 $(".for-login").toggle(true); 1853} 1854 1855login.email = function () { 1856 login.reset_sections(); 1857 $(".for-email-login").toggle(true); 1858 $("#login_email").focus(); 1859} 1860 1861login.steptwo = function () { 1862 login.reset_sections(); 1863 $(".for-login").toggle(true); 1864 $("#login_email").focus(); 1865} 1866 1867login.forgot = function () { 1868 login.reset_sections(); 1869 if ($("#login_email").val()) { 1870 $("#forgot_email").val($("#login_email").val()); 1871 } 1872 $(".for-forgot").toggle(true); 1873 $("#forgot_email").focus(); 1874} 1875 1876login.login_with_email_link = function () { 1877 login.reset_sections(); 1878 if ($("#login_email").val()) { 1879 $("#login_with_email_link_email").val($("#login_email").val()); 1880 } 1881 $(".for-login-with-email-link").toggle(true); 1882 $("#login_with_email_link_email").focus(); 1883} 1884 1885login.signup = function () { 1886 login.reset_sections(); 1887 $(".for-signup").toggle(true); 1888 $("#signup_fullname").focus(); 1889} 1890 1891 1892// Login 1893login.call = function (args, callback) { 1894 // setTimeout(()=>{ 1895 1896 // login.set_status('در ØØ§Ù تأÛÛØ¯...', 'blue'); 1897 // }, "1000") 1898 if (!args.staff) { 1899 login.set_status('ÙØ±Ùد', 'green'); 1900 1901 } else { 1902 1903 login.set_status('در ØØ§Ù تأÛÛØ¯...', 'blue'); 1904 $('.verification-mail').text("Check your email please."); 1905 } 1906 1907 1908 1909 1910 return frappe.call({ 1911 type: "POST", 1912 args: args, 1913 callback: callback, 1914 freeze: true, 1915 statusCode: login.login_handlers 1916 }); 1917} 1918 1919login.set_status = function (message, color) { 1920 $('section:visible .btn-primary').text(message) 1921 if (color == "red") { 1922 $('section:visible .page-card-body').addClass("invalid"); 1923 } 1924} 1925 1926login.set_invalid = function (message) { 1927 $(".login-content.page-card").addClass('invalid-login'); 1928 setTimeout(() => { 1929 $(".login-content.page-card").removeClass('invalid-login'); 1930 }, 500) 1931 login.set_status(message, 'red'); 1932 $("#login_password").focus(); 1933} 1934 1935login.login_handlers = (function () { 1936 var get_error_handler = function (default_message) { 1937 return function (xhr, data) { 1938 if (xhr.responseJSON) { 1939 data = xhr.responseJSON; 1940 } 1941 1942 var message = default_message; 1943 if (data._server_messages) { 1944 message = ($.map(JSON.parse(data._server_messages || '[]'), function (v) { 1945 // temp fix for messages sent as dict 1946 try { 1947 return JSON.parse(v).message; 1948 } catch (e) { 1949 return v; 1950 } 1951 }) || []).join('<br>') || default_message; 1952 } 1953 1954 if (message === default_message) { 1955 login.set_invalid(message); 1956 } else { 1957 login.reset_sections(false); 1958 } 1959 1960 }; 1961 } 1962 1963 var login_handlers = { 1964 200: function (data) { 1965 if (data.message == 'Logged In') { 1966 storePendingLoginName(""); 1967 login.set_status('ÙØ±Ùد با Ù ÙÙÙÛØª Ø§ÙØ¬Ø§Ù شد', 'green'); 1968 document.body.innerHTML = `<div class="centered splash d-flex w-100 h-100 justify-content-center align-items-center" style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"> 1969 <img src="/assets/erpnext/images/erpnext-logo.svg" 1970 style="max-width: 100px; max-height: 100px;display: flex;"> 1971</div>`; 1972 window.location.href = frappe.utils.sanitise_redirect(frappe.utils.get_url_arg("redirect-to")) || data.home_page; 1973 } else if (data.message == 'Password Reset') { 1974 window.location.href = frappe.utils.sanitise_redirect(data.redirect_to); 1975 } else if (data.message == "No App") { 1976 storePendingLoginName(""); 1977 login.set_status("ÙØ±Ùد با Ù ÙÙÙÛØª Ø§ÙØ¬Ø§Ù شد", 'green'); 1978 if (localStorage) { 1979 localStorage.removeItem("last_visited"); 1980 } 1981 window.location.href = "/"; 1982 } else if (window.location.hash === '#forgot') { 1983 if (data.message === 'not found') { 1984 login.set_status('کاربر Ù Ø¹ØªØ¨Ø±Û ÙÛØ³Øª', 'red'); 1985 } else if (data.message == 'not allowed') { 1986 login.set_status('٠جاز ÙÛØ³Øª', 'red'); 1987 } else if (data.message == 'disabled') { 1988 login.set_status('٠جاز ÙÛØ³Øª: کاربر ØºÛØ±ÙعاÙ', 'red'); 1989 } else { 1990 login.set_status('Ø¯Ø³ØªÙØ±Ø§ÙØ¹Ù Ù ÙØ§ اÛÙ Û٠شد', 'green'); 1991 } 1992 1993 1994 } else if (window.location.hash === '#signup') { 1995 if (cint(data.message[0]) == 0) { 1996 login.set_status(data.message[1], 'red'); 1997 } else { 1998 login.set_status('Ù ÙÙÙÛØª', 'green'); 1999 frappe.msgprint(data.message[1]) 2000 } 2001 //login.set_status(__(data.message), 'green'); 2002 } 2003 2004 //OTP verification 2005 if (data.verification && data.message != 'Logged In') { 2006 login.set_status('Ù ÙÙÙÛØª', 'green'); 2007 2008 document.cookie = "tmp_id=" + data.tmp_id; 2009 2010 if (data.verification.method == 'OTP App') { 2011 continue_otp_app(data.verification.setup, data.verification.qrcode); 2012 } else if (data.verification.method == 'SMS') { 2013 // Show OTP page for SMS 2014 const mobileNo = args.mobile_no || $('#login_mobile').val(); 2015 if (mobileNo) { 2016 showOTPPage(mobileNo); 2017 } else { 2018 continue_sms(data.verification.setup, data.verification.prompt); 2019 } 2020 } else if (data.verification.method == 'Email') { 2021 continue_email(data.verification.setup, data.verification.prompt); 2022 } 2023 } 2024 }, 2025 401: get_error_handler('ÙØ±Ùد ÙØ§Ù عتبر Ø¯ÙØ¨Ø§Ø±Ù Ø§Ù ØªØØ§Ù Ú©ÙÛØ¯.'), 2026 417: get_error_handler('اÙÙ! Ù Ø´Ú©ÙÛ Ù¾ÛØ´ آ٠د'), 2027 404: get_error_handler('کاربر ÙØ¬Ùد ÙØ¯Ø§Ø±Ø¯.') 2028 }; 2029 2030 return login_handlers; 2031})(); 2032 2033frappe.ready(function () { 2034 2035 login.bind_events(); 2036 initLoginNameField(); 2037 2038 if (!window.location.hash) { 2039 window.location.hash = "#login"; 2040 } else { 2041 $(window).trigger("hashchange"); 2042 } 2043 2044 $(".form-signup, .form-forgot, .form-login-with-email-link").removeClass("hide"); 2045 $(document).trigger('login_rendered'); 2046
2047 // Auto-enable staff mode if signup is disabled 2048 if (window.disable_signup) { 2049 $("#staff").prop("checked", true); 2050 staff_login(); 2051 // Hide the staff label and its container when signup is disabled 2052 $("#staff-label").closest(".form-group").hide(); 2053 } 2054}); 2055 2056var verify_token = function (event) { 2057 $(".form-verify").on("submit", function (eventx) { 2058 eventx.preventDefault(); 2059 var args = {}; 2060 args.cmd = "login"; 2061 args.otp = $("#login_token").val(); 2062 args.tmp_id = frappe.get_cookie('tmp_id'); 2063 if (!args.otp) { 2064 frappe.msgprint('ر٠ز ÙØ±Ùد ÙØ§Ø²Ù است'); 2065 return false; 2066 } 2067 login.call(args); 2068 return false; 2069 }); 2070} 2071 2072var request_otp = function (r) { 2073 $('.login-content').empty(); 2074 $('.login-content:visible').append( 2075 `<div id="twofactor_div"> 2076 <form class="form-verify"> 2077 <div class="page-card-head"> 2078 <span class="indicator blue" data-text="Verification">تاÛÛØ¯</span> 2079 </div> 2080 <div id="otp_div"></div> 2081 <input type="text" id="login_token" autocomplete="off" class="form-control" placeholder=کد تاÛÛØ¯ required="" autofocus=""> 2082 <button class="btn btn-sm btn-primary btn-block mt-3" id="verify_token">Ø¨Ø±Ø±Ø³Û Ù Ø¬Ø¯Ø¯</button> 2083 </form> 2084 </div>` 2085 ); 2086 // add event handler for submit button 2087 verify_token(); 2088} 2089 2090var continue_otp_app = function (setup, qrcode) { 2091 request_otp(); 2092 var qrcode_div = $('<div class="text-muted" style="padding-bottom: 15px;"></div>'); 2093 2094 if (setup) { 2095 direction = $('<div>').attr('id', 'qr_info').html('کد ÙÙ Ø§ÛØ´ داد٠شد٠در OTP App را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.'); 2096 qrcode_div.append(direction); 2097 $('#otp_div').prepend(qrcode_div); 2098 } else { 2099 direction = $('<div>').attr('id', 'qr_info').html('راÙâØ§ÙØ¯Ø§Ø²Û OTP با Ø§Ø³ØªÙØ§Ø¯Ù از Ø¨Ø±ÙØ§Ù Ù OTP تک٠ÛÙ ÙØ´Ø¯. ÙØ·Ùا با Ù Ø¯ÛØ± ت٠اس Ø¨Ú¯ÛØ±Ûد'); 2100 qrcode_div.append(direction); 2101 $('#otp_div').prepend(qrcode_div); 2102 } 2103} 2104 2105var continue_sms = function (setup, prompt) { 2106 // Use new OTP page instead of old request_otp 2107 const mobileNo = $('#login_mobile').val() || $('#display_mobile_number').text(); 2108 if (mobileNo && setup) { 2109 showOTPPage(mobileNo); 2110 } else { 2111 // Fallback to old method if needed 2112 request_otp(); 2113 var sms_div = $('<div class="text-muted" style="padding-bottom: 15px;"></div>'); 2114 if (setup) { 2115 sms_div.append(prompt) 2116 $('#otp_div').prepend(sms_div); 2117 } else { 2118 direction = $('<div>').attr('id', 'qr_info').html(prompt || 'اس ا٠اس Ø§Ø±Ø³Ø§Ù ÙØ´Ø¯ ÙØ·Ùا با Ù Ø¯ÛØ± ت٠اس Ø¨Ú¯ÛØ±Ûد'); 2119 sms_div.append(direction); 2120 $('#otp_div').prepend(sms_div) 2121 } 2122 } 2123} 2124 2125var continue_email = function (setup, prompt) { 2126 request_otp(); 2127 var email_div = $('<div class="text-muted" style="padding-bottom: 15px;"></div>'); 2128 2129 if (setup) { 2130 email_div.append(prompt) 2131 $('#otp_div').prepend(email_div); 2132 } else { 2133 var direction = $('<div>').attr('id', 'qr_info').html(prompt || 'اÛÙ Û٠کد تأÛÛØ¯ Ø§Ø±Ø³Ø§Ù ÙØ´Ø¯. ÙØ·Ùا با Ù Ø¯ÛØ± ت٠اس Ø¨Ú¯ÛØ±Ûد'); 2134 email_div.append(direction); 2135 $('#otp_div').prepend(email_div); 2136 } 2137} 2138let interval; // Store the interval ID globally 2139let countdown; // Store the countdown value globally 2140 2141function startCountdown(seconds) { 2142 // Clear any existing interval to avoid multiple intervals running simultaneously 2143 clearInterval(interval); 2144 2145 if (seconds !== 0) { 2146 countdown = seconds; 2147 const countdownElement = document.getElementById('countdown-container'); 2148 countdownElement.style.display = 'flex'; 2149 2150 interval = setInterval(() => { 2151 document.getElementById('countdown').textContent = `${countdown} ثاÙÛ٠تا ارسا٠٠جدد کد`; 2152 if (countdown === 0) { 2153 clearInterval(interval); 2154 var is_checked = $("#staff").is(":checked"); 2155 if (!is_checked) { 2156 const send_again = document.getElementById('send-code-again'); 2157 send_again.style.display = 'flex'; 2158 } 2159 } 2160 countdown--; 2161 }, 1000); 2162 } 2163} 2164 2165function stopCountdown() { 2166 clearInterval(interval); 2167} 2168 2169function resetCountdown(seconds) { 2170 stopCountdown(); 2171 startCountdown(seconds); 2172} 2173 2174document.addEventListener('DOMContentLoaded', function () { 2175 2176 // document.getElementById("loginButton").addEventListener("click", function() { 2177 // let button = document.getElementById("loginButton"); 2178 // button.disabled = true; // Disable button 2179 // setTimeout(() => { 2180 // button.disabled = false; // Re-enable after 100ms 2181 // }, 500); 2182 // }); 2183 2184 const login_password = document.getElementById('password-field'); 2185 2186 const login_email = document.getElementById('email-field'); 2187 2188 const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim()); 2189 if (login_password.style.display == 'none' && !mobile) { 2190 $("#login_email").val(""); 2191 $("#login_password").val(""); 2192 login_email.style.display = 'none'; 2193 const username_label = document.getElementById('login-label-username'); 2194 username_label.style.display = 'flex'; 2195 } 2196
2197 const element = document.getElementById("send-code-again"); 2198 // element.addEventListener("click", myFunction); 2199 if (!element.hasEventListener) { 2200 element.addEventListener("click", myFunction); 2201 element.hasEventListener = true; // Mark that the event listener has been added 2202 } 2203 // u = $("#login_email").val(); 2204 2205 2206 function myFunction() { 2207 2208 const login_email = document.getElementById('login_email'); 2209 login_email.disabled = true; 2210 $('#login_email').css('color', 'gray') 2211 $('#login_email').css('background-color', 'rgba(255, 255, 255, 0.43') 2212 2213 // var get_name = false; 2214 // var full_name = ""; 2215 2216 2217 // frappe.call({ 2218 // method: "frappe.website.doctype.website_settings.website_settings.get_name_at_sign_up", 2219 // args:{}, 2220 // callback: function(response){ 2221 // if(response){ 2222 2223 // get_name = response.message; 2224 2225 // } 2226 // } 2227 // }) 2228 2229 // if(get_name){ 2230 // full_name = frappe.utils.xss_sanitise(($("#name").val() || "").trim()) 2231 2232 // }else{ 2233 // full_name = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()) 2234 // } 2235 let inputVal = ($("#login_email").val() || "").trim(); 2236 let isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inputVal); 2237 2238 if (isEmail && allow_email_login) { 2239 frappe.call({ 2240 type: "POST", 2241 method: "frappe.core.doctype.user.user.login_sign_up_mobile", 2242 args: { 2243 email: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()), 2244 full_name: "", 2245 mobile_no: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()), 2246 redirect_to: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()) 2247 }, 2248 2249 callback: function (r) { 2250 const send_again = document.getElementById('send-code-again'); 2251 send_again.style.display = 'none'; 2252 2253 startCountdown(120); 2254 2255 } 2256 }) 2257 2258 } else { 2259 frappe.call({ 2260 type: "POST", 2261 method: "frappe.core.doctype.user.user.login_sign_up_mobile", 2262 args: { 2263 email: "", 2264 full_name: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()), 2265 mobile_no: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()), 2266 redirect_to: frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()) 2267 }, 2268 2269 callback: function (r) { 2270 const send_again = document.getElementById('send-code-again'); 2271 send_again.style.display = 'none'; 2272 2273 startCountdown(120); 2274 2275 } 2276 }) 2277 } 2278 2279 // frappe.call({ 2280 // type: "POST", 2281 // method: "frappe.core.doctype.user.user.login_sign_up_mobile", 2282 // args: (function () { 2283 // let inputVal = ($("#login_email").val() || "").trim(); 2284 // let isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inputVal); 2285 2286 // return { 2287 // email: isEmail ? frappe.utils.xss_sanitise(inputVal) : "", 2288 // full_name: frappe.utils.xss_sanitise(inputVal), 2289 // mobile_no: isEmail ? "" : frappe.utils.xss_sanitise(inputVal), 2290 // redirect_to: frappe.utils.xss_sanitise(inputVal) 2291 // }; 2292 // })(), 2293 2294 // callback: function (r) { 2295 // const send_again = document.getElementById('send-code-again'); 2296 // send_again.style.display = 'none'; 2297 2298 // startCountdown(120); 2299 // } 2300 // }); 2301 2302 } 2303 2304}); 2305 2306function active_number() { 2307 const login_email = document.getElementById('login_email'); 2308 login_email.disabled = false; 2309 2310 const edit_number = document.getElementById('edit-mobile-number'); 2311 edit_number.disabled = true; 2312 stopCountdown(); 2313 2314 2315 $('#login_email').css('color', 'black') 2316 $('#login_email').css('background-color', 'white') 2317 const send_again = document.getElementById('send-code-again'); 2318 send_again.style.display = 'flex'; 2319 // startCountdown(0); 2320 2321} 2322 2323function staff_login() { 2324 var is_staff = $("#staff").is(":checked"); 2325 2326 if (is_staff) { 2327 setLoginNameFieldVisible(false); 2328 login.set_status('ÙØ±Ùد', 'blue'); 2329 const email_label = document.getElementById('login-label-email'); 2330 email_label.style.display = 'flex'; 2331 const password_label = document.getElementById('login-label-password'); 2332 password_label.style.display = 'flex'; 2333 const username_label = document.getElementById('login-label-username'); 2334 username_label.style.display = 'none'; 2335 $("#login_mobile").css("border", "none"); 2336 $("#login_email").css("border", "none"); 2337 $("#login_password").css("border", "none"); 2338 const email_field = document.getElementById('email-field'); 2339 email_field.style.display = 'flex'; 2340 const password_field = document.getElementById('password-field'); 2341 password_field.style.display = 'flex'; 2342 const login_mobile = document.getElementById('login_mobile'); 2343 login_mobile.style.display = 'none'; 2344 const login_email = document.getElementById('login_email'); 2345 login_email.disabled = false;
2346 $('#login_email').css('color', 'black') 2347 $('#login_email').css('background-color', 'white') 2348 2349 const countdownElement = document.getElementById('countdown-container'); 2350 countdownElement.style.display = 'none'; 2351 const send_again = document.getElementById('send-code-again'); 2352 send_again.style.display = 'none '; 2353 const edit_mobile = document.getElementById('edit-mobile-number'); 2354 edit_mobile.style.display = 'none'; 2355 2356 const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()); 2357 2358 // Hide staff label if signup is disabled, otherwise set the text 2359 if (window.disable_signup === false || window.disable_signup === "false") { 2360 $("#staff-label").text("ÙØ±Ùد با ر٠ز Ûکبار ٠صرÙ"); 2361 } 2362 2363 2364 2365 2366 2367 2368 2369 } else { 2370 initLoginNameField(); 2371 login.set_status('ÙØ±Ùد', 'blue'); 2372 $("#login_email").val(""); 2373 $("#login_password").val(""); 2374 2375 const email_label = document.getElementById('login-label-email'); 2376 email_label.style.display = 'none'; 2377 const password_label = document.getElementById('login-label-password'); 2378 password_label.style.display = 'none'; 2379 const username_label = document.getElementById('login-label-username'); 2380 username_label.style.display = 'flex'; 2381 $("#login_mobile").css("border", "none"); 2382 $("#login_email").css("border", "none"); 2383 $("#login_password").css("border", "none"); 2384 const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()); 2385 const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim()); 2386 const pass = $("#login_password").val() 2387 const email_field = document.getElementById('email-field'); 2388 email_field.style.display = 'none'; 2389 const password_field = document.getElementById('password-field'); 2390 password_field.style.display = 'none'; 2391 const login_mobile = document.getElementById('login_mobile'); 2392 login_mobile.style.display = 'flex'; 2393 const login_email = document.getElementById('login_email'); 2394 login_email.disabled = true; 2395 $('#login_email').css('color', 'gray') 2396 $('#login_email').css('background-color', 'rgba(255, 255, 255, 0.43)'); 2397 if (user && mobile) { 2398 2399 const edit_mobile = document.getElementById('edit-mobile-number'); 2400 edit_mobile.style.display = 'flex'; 2401 2402 2403 } 2404 if (user && !mobile) { 2405 2406 $("#login_email").val(""); 2407 $("#login_password").val(""); 2408 2409 } 2410 if (pass && !mobile) { 2411 $("#login_email").val(""); 2412 $("#login_password").val(""); 2413 2414 } 2415 if (password_field.style.display == 'none') { 2416 var edit_mobile = document.getElementById('edit-mobile-number'); 2417 edit_mobile.style.display = 'none'; 2418 } 2419 2420 // Hide staff label if signup is disabled, otherwise set the text 2421 if (window.disable_signup) { 2422 $("#staff-label").closest(".form-group").hide(); 2423 } else { 2424 $("#staff-label").text("ÙØ±Ùد با ر٠ز ثابت"); 2425 } 2426 2427 2428 2429 2430 2431 2432 } 2433 2434} 2435 2436 2437 2438 2439 2440function check_staff() { 2441 var is_checked = $("#staff").is(":checked"); 2442 2443 if ($("#staff-label").text() == "Website User") { 2444 const user = frappe.utils.xss_sanitise(($("#login_email").val() || "").trim()); 2445 const mobile = frappe.utils.xss_sanitise(($("#login_mobile").val() || "").trim()); 2446 2447 } 2448 if (is_checked) { 2449 $("#staff").is(":checked") = false; 2450 2451 2452 } else { 2453 2454 $("#staff").is(":checked") = true; 2455 2456 2457 } 2458 2459} 2460 2461 2462function check_digits_ascii(digits) { 2463 2464 var inputValue = ""; 2465 2466 for (var i = 0; i < digits.length; i++) { 2467 var currentChar = digits.charAt(i); 2468 var ascii = currentChar.charCodeAt(0); 2469 2470 2471 2472 2473 switch (ascii) { 2474 2475 // 0 2476 case 176219: 2477 currentChar = String.fromCharCode(48); 2478 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2479 break; 2480 2481 case 1776: 2482 currentChar = String.fromCharCode(48); 2483 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2484 break; 2485 2486 // 1 2487 case 177219: 2488 currentChar = String.fromCharCode(49); 2489 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2490 break; 2491 2492 case 1777: 2493 currentChar = String.fromCharCode(49); 2494 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2495 break; 2496 2497 // 2 2498 case 178219: 2499 currentChar = String.fromCharCode(50); 2500 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2501 break; 2502 2503 case 1778: 2504 currentChar = String.fromCharCode(50); 2505 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2506 break; 2507 2508 // 3 2509 case 179219: 2510 currentChar = String.fromCharCode(51); 2511 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2512 break; 2513 2514 case 1779: 2515 currentChar = String.fromCharCode(51); 2516 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2517 break; 2518 2519 // 4 2520 case 180219: 2521 currentChar = String.fromCharCode(52); 2522 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2523 break; 2524 2525 case 1780: 2526 currentChar = String.fromCharCode(52); 2527 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2528 break; 2529 2530 // 5 2531 case 181219: 2532 currentChar = String.fromCharCode(53); 2533 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2534 break; 2535 2536 case 1781: 2537 currentChar = String.fromCharCode(53); 2538 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2539 break; 2540 2541 // 6 2542 case 182219: 2543 currentChar = String.fromCharCode(54); 2544 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2545 break; 2546 2547 case 1782: 2548 currentChar = String.fromCharCode(54); 2549 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2550 break; 2551 2552 // 7 2553 case 183219: 2554 currentChar = String.fromCharCode(55); 2555 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2556 break; 2557 2558 case 1783: 2559 currentChar = String.fromCharCode(55); 2560 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2561 break; 2562 2563 // 8 2564 case 184219: 2565 currentChar = String.fromCharCode(56); 2566 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2567 break; 2568 2569 case 1784: 2570 currentChar = String.fromCharCode(56); 2571 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2572 break; 2573 2574 // 9 2575 case 185219: 2576 currentChar = String.fromCharCode(57); 2577 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2578 break; 2579 2580 case 1785: 2581 currentChar = String.fromCharCode(57); 2582 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2583 break; 2584 2585 default: 2586 inputValue = inputValue.substring(0, i) + currentChar + inputValue.substring(i + 1); 2587 } 2588 2589 2590 } 2591 2592 2593 return inputValue 2594 2595} 2596 2597// OTP Management Functions 2598function showOTPPage(mobileNumber, fullName) { 2599 if (fullName) { 2600 storePendingLoginName(fullName); 2601 } 2602 // Hide login loader if active 2603 hideLoader('loginLoader'); 2604 2605 // Convert to Persian digits if needed 2606 const persianDigits = ['Û°', 'Û±', 'Û²', 'Û³', 'Û´', 'Ûµ', 'Û¶', 'Û·', 'Û¸', 'Û¹']; 2607 let displayNumber = mobileNumber; 2608 if (/^\d+$/.test(mobileNumber)) { 2609 displayNumber = mobileNumber.split('').map(d => persianDigits[parseInt(d)]).join(''); 2610 } 2611 2612 // Hide login form container completely 2613 $('.login-form-container').css('display', 'none').attr('style', function(i, s) { 2614 return (s || '') + ' display: none !important;'; 2615 }); 2616 // Show OTP page container - add show-otp class 2617 $('.otp-container').addClass('show-otp'); 2618 2619 // Set mobile number 2620 $('#display_mobile_number').text(displayNumber); 2621 // Focus first OTP input 2622 setTimeout(() => { 2623 $('#otp_1').focus(); 2624 }, 100); 2625 // Clear all OTP inputs 2626 $('.otp-input').val('').removeClass('filled').addClass('empty').css('color', 'transparent'); 2627 clearOTPErrorState(); 2628 otpAutoSubmitEnabled = true; 2629 otpSubmitInProgress = false;
2630 // Disable submit button 2631 $('#otp_submit_btn').prop('disabled', true).text('تأÛÛØ¯ ٠ادا٠Ù'); 2632 2633 // Start countdown timer 2634 startOTPCountdown(120); 2635} 2636 2637function editMobileNumber() { 2638 // Hide OTP loader if active 2639 hideLoader('otpLoader'); 2640 2641 // Get mobile number from display (convert Persian to English if needed) 2642 const displayNumber = $('#display_mobile_number').text(); 2643 const englishDigits = displayNumber.replace(/[Û°-Û¹]/g, function(d) { 2644 return 'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(d); 2645 }).replace(/\D/g, ''); 2646 2647 // Hide OTP page container - remove show-otp class 2648 $('.otp-container').removeClass('show-otp'); 2649 // Show login form container 2650 $('.login-form-container').attr('style', function(i, s) { 2651 return (s ? s.replace(/display:\s*none\s*!important;?/gi, '') : '') + ' display: flex !important;'; 2652 }); 2653 // Set mobile number in input field 2654 $('#login_mobile').val(englishDigits); 2655 // Focus mobile input 2656 setTimeout(() => { 2657 $('#login_mobile').focus().select(); 2658 if (window.get_name_at_sign_up) { 2659 updateLoginNameForMobile(); 2660 } 2661 }, 100); 2662 2663 // Stop countdown if running 2664 if (otpCountdownInterval) { 2665 clearInterval(otpCountdownInterval); 2666 otpCountdownInterval = null; 2667 } 2668 $('#otp_countdown').hide(); 2669 $('#otp_resend_link').hide(); 2670} 2671 2672// Initialize OTP input handlers 2673function initOTPInputs() { 2674 // Function to update empty state (show/hide dash) 2675 function updateEmptyState(input) { 2676 const $input = $(input); 2677 const $wrapper = $input.closest('.otp-input-wrapper'); 2678 if (!$input.val() || $input.val().trim() === '') { 2679 $input.addClass('empty'); 2680 // Only show dash if not focused 2681 if (!$input.is(':focus')) { 2682 $wrapper.addClass('show-dash'); 2683 } else { 2684 $wrapper.removeClass('show-dash'); 2685 } 2686 } else { 2687 $input.removeClass('empty'); 2688 $wrapper.removeClass('show-dash'); 2689 } 2690 } 2691 2692 // Initialize all inputs with empty state 2693 $('.otp-input').each(function() { 2694 updateEmptyState(this); 2695 }); 2696 2697 // Handle focus - border changes to dark blue, hide dash 2698 $('.otp-input').on('focus', function() { 2699 const $input = $(this); 2700 const $wrapper = $input.closest('.otp-input-wrapper'); 2701 // Hide dash when focused 2702 $wrapper.removeClass('show-dash'); 2703 // Border will change to dark blue via CSS :focus 2704 }); 2705 2706 // Handle blur - show dash if empty, reset border 2707 $('.otp-input').on('blur', function() { 2708 const $input = $(this); 2709 updateEmptyState(this); 2710 }); 2711 2712 $('.otp-input').on('input', function() { 2713 const currentInput = $(this); 2714 const value = currentInput.val(); 2715 2716 // Only allow numbers 2717 if (value && !/^\d$/.test(value)) { 2718 currentInput.val(''); 2719 updateEmptyState(currentInput[0]); 2720 return; 2721 } 2722 2723 // Remove empty class and add filled class 2724 if (value) { 2725 clearOTPErrorState(); 2726 currentInput.removeClass('empty'); 2727 currentInput.addClass('filled'); 2728 // Change text color to dark primary color 2729 currentInput.css('color', 'var(--dark-primary)'); 2730 } else { 2731 currentInput.removeClass('filled'); 2732 currentInput.addClass('empty'); 2733 currentInput.css('color', 'transparent'); 2734 } 2735 2736 // Update empty state to manage dash display 2737 updateEmptyState(currentInput[0]); 2738 2739 // Move to next input if value entered 2740 if (value && currentInput.closest('.otp-input-wrapper').next('.otp-input-wrapper').length) { 2741 currentInput.closest('.otp-input-wrapper').next('.otp-input-wrapper').find('.otp-input').focus(); 2742 } 2743 2744 // Check if all fields are filled 2745 checkOTPComplete(); 2746 }); 2747 2748 // Handle backspace 2749 $('.otp-input').on('keydown', function(e) { 2750 const $input = $(this); 2751 if (e.key === 'Backspace' && !$input.val() && $input.closest('.otp-input-wrapper').prev('.otp-input-wrapper').length) { 2752 $input.closest('.otp-input-wrapper').prev('.otp-input-wrapper').find('.otp-input').focus(); 2753 } else if (e.key === 'Backspace' && $input.val()) { 2754 // Clear current input on backspace 2755 setTimeout(() => { 2756 if (!$input.val()) { 2757 $input.removeClass('filled'); 2758 $input.addClass('empty'); 2759 $input.css('color', 'transparent'); 2760 updateEmptyState($input[0]); 2761 } 2762 }, 0); 2763 } 2764 }); 2765 2766 // Handle paste 2767 $('.otp-input').on('paste', function(e) { 2768 e.preventDefault(); 2769 const pastedData = (e.originalEvent.clipboardData || window.clipboardData).getData('text'); 2770 const digits = pastedData.replace(/\D/g, '').substring(0, 5); 2771 2772 $('.otp-input').each(function(index) { 2773 const $input = $(this); 2774 if (index < digits.length) { 2775 $input.val(digits[index]).addClass('filled').removeClass('empty'); 2776 $input.css('color', 'var(--dark-primary)'); 2777 } else { 2778 $input.val('').removeClass('filled').addClass('empty'); 2779 $input.css('color', 'transparent'); 2780 } 2781 updateEmptyState($input[0]); 2782 }); 2783 2784 if (digits.length === 5) { 2785 $('#otp_5').focus(); 2786 } else if (digits.length > 0) { 2787 $('#otp_' + digits.length).focus(); 2788 } 2789 2790 checkOTPComplete(); 2791 }); 2792} 2793 2794function checkOTPComplete() { 2795 const otp1 = $('#otp_1').val(); 2796 const otp2 = $('#otp_2').val(); 2797 const otp3 = $('#otp_3').val(); 2798 const otp4 = $('#otp_4').val(); 2799 const otp5 = $('#otp_5').val(); 2800 2801 if (otp1 && otp2 && otp3 && otp4 && otp5) { 2802 $('#otp_submit_btn').prop('disabled', false); 2803 if (otpAutoSubmitEnabled && !otpSubmitInProgress) { 2804 handleOTPSubmit(true); 2805 } 2806 } else { 2807 $('#otp_submit_btn').prop('disabled', true); 2808 } 2809} 2810 2811// Handle OTP form submission 2812function handleOTPSubmit(isAutoSubmit = false) { 2813 if (otpSubmitInProgress) { 2814 return; 2815 } 2816 2817 const otp = $('#otp_1').val() + $('#otp_2').val() + $('#otp_3').val() + $('#otp_4').val() + $('#otp_5').val(); 2818 2819 if (otp.length !== 5) { 2820 frappe.msgprint('ÙØ·Ùا کد 5 رÙÙ Û Ø±Ø§ Ú©Ø§Ù Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯'); 2821 return; 2822 } 2823 2824 // Get mobile number and convert Persian to English 2825 const displayNumber = $('#display_mobile_number').text(); 2826 const mobileNo = displayNumber.replace(/[Û°-Û¹]/g, function(d) { 2827 return 'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(d); 2828 }).replace(/\D/g, ''); 2829 2830 if (!mobileNo) { 2831 frappe.msgprint('Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ ÛØ§Ùت ÙØ´Ø¯'); 2832 return; 2833 } 2834 2835 otpSubmitInProgress = true; 2836 2837 // Show loader 2838 showLoader('otpLoader', 'در ØØ§Ù بررسÛ...'); 2839 2840 var args = {}; 2841 args.cmd = "login"; 2842 args.usr = mobileNo; // Mobile number as username 2843 args.pwd = otp; // OTP as password for SMS login 2844 args.otp = otp; // Also send as otp for 2FA compatibility 2845 args.mobile_no = mobileNo; 2846 args.tmp_id = frappe.get_cookie('tmp_id'); 2847 const pendingName = getPendingLoginName(); 2848 if (pendingName) { 2849 args.name = pendingName; 2850 } 2851 2852 // Disable submit button during request 2853 $('#otp_submit_btn').prop('disabled', true).text('در ØØ§Ù بررسÛ...'); 2854 2855 login.call(args, function(data) { 2856 // Hide loader 2857 hideLoader('otpLoader'); 2858 otpSubmitInProgress = false;
2859 // Re-enable button if login fails 2860 if (data && data.message !== 'Logged In' && data.message !== 'No App') { 2861 if (isAutoSubmit) { 2862 otpAutoSubmitEnabled = false; 2863 } 2864 setOTPErrorState(); 2865 $('#otp_submit_btn').prop('disabled', false).text('تأÛÛØ¯ ٠ادا٠Ù'); 2866 // Show error message if available 2867 if (data.exc) { 2868 frappe.msgprint(data.exc); 2869 } else if (data.message) { 2870 frappe.msgprint(data.message); 2871 } 2872 } 2873 }).fail(function(error) { 2874 // Hide loader 2875 hideLoader('otpLoader'); 2876 otpSubmitInProgress = false; 2877 otpAutoSubmitEnabled = false; 2878 setOTPErrorState(); 2879 $('#otp_submit_btn').prop('disabled', false).text('تأÛÛØ¯ ٠ادا٠Ù'); 2880 if (error && error.responseJSON && error.responseJSON._server_messages) { 2881 try { 2882 const messages = JSON.parse(error.responseJSON._server_messages || '[]').map(function(v) { 2883 try { 2884 return JSON.parse(v).message; 2885 } catch (e) { 2886 return v; 2887 } 2888 }).filter(Boolean); 2889 if (messages.length) { 2890 frappe.msgprint(messages.join('<br>')); 2891 return; 2892 } 2893 } catch (e) { 2894 // fall through to generic message 2895 } 2896 } 2897 if (error && error.message) { 2898 frappe.msgprint(error.message); 2899 } else { 2900 frappe.msgprint('خطا در ÙØ±Ùد. ÙØ·Ùا Ø¯ÙØ¨Ø§Ø±Ù ØªÙØ§Ø´ Ú©ÙÛØ¯.'); 2901 } 2902 }); 2903} 2904 2905// OTP Countdown Timer 2906let otpCountdownInterval = null; 2907let otpCountdownSeconds = 0; 2908let otpAutoSubmitEnabled = true; 2909let otpSubmitInProgress = false; 2910 2911function clearOTPErrorState() { 2912 $('.otp-input').removeClass('otp-invalid'); 2913} 2914 2915function setOTPErrorState() { 2916 $('.otp-input').addClass('otp-invalid'); 2917} 2918 2919function startOTPCountdown(seconds) { 2920 otpCountdownSeconds = seconds; 2921 2922 // Clear existing interval 2923 if (otpCountdownInterval) { 2924 clearInterval(otpCountdownInterval); 2925 } 2926 2927 // Show countdown, hide resend link 2928 $('#otp_countdown').show(); 2929 $('#otp_resend_link').hide(); 2930 2931 // Update countdown display 2932 updateOTPCountdown(); 2933 2934 // Start interval 2935 otpCountdownInterval = setInterval(function() { 2936 otpCountdownSeconds--; 2937 updateOTPCountdown(); 2938 2939 if (otpCountdownSeconds <= 0) { 2940 clearInterval(otpCountdownInterval); 2941 otpCountdownInterval = null; 2942 // Show resend link, hide countdown 2943 $('#otp_countdown').hide(); 2944 $('#otp_resend_link').show(); 2945 } 2946 }, 1000); 2947} 2948 2949function updateOTPCountdown() { 2950 const minutes = Math.floor(otpCountdownSeconds / 60); 2951 const seconds = otpCountdownSeconds % 60; 2952 const persianDigits = ['Û°', 'Û±', 'Û²', 'Û³', 'Û´', 'Ûµ', 'Û¶', 'Û·', 'Û¸', 'Û¹']; 2953 const toFa = (n) => String(n).split('').map((d) => (/^\d$/.test(d) ? persianDigits[parseInt(d, 10)] : d)).join(''); 2954 const mm = toFa(minutes); 2955 const ss = toFa(String(seconds).padStart(2, '0')); 2956 $('#countdown_text').text('ارسا٠٠جدد کد تا ' + mm + ':' + ss); 2957} 2958 2959function resendOTP() { 2960 const mobileNumber = $('#display_mobile_number').text().replace(/[Û°-Û¹]/g, function(d) { 2961 return 'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(d); 2962 }).replace(/\D/g, ''); 2963 2964 if (!mobileNumber) { 2965 frappe.msgprint('Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ ÛØ§Ùت ÙØ´Ø¯'); 2966 return; 2967 } 2968 2969 // Call API to resend OTP 2970 var args = { 2971 cmd: "frappe.core.doctype.user.user.login_sign_up_mobile", 2972 mobile_no: mobileNumber, 2973 email: "", 2974 full_name: getPendingLoginName(), 2975 redirect_to: "" 2976 }; 2977 2978 frappe.call({ 2979 type: "POST", 2980 method: "frappe.core.doctype.user.user.login_sign_up_mobile", 2981 args: args, 2982 callback: function(r) { 2983 if (r && !r.exc) { 2984 frappe.show_alert('کد ٠جددا٠ارسا٠شد', 3); 2985 // Restart countdown 2986 startOTPCountdown(120); 2987 } else { 2988 frappe.msgprint('خطا در ارسا٠٠جدد کد'); 2989 } 2990 } 2991 }); 2992} 2993 2994// Toggle between mobile OTP login and password login 2995function togglePasswordLogin() { 2996 // Hide any active loaders 2997 hideLoader('loginLoader'); 2998 hideLoader('otpLoader'); 2999 3000 const isPasswordMode = $('#email-field-group').is(':visible'); 3001 3002 if (isPasswordMode) { 3003 // Switch to mobile OTP mode 3004 $('#email-field-group').hide(); 3005 $('#password-field-group').hide(); 3006 $('#login_mobile').closest('.form-group').show(); 3007 initLoginNameField(); 3008 $('#loginButton').show(); 3009 $('#passwordLoginButton').hide(); 3010 $('#togglePasswordLoginBtn').show(); 3011 $('#authModeDivider').show(); 3012 $('#toggleToOtpLogin').hide(); 3013 $('#login-form-title').text('ÙØ±Ùد / ثبت ÙØ§Ù '); 3014 $('#login-form-subtitle').text( 3015 window.get_name_at_sign_up 3016 ? 'Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ Ù ÙØ§Ù Ø®ÙØ¯ را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯' 3017 : 'Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ Ø®ÙØ¯ را ÙØ§Ø±Ø¯ Ú©ÙÛØ¯' 3018 ); 3019 // Clear email and password fields when switching back to OTP mode 3020 $('#login_email').val(''); 3021 $('#login_password').val(''); 3022 $('#login_mobile').focus(); 3023 } else { 3024 // Switch to password login mode â name is only collected in OTP/signup flow 3025 setLoginNameFieldVisible(false); 3026 $('#login_mobile').closest('.form-group').hide(); 3027 $('#email-field-group').show(); 3028 $('#password-field-group').show(); 3029 $('#loginButton').hide(); 3030 $('#passwordLoginButton').show(); 3031 $('#togglePasswordLoginBtn').hide(); 3032 $('#authModeDivider').show(); 3033 $('#toggleToOtpLogin').show(); 3034 $('#login-form-title').text('ÙØ±Ùد با ر٠ز ثابت'); 3035 $('#login-form-subtitle').text('با Ø´Ù Ø§Ø±Ù Ù ÙØ¨Ø§ÛÙ ÛØ§ اÛÙ ÛÙ Ø®ÙØ¯ ÙØ§Ø±Ø¯ Ø´ÙÛØ¯.'); 3036 // Clear mobile field when switching to password mode 3037 $('#login_mobile').val(''); 3038 $('#login_email').focus(); 3039 } 3040} 3041 3042// Initialize on page load 3043$(document).ready(function() { 3044 // Ensure OTP container is hidden on page load 3045 $('.otp-container').removeClass('show-otp'); 3046 $('.login-form-container').attr('style', function(i, s) { 3047 return (s ? s.replace(/display:\s*none\s*!important;?/gi, '') : '') + ' display: flex !important;'; 3048 }); 3049 3050 // Ensure password login fields are hidden by default 3051 $('#email-field-group').hide(); 3052 $('#password-field-group').hide(); 3053 $('#passwordLoginButton').hide(); 3054 $('#toggleToOtpLogin').hide(); 3055 3056 initOTPInputs(); 3057 3058 // Handle OTP form submission 3059 $('.form-otp-verification').on('submit', function(e) { 3060 e.preventDefault(); 3061 handleOTPSubmit(false); 3062 }); 3063});</script>
3063 3064 3065
3065<script>frappe.csrf_token = "None";</script>
3065<!--
3065<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aos.js"></script>
3065 3066
3066<script> 3067 AOS.init({ 3068 duration: 300, // Faster animation (default: 1000ms) 3069 once: true, // Animation happens only once 3070 offset: 200, // Trigger animation 100px before element comes into view 3071 easing: 'ease-in-out' 3072 }); 3073 </script>
3073 --> 3074 3075</body> 3076 3077</html>
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.